@webless/agent 0.4.1 → 0.6.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/embed.cjs CHANGED
@@ -50,7 +50,7 @@ function closeAgentPanel(customerId) {
50
50
  }
51
51
 
52
52
  // src/react/components/AgentWidget/AgentWidget.tsx
53
- var import_react6 = require("react");
53
+ var import_react10 = require("react");
54
54
 
55
55
  // src/react/page-shift.ts
56
56
  var import_react = require("react");
@@ -319,6 +319,9 @@ function runtimeSessionIdKey(visitorSessionId, prefix) {
319
319
  function runtimeStreamIndexKey(visitorSessionId, prefix) {
320
320
  return `${prefix}:eve:${visitorSessionId}:streamIndex`;
321
321
  }
322
+ function runtimeLastMessageKey(visitorSessionId, prefix) {
323
+ return `${prefix}:eve:${visitorSessionId}:lastMessage`;
324
+ }
322
325
  function loadPersistedAgentSession(visitorSessionId, options) {
323
326
  if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return null;
324
327
  const prefix = resolvePrefix(options);
@@ -326,9 +329,11 @@ function loadPersistedAgentSession(visitorSessionId, options) {
326
329
  if (!sessionId) return null;
327
330
  const rawIndex = sessionStorage.getItem(runtimeStreamIndexKey(visitorSessionId, prefix));
328
331
  const streamIndex = rawIndex ? Number.parseInt(rawIndex, 10) : 0;
332
+ const lastMessage = sessionStorage.getItem(runtimeLastMessageKey(visitorSessionId, prefix))?.trim();
329
333
  return {
330
334
  sessionId,
331
- streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0
335
+ streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0,
336
+ ...lastMessage ? { lastMessage } : {}
332
337
  };
333
338
  }
334
339
  function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, options) {
@@ -342,11 +347,19 @@ function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, opt
342
347
  String(Math.max(0, streamIndex))
343
348
  );
344
349
  }
350
+ function savePersistedAgentTurnMessage(visitorSessionId, message, options) {
351
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !message) {
352
+ return;
353
+ }
354
+ const prefix = resolvePrefix(options);
355
+ sessionStorage.setItem(runtimeLastMessageKey(visitorSessionId, prefix), message);
356
+ }
345
357
  function clearPersistedAgentSession(visitorSessionId, options) {
346
358
  if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return;
347
359
  const prefix = resolvePrefix(options);
348
360
  sessionStorage.removeItem(runtimeSessionIdKey(visitorSessionId, prefix));
349
361
  sessionStorage.removeItem(runtimeStreamIndexKey(visitorSessionId, prefix));
362
+ sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
350
363
  }
351
364
 
352
365
  // src/runtime/client.ts
@@ -361,6 +374,12 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
361
374
  if (event.type === "message.completed") {
362
375
  handlers.onComplete?.();
363
376
  }
377
+ if (event.type === "action.result") {
378
+ const result = event.data.result;
379
+ if (result && typeof result === "object" && "output" in result) {
380
+ handlers.onActionResult?.(result.output);
381
+ }
382
+ }
364
383
  if (event.type !== "message.appended") return rendered;
365
384
  const { messageDelta, messageSoFar } = event.data;
366
385
  let delta = messageDelta;
@@ -374,6 +393,12 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
374
393
  if (delta) handlers.onDelta(delta);
375
394
  return next;
376
395
  }
396
+ function isResumeTurnMessage(received, candidate) {
397
+ if (received === candidate) return true;
398
+ return Boolean(candidate) && received.endsWith(`
399
+
400
+ ${candidate}`);
401
+ }
377
402
  function latestTurnEvents(events) {
378
403
  let startIndex = -1;
379
404
  for (let index = events.length - 1; index >= 0; index -= 1) {
@@ -605,6 +630,11 @@ var AgentSession = class {
605
630
  this.session = session;
606
631
  try {
607
632
  const activeSession = session;
633
+ savePersistedAgentTurnMessage(
634
+ this.visitorSessionId,
635
+ message,
636
+ this.storeOptions
637
+ );
608
638
  response = await withCapabilityRefresh(
609
639
  this.capability,
610
640
  () => activeSession.send(message, { signal })
@@ -620,6 +650,11 @@ var AgentSession = class {
620
650
  }
621
651
  }
622
652
  if (!response) {
653
+ savePersistedAgentTurnMessage(
654
+ this.visitorSessionId,
655
+ message,
656
+ this.storeOptions
657
+ );
623
658
  const created = await withCapabilityRefresh(
624
659
  this.capability,
625
660
  () => client.sessions.create({ message, signal })
@@ -674,7 +709,9 @@ var AgentSession = class {
674
709
  );
675
710
  const turnEvents = latestTurnEvents(snapshot.events);
676
711
  const received = turnEvents[0];
677
- if (received?.type !== "message.received" || received.data.message !== message) {
712
+ const lastSent = persisted.lastMessage;
713
+ const inFlight = !turnEvents.some((event) => isTurnBoundary(event));
714
+ if (received?.type !== "message.received" || !(isResumeTurnMessage(received.data.message, message) || inFlight && lastSent !== void 0 && received.data.message === lastSent)) {
678
715
  return null;
679
716
  }
680
717
  let rendered = renderTurn(turnEvents);
@@ -838,8 +875,248 @@ function formatAgentError(error) {
838
875
  return TRANSIENT_AGENT_ERROR_MESSAGE;
839
876
  }
840
877
 
878
+ // src/react/lib/tool-card.ts
879
+ function bookingOfferIdentityKey(offer) {
880
+ const eventTypes = offer.eventTypes.map(
881
+ (item) => `${item.uri}:${item.duration ?? ""}:${item.locationKind ?? ""}:${item.location ?? ""}`
882
+ ).join("|");
883
+ const slots = offer.slots.map((slot) => `${slot.eventTypeUri ?? ""}:${slot.startTime}`).join("|");
884
+ return `${eventTypes}::${slots}` || "offer";
885
+ }
886
+ var FENCE_PATTERN = /```(?:webless-tool-card|json)\s*([\s\S]*?)```/gi;
887
+ function asRecord(value) {
888
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
889
+ }
890
+ function asString(value) {
891
+ return typeof value === "string" ? value.trim() : "";
892
+ }
893
+ function isEventUri(value) {
894
+ return /^https:\/\/api\.calendly\.com\/scheduled_events\/[^/]+$/i.test(value);
895
+ }
896
+ function isEventTypeUri(value) {
897
+ return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
898
+ }
899
+ function parseToolCard(value) {
900
+ const record = asRecord(value);
901
+ if (!record) return null;
902
+ if (record.booking_offer && asString(record.type) !== "booking_offer") {
903
+ const nested = parseToolCard(record.booking_offer);
904
+ if (nested) return nested;
905
+ }
906
+ const type = asString(record.type);
907
+ if (type === "booking_offer") {
908
+ const eventTypes = Array.isArray(record.eventTypes) ? record.eventTypes.flatMap((item) => {
909
+ const entry = asRecord(item);
910
+ const uri = asString(entry?.uri);
911
+ if (!entry || !isEventTypeUri(uri)) return [];
912
+ const duration = entry.duration;
913
+ const locationKind = asString(entry.locationKind);
914
+ const location = asString(entry.location);
915
+ return [
916
+ {
917
+ name: asString(entry.name) || "Meeting",
918
+ uri,
919
+ ...typeof duration === "number" ? { duration } : {},
920
+ ...locationKind ? { locationKind } : {},
921
+ ...location ? { location } : {}
922
+ }
923
+ ];
924
+ }) : [];
925
+ const slots = Array.isArray(record.slots) ? record.slots.flatMap((item) => {
926
+ const entry = asRecord(item);
927
+ const startTime = asString(entry?.startTime);
928
+ if (!entry || !startTime) return [];
929
+ const eventTypeUri = asString(entry.eventTypeUri);
930
+ return [
931
+ {
932
+ startTime,
933
+ ...isEventTypeUri(eventTypeUri) ? { eventTypeUri } : {}
934
+ }
935
+ ];
936
+ }) : [];
937
+ if (slots.length === 0) return null;
938
+ return { type: "booking_offer", eventTypes, slots };
939
+ }
940
+ if (type === "booking_confirmed") {
941
+ const eventUri = asString(record.eventUri);
942
+ if (!isEventUri(eventUri)) return null;
943
+ const inviteeUri = asString(record.inviteeUri);
944
+ const inviteeEmail = asString(record.inviteeEmail);
945
+ const startTime = asString(record.startTime);
946
+ return {
947
+ type: "booking_confirmed",
948
+ eventUri,
949
+ ...inviteeUri ? { inviteeUri } : {},
950
+ ...inviteeEmail ? { inviteeEmail } : {},
951
+ ...startTime ? { startTime } : {}
952
+ };
953
+ }
954
+ if (type === "booking_canceled") {
955
+ const eventUri = asString(record.eventUri);
956
+ if (!isEventUri(eventUri)) return null;
957
+ return { type: "booking_canceled", eventUri };
958
+ }
959
+ return null;
960
+ }
961
+ function formatBookingOfferFence(offer) {
962
+ return [
963
+ "```webless-tool-card",
964
+ JSON.stringify({
965
+ type: "booking_offer",
966
+ eventTypes: offer.eventTypes,
967
+ slots: offer.slots
968
+ }),
969
+ "```"
970
+ ].join("\n");
971
+ }
972
+ function bookingOfferFromActionOutput(output) {
973
+ const record = asRecord(output);
974
+ const data = asRecord(record?.data) ?? record;
975
+ const card = parseToolCard(data);
976
+ return card?.type === "booking_offer" ? card : null;
977
+ }
978
+ function ensureBookingOfferText(text, offer) {
979
+ if (!offer) return text;
980
+ if (extractToolCards(text).some((card) => card.type === "booking_offer")) {
981
+ return text;
982
+ }
983
+ const visible = stripToolCards(text).trim() || text.trim();
984
+ return `${visible}
985
+
986
+ ${formatBookingOfferFence(offer)}`;
987
+ }
988
+ function hideToolCardFences(text) {
989
+ 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();
990
+ }
991
+ function visitorTimeZone() {
992
+ try {
993
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
994
+ } catch {
995
+ return "UTC";
996
+ }
997
+ }
998
+ function extractToolCards(text) {
999
+ const cards = [];
1000
+ for (const match of text.matchAll(FENCE_PATTERN)) {
1001
+ try {
1002
+ const card = parseToolCard(JSON.parse(match[1] ?? ""));
1003
+ if (card) cards.push(card);
1004
+ } catch {
1005
+ }
1006
+ }
1007
+ return cards;
1008
+ }
1009
+ function stripToolCards(text) {
1010
+ return text.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
1011
+ }
1012
+ function localDateKey(date) {
1013
+ if (Number.isNaN(date.getTime())) return "";
1014
+ return [
1015
+ date.getFullYear(),
1016
+ String(date.getMonth() + 1).padStart(2, "0"),
1017
+ String(date.getDate()).padStart(2, "0")
1018
+ ].join("-");
1019
+ }
1020
+ function slotDateKey(startTime) {
1021
+ return localDateKey(new Date(startTime)) || startTime;
1022
+ }
1023
+ function bookingSlotsForEventType(slots, eventTypeUri) {
1024
+ return slots.filter(
1025
+ (slot) => !eventTypeUri || !slot.eventTypeUri || slot.eventTypeUri === eventTypeUri
1026
+ );
1027
+ }
1028
+ function firstAvailableBookingMonth(slots) {
1029
+ let earliest;
1030
+ for (const slot of slots) {
1031
+ const key = slotDateKey(slot.startTime);
1032
+ if (!earliest || key < earliest) earliest = key;
1033
+ }
1034
+ const [year, month] = (earliest ?? slotDateKey((/* @__PURE__ */ new Date()).toISOString())).split("-").map(Number);
1035
+ if (!year || !month) {
1036
+ const now = /* @__PURE__ */ new Date();
1037
+ return { year: now.getFullYear(), month: now.getMonth() };
1038
+ }
1039
+ return { year, month: month - 1 };
1040
+ }
1041
+ function formatMonthTitle(year, month) {
1042
+ return new Intl.DateTimeFormat(void 0, {
1043
+ month: "long",
1044
+ year: "numeric"
1045
+ }).format(new Date(year, month, 1));
1046
+ }
1047
+ function formatLongDate(startTime) {
1048
+ const date = new Date(startTime);
1049
+ if (Number.isNaN(date.getTime())) return startTime;
1050
+ return new Intl.DateTimeFormat(void 0, {
1051
+ weekday: "long",
1052
+ month: "long",
1053
+ day: "numeric"
1054
+ }).format(date);
1055
+ }
1056
+ function weekdayLabels() {
1057
+ return Array.from(
1058
+ { length: 7 },
1059
+ (_, index) => new Intl.DateTimeFormat(void 0, { weekday: "short" }).format(
1060
+ new Date(2026, 7, 3 + index)
1061
+ )
1062
+ );
1063
+ }
1064
+ function formatTimeChip(startTime) {
1065
+ const date = new Date(startTime);
1066
+ if (Number.isNaN(date.getTime())) return startTime;
1067
+ return new Intl.DateTimeFormat(void 0, {
1068
+ hour: "numeric",
1069
+ minute: "2-digit"
1070
+ }).format(date);
1071
+ }
1072
+ function formatSlotTimeZone(startTime) {
1073
+ const date = new Date(startTime);
1074
+ if (Number.isNaN(date.getTime())) return "";
1075
+ return new Intl.DateTimeFormat(void 0, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
1076
+ }
1077
+ function formatSlotLabel(startTime) {
1078
+ const date = new Date(startTime);
1079
+ if (Number.isNaN(date.getTime())) return startTime;
1080
+ return new Intl.DateTimeFormat(void 0, {
1081
+ weekday: "short",
1082
+ month: "short",
1083
+ day: "numeric",
1084
+ hour: "numeric",
1085
+ minute: "2-digit",
1086
+ timeZoneName: "short"
1087
+ }).format(date);
1088
+ }
1089
+ function formatBookingRequest(input) {
1090
+ return [
1091
+ "Book this meeting now with CALENDLY_POST_INVITEE.",
1092
+ "Do not open a Calendly URL and do not list other scheduled events.",
1093
+ "Do not invent a location kind. Use only the location fields below.",
1094
+ `event_type: ${input.eventTypeUri}`,
1095
+ `start_time: ${input.startTime}`,
1096
+ `invitee.name: ${input.inviteeName}`,
1097
+ `invitee.email: ${input.inviteeEmail}`,
1098
+ `invitee.timezone: ${input.timezone}`,
1099
+ ...input.locationKind ? [
1100
+ `location.kind: ${input.locationKind}`,
1101
+ ...input.location ? [`location.location: ${input.location}`] : []
1102
+ ] : ["Do not send a location field."],
1103
+ "After it succeeds, reply with one short confirmation and a webless-tool-card booking_confirmed block using visitor_booking."
1104
+ ].join("\n");
1105
+ }
1106
+ function visitorBookingPrefix(booking) {
1107
+ return [
1108
+ "This visitor already booked a meeting. Use only this meeting:",
1109
+ `- scheduled event URI: ${booking.eventUri}`,
1110
+ ...booking.inviteeUri ? [`- invitee URI: ${booking.inviteeUri}`] : [],
1111
+ ...booking.inviteeEmail ? [`- invitee email: ${booking.inviteeEmail}`] : [],
1112
+ "For details call CALENDLY_GET_EVENT or CALENDLY_GET_EVENT_INVITEE with those URIs.",
1113
+ "If you must list events, pass this invitee_email. Never describe any other scheduled event.",
1114
+ "start_time values from Calendly are UTC."
1115
+ ].join("\n");
1116
+ }
1117
+
841
1118
  // src/react/persisted-conversation.ts
842
- var CONVERSATION_VERSION = 1;
1119
+ var CONVERSATION_VERSION = 2;
843
1120
  function conversationKey(storageKeyPrefix, visitorSessionId) {
844
1121
  return `${storageKeyPrefix}:conversation:${visitorSessionId}`;
845
1122
  }
@@ -849,13 +1126,39 @@ function parseMessage(value) {
849
1126
  if (typeof record.id !== "string" || record.role !== "agent" && record.role !== "visitor" || typeof record.text !== "string" || typeof record.createdAt !== "number" || !Number.isFinite(record.createdAt)) {
850
1127
  return null;
851
1128
  }
1129
+ if (record.role === "visitor") {
1130
+ return {
1131
+ id: record.id,
1132
+ role: "visitor",
1133
+ text: record.text,
1134
+ createdAt: record.createdAt,
1135
+ ...typeof record.runtimeText === "string" && record.runtimeText ? { runtimeText: record.runtimeText } : {}
1136
+ };
1137
+ }
852
1138
  return {
853
1139
  id: record.id,
854
- role: record.role,
1140
+ role: "agent",
855
1141
  text: record.text,
856
1142
  createdAt: record.createdAt
857
1143
  };
858
1144
  }
1145
+ function parseToolStep(value) {
1146
+ if (typeof value !== "object" || value === null) return null;
1147
+ const record = value;
1148
+ if (typeof record.id !== "string" || record.kind !== "planning" && record.kind !== "search" && record.kind !== "specialist" || typeof record.label !== "string" || record.state !== "completed" && record.state !== "active" && record.state !== "pending" && record.state !== "error") {
1149
+ return null;
1150
+ }
1151
+ return {
1152
+ id: record.id,
1153
+ kind: record.kind,
1154
+ label: record.label,
1155
+ state: record.state,
1156
+ ...typeof record.detail === "string" && record.detail ? { detail: record.detail } : {}
1157
+ };
1158
+ }
1159
+ function visitorTurnText(message) {
1160
+ return message.role === "visitor" && message.runtimeText ? message.runtimeText : message.text;
1161
+ }
859
1162
  function loadPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
860
1163
  if (typeof sessionStorage === "undefined") return null;
861
1164
  const raw = sessionStorage.getItem(conversationKey(storageKeyPrefix, visitorSessionId));
@@ -864,15 +1167,19 @@ function loadPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
864
1167
  const value = JSON.parse(raw);
865
1168
  if (typeof value !== "object" || value === null) return null;
866
1169
  const record = value;
867
- if (record.version !== CONVERSATION_VERSION || !Array.isArray(record.messages) || typeof record.pending !== "boolean" || typeof record.streamingText !== "string") {
1170
+ if (record.version !== 1 && record.version !== CONVERSATION_VERSION || !Array.isArray(record.messages) || typeof record.pending !== "boolean" || typeof record.streamingText !== "string" || record.version === CONVERSATION_VERSION && !Array.isArray(record.toolSteps)) {
868
1171
  return null;
869
1172
  }
870
1173
  const messages = record.messages.map(parseMessage);
871
1174
  if (messages.some((message) => message === null)) return null;
1175
+ const storedToolSteps = record.version === CONVERSATION_VERSION && Array.isArray(record.toolSteps) ? record.toolSteps : [];
1176
+ const toolSteps = storedToolSteps.map(parseToolStep);
1177
+ if (toolSteps.some((step) => step === null)) return null;
872
1178
  return {
873
1179
  messages: messages.filter((message) => message !== null),
874
1180
  pending: record.pending,
875
- streamingText: record.streamingText
1181
+ streamingText: record.streamingText,
1182
+ toolSteps: toolSteps.filter((step) => step !== null)
876
1183
  };
877
1184
  } catch {
878
1185
  return null;
@@ -888,6 +1195,42 @@ function savePersistedAgentConversation(storageKeyPrefix, visitorSessionId, conv
888
1195
  function clearPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
889
1196
  if (typeof sessionStorage === "undefined") return;
890
1197
  sessionStorage.removeItem(conversationKey(storageKeyPrefix, visitorSessionId));
1198
+ clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId);
1199
+ }
1200
+ function pendingBookingKey(storageKeyPrefix, visitorSessionId) {
1201
+ return `${storageKeyPrefix}:pending-booking:${visitorSessionId}`;
1202
+ }
1203
+ function loadPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
1204
+ if (typeof sessionStorage === "undefined") return null;
1205
+ const raw = sessionStorage.getItem(
1206
+ pendingBookingKey(storageKeyPrefix, visitorSessionId)
1207
+ );
1208
+ if (!raw) return null;
1209
+ try {
1210
+ const value = JSON.parse(raw);
1211
+ if (typeof value !== "object" || value === null) return null;
1212
+ const record = value;
1213
+ if (typeof record.eventUri !== "string" || !record.eventUri) return null;
1214
+ return {
1215
+ eventUri: record.eventUri,
1216
+ ...typeof record.inviteeUri === "string" && record.inviteeUri ? { inviteeUri: record.inviteeUri } : {},
1217
+ ...typeof record.inviteeEmail === "string" && record.inviteeEmail ? { inviteeEmail: record.inviteeEmail } : {},
1218
+ ...typeof record.startTime === "string" && record.startTime ? { startTime: record.startTime } : {}
1219
+ };
1220
+ } catch {
1221
+ return null;
1222
+ }
1223
+ }
1224
+ function savePendingWidgetBooking(storageKeyPrefix, visitorSessionId, booking) {
1225
+ if (typeof sessionStorage === "undefined") return;
1226
+ sessionStorage.setItem(
1227
+ pendingBookingKey(storageKeyPrefix, visitorSessionId),
1228
+ JSON.stringify(booking)
1229
+ );
1230
+ }
1231
+ function clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
1232
+ if (typeof sessionStorage === "undefined") return;
1233
+ sessionStorage.removeItem(pendingBookingKey(storageKeyPrefix, visitorSessionId));
891
1234
  }
892
1235
 
893
1236
  // src/react/hooks/useAgentChat.ts
@@ -907,6 +1250,7 @@ function createInitialState(greeting = DEFAULT_GREETING) {
907
1250
  journey: null,
908
1251
  followUps: [],
909
1252
  streamingText: "",
1253
+ pendingOffer: null,
910
1254
  error: null
911
1255
  };
912
1256
  }
@@ -916,7 +1260,8 @@ function stateFromConversation(conversation, initialState) {
916
1260
  ...initialState,
917
1261
  messages: conversation.messages,
918
1262
  phase: conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
919
- streamingText: conversation.streamingText
1263
+ streamingText: conversation.streamingText,
1264
+ toolSteps: conversation.toolSteps
920
1265
  };
921
1266
  }
922
1267
  function upsertToolStep(steps, item) {
@@ -989,6 +1334,9 @@ function useAgentChat({
989
1334
  initialState
990
1335
  )
991
1336
  );
1337
+ const pendingBookingRef = (0, import_react2.useRef)(
1338
+ loadPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId)
1339
+ );
992
1340
  const runRef = (0, import_react2.useRef)(null);
993
1341
  const clientRef = (0, import_react2.useRef)(
994
1342
  createAgentClient({
@@ -1021,6 +1369,10 @@ function useAgentChat({
1021
1369
  visitorSessionId: visitorId,
1022
1370
  storageKeyPrefix: resolvedStorageKeyPrefix
1023
1371
  });
1372
+ pendingBookingRef.current = loadPendingWidgetBooking(
1373
+ resolvedStorageKeyPrefix,
1374
+ visitorId
1375
+ );
1024
1376
  setState(
1025
1377
  stateFromConversation(
1026
1378
  loadPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId),
@@ -1043,19 +1395,22 @@ function useAgentChat({
1043
1395
  savePersistedAgentConversation(resolvedStorageKeyPrefix, visitorId, {
1044
1396
  messages: state.messages,
1045
1397
  pending: isAgentBusy(state.phase),
1046
- streamingText: state.streamingText
1398
+ streamingText: state.streamingText,
1399
+ toolSteps: state.toolSteps
1047
1400
  });
1048
1401
  }, [
1049
1402
  resolvedStorageKeyPrefix,
1050
1403
  state.messages,
1051
1404
  state.phase,
1052
1405
  state.streamingText,
1406
+ state.toolSteps,
1053
1407
  visitorId
1054
1408
  ]);
1055
1409
  const reset = (0, import_react2.useCallback)(() => {
1056
1410
  runRef.current?.abort();
1057
1411
  runRef.current = null;
1058
1412
  clientRef.current.reset();
1413
+ pendingBookingRef.current = null;
1059
1414
  clearPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId);
1060
1415
  setState(initialState);
1061
1416
  }, [initialState, resolvedStorageKeyPrefix, visitorId]);
@@ -1067,6 +1422,7 @@ function useAgentChat({
1067
1422
  try {
1068
1423
  let streamStarted = Boolean(initialText);
1069
1424
  let streamed = initialText;
1425
+ const capturedOffers = [];
1070
1426
  const handlers = {
1071
1427
  onWork: (item) => {
1072
1428
  if (!isActiveRun()) return;
@@ -1076,6 +1432,12 @@ function useAgentChat({
1076
1432
  toolSteps: upsertToolStep(prev.toolSteps, item)
1077
1433
  }));
1078
1434
  },
1435
+ onActionResult: (output) => {
1436
+ const offer = bookingOfferFromActionOutput(output);
1437
+ if (!offer) return;
1438
+ capturedOffers.push(offer);
1439
+ setState((prev) => ({ ...prev, pendingOffer: offer }));
1440
+ },
1079
1441
  onDelta: (delta) => {
1080
1442
  if (!isActiveRun()) return;
1081
1443
  if (!streamStarted) {
@@ -1091,7 +1453,8 @@ function useAgentChat({
1091
1453
  setState((prev) => ({
1092
1454
  ...prev,
1093
1455
  phase: "streaming",
1094
- streamingText: streamed
1456
+ streamingText: hideToolCardFences(streamed),
1457
+ pendingOffer: prev.pendingOffer ?? capturedOffers.at(-1) ?? null
1095
1458
  }));
1096
1459
  },
1097
1460
  onComplete: () => {
@@ -1112,18 +1475,34 @@ function useAgentChat({
1112
1475
  });
1113
1476
  }
1114
1477
  if (!isActiveRun() || finalText === null) return;
1478
+ const displayText = ensureBookingOfferText(
1479
+ finalText,
1480
+ capturedOffers.at(-1) ?? null
1481
+ );
1115
1482
  const agentMessage = {
1116
1483
  id: `agent-${Date.now()}`,
1117
1484
  role: "agent",
1118
- text: finalText,
1485
+ text: displayText,
1119
1486
  createdAt: Date.now()
1120
1487
  };
1488
+ const parsedCards = extractToolCards(displayText);
1489
+ for (const card of parsedCards) {
1490
+ if (card.type === "booking_confirmed") {
1491
+ pendingBookingRef.current = card;
1492
+ savePendingWidgetBooking(resolvedStorageKeyPrefix, visitorId, card);
1493
+ }
1494
+ if (card.type === "booking_canceled" && pendingBookingRef.current?.eventUri === card.eventUri) {
1495
+ pendingBookingRef.current = null;
1496
+ clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
1497
+ }
1498
+ }
1121
1499
  setState((prev) => ({
1122
1500
  ...prev,
1123
1501
  phase: "complete",
1124
1502
  messages: [...prev.messages, agentMessage],
1125
1503
  toolSteps: completeActivePlanning(prev.toolSteps),
1126
1504
  streamingText: "",
1505
+ pendingOffer: null,
1127
1506
  followUps: [],
1128
1507
  journey: null
1129
1508
  }));
@@ -1145,26 +1524,54 @@ function useAgentChat({
1145
1524
  } : step
1146
1525
  ),
1147
1526
  streamingText: "",
1527
+ pendingOffer: null,
1148
1528
  error: message
1149
1529
  }));
1150
1530
  runRef.current = null;
1151
1531
  }
1152
1532
  },
1153
- []
1533
+ [resolvedStorageKeyPrefix, visitorId]
1534
+ );
1535
+ const rememberBooking = (0, import_react2.useCallback)(
1536
+ (booking) => {
1537
+ const current = pendingBookingRef.current;
1538
+ if (current?.eventUri === booking.eventUri && current.inviteeEmail === booking.inviteeEmail && current.inviteeUri === booking.inviteeUri) {
1539
+ return;
1540
+ }
1541
+ pendingBookingRef.current = booking;
1542
+ savePendingWidgetBooking(resolvedStorageKeyPrefix, visitorId, booking);
1543
+ },
1544
+ [resolvedStorageKeyPrefix, visitorId]
1545
+ );
1546
+ const forgetBooking = (0, import_react2.useCallback)(
1547
+ (eventUri) => {
1548
+ const current = pendingBookingRef.current;
1549
+ if (!current) return;
1550
+ if (eventUri && current.eventUri !== eventUri) return;
1551
+ pendingBookingRef.current = null;
1552
+ clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
1553
+ },
1554
+ [resolvedStorageKeyPrefix, visitorId]
1154
1555
  );
1155
1556
  const submit = (0, import_react2.useCallback)(
1156
- async (visitorText) => {
1557
+ async (visitorText, options) => {
1157
1558
  if (runRef.current) {
1158
1559
  runRef.current.abort();
1159
1560
  clientRef.current.cancelActive();
1160
1561
  }
1161
1562
  const controller = new AbortController();
1162
1563
  runRef.current = controller;
1564
+ const booking = pendingBookingRef.current;
1565
+ const outgoing = options?.runtimeText ?? visitorText;
1566
+ const runtimeText = booking ? `${visitorBookingPrefix(booking)}
1567
+
1568
+ ${outgoing}` : outgoing;
1163
1569
  const visitorMessage = {
1164
1570
  id: `visitor-${Date.now()}`,
1165
1571
  role: "visitor",
1166
1572
  text: visitorText,
1167
- createdAt: Date.now()
1573
+ createdAt: Date.now(),
1574
+ ...runtimeText !== visitorText ? { runtimeText } : {}
1168
1575
  };
1169
1576
  setState((prev) => ({
1170
1577
  ...prev,
@@ -1181,9 +1588,10 @@ function useAgentChat({
1181
1588
  journey: null,
1182
1589
  followUps: [],
1183
1590
  streamingText: "",
1591
+ pendingOffer: null,
1184
1592
  error: null
1185
1593
  }));
1186
- await runTurn({ controller, resume: false, visitorText });
1594
+ await runTurn({ controller, resume: false, visitorText: runtimeText });
1187
1595
  },
1188
1596
  [runTurn]
1189
1597
  );
@@ -1210,12 +1618,13 @@ function useAgentChat({
1210
1618
  journey: null,
1211
1619
  followUps: [],
1212
1620
  streamingText: "",
1621
+ pendingOffer: null,
1213
1622
  error: null
1214
1623
  }));
1215
1624
  await runTurn({
1216
1625
  controller,
1217
1626
  resume: false,
1218
- visitorText: visitorMessage.text
1627
+ visitorText: visitorTurnText(visitorMessage)
1219
1628
  });
1220
1629
  }, [runTurn, state.messages]);
1221
1630
  (0, import_react2.useEffect)(() => {
@@ -1232,7 +1641,7 @@ function useAgentChat({
1232
1641
  controller,
1233
1642
  initialText: conversation.streamingText,
1234
1643
  resume: true,
1235
- visitorText: visitorMessage.text
1644
+ visitorText: visitorTurnText(visitorMessage)
1236
1645
  });
1237
1646
  return () => {
1238
1647
  if (runRef.current === controller) {
@@ -1252,6 +1661,8 @@ function useAgentChat({
1252
1661
  reset,
1253
1662
  retry,
1254
1663
  submit,
1664
+ rememberBooking,
1665
+ forgetBooking,
1255
1666
  visitorSessionId: visitorId,
1256
1667
  sessionId: clientRef.current.getActiveSessionId()
1257
1668
  };
@@ -1259,13 +1670,6 @@ function useAgentChat({
1259
1670
  function hasVisitorMessages(messages) {
1260
1671
  return messages.some((message) => message.role === "visitor");
1261
1672
  }
1262
- function createIdleSuggestions() {
1263
- return [
1264
- { id: "idle-1", label: "Hello \u2014 what can you do?" },
1265
- { id: "idle-2", label: "Help me evaluate this product" },
1266
- { id: "idle-3", label: "What should I ask you?" }
1267
- ];
1268
- }
1269
1673
  function isAgentBusy(phase) {
1270
1674
  return phase === "thinking" || phase === "running-tools" || phase === "streaming";
1271
1675
  }
@@ -1304,7 +1708,7 @@ function normalizeAgentPlacement(placement) {
1304
1708
  }
1305
1709
 
1306
1710
  // src/react/components/AgentRail/AgentRail.tsx
1307
- var import_react5 = require("react");
1711
+ var import_react9 = require("react");
1308
1712
 
1309
1713
  // src/react/types/conversation.ts
1310
1714
  var defaultAgentRailTheme = {
@@ -1325,15 +1729,62 @@ var defaultAgentRailTheme = {
1325
1729
  fontBody: '"Mulish", "Avenir Next", "Segoe UI", sans-serif',
1326
1730
  fontDisplay: '"Space Grotesk", sans-serif'
1327
1731
  };
1732
+ var defaultDarkAgentRailTheme = {
1733
+ ...defaultAgentRailTheme,
1734
+ brand: "#a77bff",
1735
+ brandSoft: "#2b2140",
1736
+ brandDeep: "#f5f0ff",
1737
+ surface: "#101218",
1738
+ surfaceMuted: "#1a1e27",
1739
+ text: "#f5f7fb",
1740
+ textMuted: "#b6bfce",
1741
+ textSubtle: "#919cad",
1742
+ border: "rgb(226 232 240 / 0.16)",
1743
+ visitorBubble: "#7c3aed",
1744
+ success: "#55cf91",
1745
+ danger: "#ff8da1"
1746
+ };
1747
+
1748
+ // src/react/hooks/useAgentColorScheme.ts
1749
+ var import_react4 = require("react");
1750
+ var DARK_MODE_QUERY = "(prefers-color-scheme: dark)";
1751
+ function subscribeToDarkMode(onChange) {
1752
+ if (typeof window === "undefined" || !window.matchMedia) {
1753
+ return () => void 0;
1754
+ }
1755
+ const mediaQuery = window.matchMedia(DARK_MODE_QUERY);
1756
+ if (typeof mediaQuery.addEventListener === "function") {
1757
+ mediaQuery.addEventListener("change", onChange);
1758
+ return () => mediaQuery.removeEventListener("change", onChange);
1759
+ }
1760
+ mediaQuery.addListener(onChange);
1761
+ return () => mediaQuery.removeListener(onChange);
1762
+ }
1763
+ function getPrefersDarkMode() {
1764
+ return typeof window !== "undefined" && Boolean(window.matchMedia?.(DARK_MODE_QUERY).matches);
1765
+ }
1766
+ function useAgentColorScheme(colorScheme = "auto") {
1767
+ const prefersDarkMode = (0, import_react4.useSyncExternalStore)(
1768
+ subscribeToDarkMode,
1769
+ getPrefersDarkMode,
1770
+ () => false
1771
+ );
1772
+ return resolveAgentColorScheme(colorScheme, prefersDarkMode);
1773
+ }
1774
+ function resolveAgentColorScheme(colorScheme = "auto", prefersDarkMode) {
1775
+ return colorScheme === "auto" ? prefersDarkMode ? "dark" : "light" : colorScheme;
1776
+ }
1328
1777
 
1329
1778
  // src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
1779
+ var import_react5 = require("react");
1330
1780
  var import_jsx_runtime = require("react/jsx-runtime");
1331
1781
  function workSummary(steps, failed, brandLabel) {
1332
1782
  const active = [...steps].reverse().find((step) => step.state === "active");
1333
1783
  if (active?.kind === "specialist")
1334
1784
  return `${active.label} is reviewing your question`;
1335
1785
  if (active?.kind === "search") return "Searching this site";
1336
- if (active) return `${brandLabel} is choosing the best way to help`;
1786
+ if (active)
1787
+ return brandLabel ? `${brandLabel} is choosing the best way to help` : "Choosing the best way to help";
1337
1788
  if (failed) return "Couldn\u2019t complete this request";
1338
1789
  const hasError = steps.some((step) => step.state === "error");
1339
1790
  const specialists = steps.filter(
@@ -1344,23 +1795,24 @@ function workSummary(steps, failed, brandLabel) {
1344
1795
  );
1345
1796
  if (hasError) return "Answered with available information";
1346
1797
  if (specialists.length > 1)
1347
- return `Answer prepared with ${specialists.length} specialists`;
1798
+ return `Brought in ${specialists.length} specialists`;
1348
1799
  if (specialists.length === 1)
1349
- return `Answer prepared with ${specialists[0]?.label}`;
1350
- if (searched) return "Answer prepared from this site";
1800
+ return `Brought in ${specialists[0]?.label}`;
1801
+ if (searched) return "Searched this site";
1351
1802
  return "Answer ready";
1352
1803
  }
1353
1804
  function stepLabel(step, brandLabel) {
1354
- return step.kind === "planning" ? brandLabel : step.label;
1805
+ return step.kind === "planning" ? brandLabel || "Supervisor" : step.label;
1355
1806
  }
1356
1807
  function stepDetail(step, steps) {
1357
1808
  if (step.kind !== "planning" || step.state !== "completed") {
1358
1809
  return step.detail;
1359
1810
  }
1360
1811
  const specialists = steps.filter((item) => item.kind === "specialist");
1361
- if (specialists.length === 1) return `Delegated to ${specialists[0]?.label}`;
1812
+ if (specialists.length === 1)
1813
+ return `Routed your question to ${specialists[0]?.label}`;
1362
1814
  if (specialists.length > 1)
1363
- return `Delegated to ${specialists.length} specialists`;
1815
+ return `Routed your question to ${specialists.length} specialists`;
1364
1816
  if (steps.some((item) => item.kind === "search"))
1365
1817
  return "Used built-in Search & Discovery";
1366
1818
  return step.detail;
@@ -1391,13 +1843,17 @@ function PlanningIcon() {
1391
1843
  ) });
1392
1844
  }
1393
1845
  function AgentActivityBubble({
1394
- brandLabel = "Webless Guide",
1846
+ brandLabel = "",
1395
1847
  brandLogoUrl,
1396
1848
  failed = false,
1397
1849
  steps
1398
1850
  }) {
1399
1851
  const active = steps.some((step) => step.state === "active");
1400
- const delegated = steps.some((step) => step.kind === "specialist");
1852
+ const receiptId = steps.map((step) => step.id).join(":");
1853
+ const [expandedReceiptId, setExpandedReceiptId] = (0, import_react5.useState)(
1854
+ null
1855
+ );
1856
+ const detailsOpen = active || expandedReceiptId === receiptId;
1401
1857
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: "agent-activity-bubble", children: [
1402
1858
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "agent-activity-bubble__status", "aria-live": "polite", children: [
1403
1859
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1409,62 +1865,69 @@ function AgentActivityBubble({
1409
1865
  ),
1410
1866
  workSummary(steps, failed, brandLabel)
1411
1867
  ] }),
1412
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1413
- "details",
1414
- {
1415
- className: "agent-activity-bubble__details",
1416
- open: active || delegated,
1417
- children: [
1418
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: failed ? "What happened" : active ? "Working" : "How this answer was prepared" }),
1419
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "agent-activity-bubble__steps", children: steps.map((step) => {
1420
- const detail = stepDetail(step, steps);
1421
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1422
- "li",
1423
- {
1424
- className: "agent-activity-bubble__step",
1425
- "data-kind": step.kind,
1426
- "data-state": step.state,
1427
- children: [
1428
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1429
- "span",
1430
- {
1431
- className: "agent-activity-bubble__step-icon",
1432
- "aria-hidden": "true",
1433
- children: step.kind === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1434
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlanningIcon, {}),
1435
- brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1436
- "img",
1437
- {
1438
- src: brandLogoUrl,
1439
- alt: "",
1440
- onError: (event) => {
1441
- event.currentTarget.hidden = true;
1442
- }
1443
- }
1444
- ) : null
1445
- ] }) : step.kind === "search" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
1446
- }
1447
- ),
1448
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-copy", children: [
1449
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-heading", children: [
1450
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: stepLabel(step, brandLabel) }),
1451
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: step.kind === "specialist" ? "Specialist" : step.kind === "search" ? "Built-in capability" : "Supervisor" })
1452
- ] }),
1453
- detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
1454
- ] })
1455
- ]
1456
- },
1457
- step.id
1868
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "agent-activity-bubble__details", children: [
1869
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1870
+ "button",
1871
+ {
1872
+ type: "button",
1873
+ className: "agent-activity-bubble__summary",
1874
+ "aria-expanded": detailsOpen,
1875
+ onClick: () => {
1876
+ if (active) return;
1877
+ setExpandedReceiptId(
1878
+ (current) => current === receiptId ? null : receiptId
1458
1879
  );
1459
- }) })
1460
- ]
1461
- }
1462
- )
1880
+ },
1881
+ children: "How this answer was made"
1882
+ }
1883
+ ),
1884
+ detailsOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "agent-activity-bubble__steps", children: steps.map((step) => {
1885
+ const detail = stepDetail(step, steps);
1886
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1887
+ "li",
1888
+ {
1889
+ className: "agent-activity-bubble__step",
1890
+ "data-kind": step.kind,
1891
+ "data-state": step.state,
1892
+ children: [
1893
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1894
+ "span",
1895
+ {
1896
+ className: "agent-activity-bubble__step-icon",
1897
+ "aria-hidden": "true",
1898
+ children: step.kind === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1899
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlanningIcon, {}),
1900
+ brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1901
+ "img",
1902
+ {
1903
+ src: brandLogoUrl,
1904
+ alt: "",
1905
+ onError: (event) => {
1906
+ event.currentTarget.hidden = true;
1907
+ }
1908
+ }
1909
+ ) : null
1910
+ ] }) : step.kind === "search" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
1911
+ }
1912
+ ),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-copy", children: [
1914
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-heading", children: [
1915
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: stepLabel(step, brandLabel) }),
1916
+ step.kind === "planning" && !brandLabel ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: step.kind === "specialist" ? "Specialist" : step.kind === "search" ? "Built-in capability" : "Supervisor" })
1917
+ ] }),
1918
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
1919
+ ] })
1920
+ ]
1921
+ },
1922
+ step.id
1923
+ );
1924
+ }) }) : null
1925
+ ] })
1463
1926
  ] });
1464
1927
  }
1465
1928
 
1466
1929
  // src/react/components/Composer/Composer.tsx
1467
- var import_react4 = require("react");
1930
+ var import_react6 = require("react");
1468
1931
  var import_jsx_runtime2 = require("react/jsx-runtime");
1469
1932
  function SendIcon() {
1470
1933
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 12V4M8 4l-3 3M8 4l3 3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
@@ -1475,8 +1938,8 @@ function Composer({
1475
1938
  variant = "default",
1476
1939
  onSubmit
1477
1940
  }) {
1478
- const [value, setValue] = (0, import_react4.useState)("");
1479
- const inputRef = (0, import_react4.useRef)(null);
1941
+ const [value, setValue] = (0, import_react6.useState)("");
1942
+ const inputRef = (0, import_react6.useRef)(null);
1480
1943
  function submitCurrent() {
1481
1944
  const trimmed = value.trim();
1482
1945
  if (!trimmed || disabled) return;
@@ -1561,34 +2024,338 @@ function FollowUpChips({
1561
2024
  ] });
1562
2025
  }
1563
2026
 
2027
+ // src/react/components/MessageBubble/MessageBubble.tsx
2028
+ var import_react8 = require("react");
2029
+
2030
+ // src/react/components/BookingCard/BookingCard.tsx
2031
+ var import_react7 = require("react");
2032
+ var import_jsx_runtime4 = require("react/jsx-runtime");
2033
+ function monthFromKey(key) {
2034
+ const [year, month] = key.split("-").map(Number);
2035
+ if (!year || !month) return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
2036
+ return { year, month: month - 1 };
2037
+ }
2038
+ function dateKeyFromParts(year, month, day) {
2039
+ return [
2040
+ year,
2041
+ String(month + 1).padStart(2, "0"),
2042
+ String(day).padStart(2, "0")
2043
+ ].join("-");
2044
+ }
2045
+ function calendarCells(year, month) {
2046
+ const firstWeekday = (new Date(year, month, 1).getDay() + 6) % 7;
2047
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
2048
+ const cells = [];
2049
+ for (let index = 0; index < firstWeekday; index += 1) cells.push(null);
2050
+ for (let day = 1; day <= daysInMonth; day += 1) {
2051
+ cells.push({ day, key: dateKeyFromParts(year, month, day) });
2052
+ }
2053
+ while (cells.length < 42) cells.push(null);
2054
+ return cells;
2055
+ }
2056
+ function BookingCard({
2057
+ offer,
2058
+ onBook
2059
+ }) {
2060
+ const fieldId = (0, import_react7.useId)();
2061
+ const defaultType = offer.eventTypes[0]?.uri ?? offer.slots[0]?.eventTypeUri ?? "";
2062
+ const [step, setStep] = (0, import_react7.useState)("date");
2063
+ const [eventTypeUri, setEventTypeUri] = (0, import_react7.useState)(defaultType);
2064
+ const [selectedDate, setSelectedDate] = (0, import_react7.useState)("");
2065
+ const [startTime, setStartTime] = (0, import_react7.useState)("");
2066
+ const [name, setName] = (0, import_react7.useState)("");
2067
+ const [email, setEmail] = (0, import_react7.useState)("");
2068
+ const slots = (0, import_react7.useMemo)(
2069
+ () => bookingSlotsForEventType(offer.slots, eventTypeUri),
2070
+ [eventTypeUri, offer.slots]
2071
+ );
2072
+ const availableByDate = (0, import_react7.useMemo)(() => {
2073
+ const next = /* @__PURE__ */ new Map();
2074
+ for (const slot of slots) {
2075
+ const key = slotDateKey(slot.startTime);
2076
+ if (!next.has(key)) next.set(key, slot.startTime);
2077
+ }
2078
+ return next;
2079
+ }, [slots]);
2080
+ const [visibleMonth, setVisibleMonth] = (0, import_react7.useState)(
2081
+ () => firstAvailableBookingMonth(slots)
2082
+ );
2083
+ function selectEventType(nextType) {
2084
+ setEventTypeUri(nextType);
2085
+ setSelectedDate("");
2086
+ setStartTime("");
2087
+ setVisibleMonth(
2088
+ firstAvailableBookingMonth(bookingSlotsForEventType(offer.slots, nextType))
2089
+ );
2090
+ }
2091
+ const daySlots = (0, import_react7.useMemo)(
2092
+ () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
2093
+ [selectedDate, slots]
2094
+ );
2095
+ const selectedType = offer.eventTypes.find((item) => item.uri === eventTypeUri);
2096
+ const selectedSample = availableByDate.get(selectedDate) ?? startTime;
2097
+ const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
2098
+ const weekdays = (0, import_react7.useMemo)(() => weekdayLabels(), []);
2099
+ const cells = calendarCells(visibleMonth.year, visibleMonth.month);
2100
+ const canPrevMonth = [...availableByDate.keys()].some((key) => {
2101
+ const month = monthFromKey(key);
2102
+ return month.year < visibleMonth.year || month.year === visibleMonth.year && month.month < visibleMonth.month;
2103
+ });
2104
+ const canNextMonth = [...availableByDate.keys()].some((key) => {
2105
+ const month = monthFromKey(key);
2106
+ return month.year > visibleMonth.year || month.year === visibleMonth.year && month.month > visibleMonth.month;
2107
+ });
2108
+ function goToMonth(offset) {
2109
+ setVisibleMonth((current) => {
2110
+ const next = new Date(current.year, current.month + offset, 1);
2111
+ return { year: next.getFullYear(), month: next.getMonth() };
2112
+ });
2113
+ }
2114
+ function selectDate(key) {
2115
+ if (!availableByDate.has(key)) return;
2116
+ setSelectedDate(key);
2117
+ setStartTime("");
2118
+ setStep("time");
2119
+ }
2120
+ function selectTime(value) {
2121
+ setStartTime(value);
2122
+ setStep("details");
2123
+ }
2124
+ function handleSubmit(event) {
2125
+ event.preventDefault();
2126
+ if (!eventTypeUri || !startTime || !name.trim() || !email.trim()) return;
2127
+ onBook?.({
2128
+ displayText: `Book the ${formatSlotLabel(startTime)} demo`,
2129
+ runtimeText: formatBookingRequest({
2130
+ eventTypeUri,
2131
+ inviteeEmail: email.trim(),
2132
+ inviteeName: name.trim(),
2133
+ startTime,
2134
+ timezone: visitorTimeZone(),
2135
+ locationKind: selectedType?.locationKind,
2136
+ location: selectedType?.location
2137
+ })
2138
+ });
2139
+ }
2140
+ 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: [
2141
+ step === "date" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2142
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__title", children: selectedType?.name || "Pick a date" }),
2143
+ timeZone ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "booking-card__tz", children: [
2144
+ "Times in ",
2145
+ timeZone
2146
+ ] }) : null,
2147
+ offer.eventTypes.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-type`, children: [
2148
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Meeting" }),
2149
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2150
+ "select",
2151
+ {
2152
+ id: `${fieldId}-type`,
2153
+ value: eventTypeUri,
2154
+ onChange: (event) => selectEventType(event.target.value),
2155
+ children: offer.eventTypes.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: item.uri, children: item.name }, item.uri))
2156
+ }
2157
+ )
2158
+ ] }) : null,
2159
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__month", children: [
2160
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2161
+ "button",
2162
+ {
2163
+ type: "button",
2164
+ className: "booking-card__nav",
2165
+ "aria-label": "Previous month",
2166
+ disabled: !canPrevMonth,
2167
+ onClick: () => goToMonth(-1),
2168
+ children: "\u2039"
2169
+ }
2170
+ ),
2171
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__month-title", children: formatMonthTitle(visibleMonth.year, visibleMonth.month) }),
2172
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2173
+ "button",
2174
+ {
2175
+ type: "button",
2176
+ className: "booking-card__nav",
2177
+ "aria-label": "Next month",
2178
+ disabled: !canNextMonth,
2179
+ onClick: () => goToMonth(1),
2180
+ children: "\u203A"
2181
+ }
2182
+ )
2183
+ ] }),
2184
+ /* @__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)) }),
2185
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "booking-card__calendar", role: "grid", "aria-label": "Available dates", children: cells.map((cell, index) => {
2186
+ if (!cell) {
2187
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "booking-card__day" }, `empty-${index}`);
2188
+ }
2189
+ const available = availableByDate.has(cell.key);
2190
+ const selected = cell.key === selectedDate;
2191
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2192
+ "button",
2193
+ {
2194
+ type: "button",
2195
+ className: [
2196
+ "booking-card__day",
2197
+ available ? "booking-card__day--available" : "",
2198
+ selected ? "booking-card__day--selected" : ""
2199
+ ].filter(Boolean).join(" "),
2200
+ disabled: !available,
2201
+ "aria-pressed": selected,
2202
+ onClick: () => selectDate(cell.key),
2203
+ children: cell.day
2204
+ },
2205
+ cell.key
2206
+ );
2207
+ }) })
2208
+ ] }, "date") : null,
2209
+ step === "time" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2210
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step-bar", children: [
2211
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2212
+ "button",
2213
+ {
2214
+ type: "button",
2215
+ className: "booking-card__nav",
2216
+ "aria-label": "Back to dates",
2217
+ onClick: () => setStep("date"),
2218
+ children: "\u2039"
2219
+ }
2220
+ ),
2221
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
2222
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__title", children: selectedSample ? formatLongDate(selectedSample) : "Pick a time" }),
2223
+ timeZone ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "booking-card__tz", children: [
2224
+ "Times in ",
2225
+ timeZone
2226
+ ] }) : null
2227
+ ] })
2228
+ ] }),
2229
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "booking-card__times", children: daySlots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2230
+ "button",
2231
+ {
2232
+ type: "button",
2233
+ className: startTime === slot.startTime ? "booking-card__time booking-card__time--selected" : "booking-card__time",
2234
+ onClick: () => selectTime(slot.startTime),
2235
+ children: formatTimeChip(slot.startTime)
2236
+ },
2237
+ slot.startTime
2238
+ )) })
2239
+ ] }, "time") : null,
2240
+ step === "details" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2241
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step-bar", children: [
2242
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2243
+ "button",
2244
+ {
2245
+ type: "button",
2246
+ className: "booking-card__nav",
2247
+ "aria-label": "Back to times",
2248
+ onClick: () => setStep("time"),
2249
+ children: "\u2039"
2250
+ }
2251
+ ),
2252
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
2253
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__title", children: "Enter details" }),
2254
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__tz", children: formatSlotLabel(startTime) }),
2255
+ selectedType?.location ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__tz", children: selectedType.location }) : null
2256
+ ] })
2257
+ ] }),
2258
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__identity", children: [
2259
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-name`, children: [
2260
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Name" }),
2261
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2262
+ "input",
2263
+ {
2264
+ id: `${fieldId}-name`,
2265
+ autoComplete: "name",
2266
+ value: name,
2267
+ onChange: (event) => setName(event.target.value),
2268
+ required: true
2269
+ }
2270
+ )
2271
+ ] }),
2272
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-email`, children: [
2273
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Email" }),
2274
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2275
+ "input",
2276
+ {
2277
+ id: `${fieldId}-email`,
2278
+ type: "email",
2279
+ autoComplete: "email",
2280
+ value: email,
2281
+ onChange: (event) => setEmail(event.target.value),
2282
+ required: true
2283
+ }
2284
+ )
2285
+ ] })
2286
+ ] }),
2287
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", className: "booking-card__submit", children: "Book this time" })
2288
+ ] }, "details") : null
2289
+ ] }) });
2290
+ }
2291
+
1564
2292
  // src/react/components/MessageBubble/MessageBubble.tsx
1565
2293
  var import_streamdown = require("streamdown");
1566
2294
  var import_styles = require("streamdown/styles.css");
1567
- var import_jsx_runtime4 = require("react/jsx-runtime");
1568
- function MessageBubble({ message }) {
2295
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2296
+ function MessageBubble({
2297
+ message,
2298
+ brandLogoUrl,
2299
+ offer,
2300
+ onBook
2301
+ }) {
2302
+ const resolvedLogoUrl = brandLogoUrl?.trim();
2303
+ const [failedLogoUrl, setFailedLogoUrl] = (0, import_react8.useState)(null);
2304
+ const showBrandLogo = Boolean(resolvedLogoUrl) && failedLogoUrl !== resolvedLogoUrl;
2305
+ const cards = message.role === "agent" ? extractToolCards(message.text) : [];
2306
+ const extractedOffers = cards.filter(
2307
+ (card) => card.type === "booking_offer"
2308
+ );
2309
+ const offers = offer ? [offer] : extractedOffers;
2310
+ const visibleText = hideToolCardFences(message.text);
2311
+ const isStreaming = message.role === "agent" && Boolean(message.streaming);
2312
+ const displayText = offers.length > 0 ? visibleText || "Pick a date and time that works for you." : visibleText || (isStreaming ? "" : message.text);
1569
2313
  if (message.role === "visitor") {
1570
- 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 }) });
2314
+ 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 }) });
1571
2315
  }
1572
- 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)(
2316
+ const agentText = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "message-bubble__text", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1573
2317
  import_streamdown.Streamdown,
1574
2318
  {
1575
2319
  animated: true,
1576
2320
  caret: "circle",
1577
2321
  className: "message-bubble__markdown",
1578
2322
  controls: false,
1579
- isAnimating: message.streaming,
2323
+ isAnimating: isStreaming,
1580
2324
  linkSafety: { enabled: false },
1581
- mode: message.streaming ? "streaming" : "static",
2325
+ mode: isStreaming ? "streaming" : "static",
1582
2326
  skipHtml: true,
1583
- children: message.text
2327
+ children: displayText
1584
2328
  }
1585
- ) }) });
2329
+ ) });
2330
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
2331
+ displayText ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "message-bubble__agent-row", children: [
2332
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2333
+ "img",
2334
+ {
2335
+ src: resolvedLogoUrl,
2336
+ alt: "",
2337
+ onError: () => {
2338
+ setFailedLogoUrl(resolvedLogoUrl ?? null);
2339
+ }
2340
+ }
2341
+ ) }),
2342
+ agentText
2343
+ ] }) : agentText : null,
2344
+ offers.map((nextOffer, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2345
+ BookingCard,
2346
+ {
2347
+ offer: nextOffer,
2348
+ onBook
2349
+ },
2350
+ `${bookingOfferIdentityKey(nextOffer)}-${index}`
2351
+ ))
2352
+ ] });
1586
2353
  }
1587
2354
 
1588
2355
  // src/react/components/AgentRail/AgentRail.tsx
1589
- var import_jsx_runtime5 = require("react/jsx-runtime");
2356
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1590
2357
  function MinimizeIcon() {
1591
- 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)(
2358
+ 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)(
1592
2359
  "path",
1593
2360
  {
1594
2361
  d: "M3.5 8h9",
@@ -1599,7 +2366,7 @@ function MinimizeIcon() {
1599
2366
  ) });
1600
2367
  }
1601
2368
  function CloseIcon() {
1602
- 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)(
2369
+ 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)(
1603
2370
  "path",
1604
2371
  {
1605
2372
  d: "M4 4l8 8M12 4l-8 8",
@@ -1610,7 +2377,7 @@ function CloseIcon() {
1610
2377
  ) });
1611
2378
  }
1612
2379
  function NewChatIcon() {
1613
- 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)(
2380
+ 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)(
1614
2381
  "path",
1615
2382
  {
1616
2383
  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",
@@ -1622,7 +2389,7 @@ function NewChatIcon() {
1622
2389
  ) });
1623
2390
  }
1624
2391
  function ExpandIcon() {
1625
- 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)(
2392
+ 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)(
1626
2393
  "path",
1627
2394
  {
1628
2395
  d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
@@ -1634,7 +2401,7 @@ function ExpandIcon() {
1634
2401
  ) });
1635
2402
  }
1636
2403
  function RestoreIcon() {
1637
- 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)(
2404
+ 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)(
1638
2405
  "path",
1639
2406
  {
1640
2407
  d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
@@ -1648,7 +2415,8 @@ function RestoreIcon() {
1648
2415
  function AgentRail({
1649
2416
  state,
1650
2417
  theme,
1651
- brandLabel = "Webless Assist",
2418
+ colorScheme = "auto",
2419
+ brandLabel = "",
1652
2420
  brandLogoUrl,
1653
2421
  poweredByLabel = "Powered by Webless",
1654
2422
  composerPlaceholder = "Ask anything\u2026",
@@ -1660,11 +2428,31 @@ function AgentRail({
1660
2428
  onReset,
1661
2429
  onRetry,
1662
2430
  onSubmit,
1663
- onFollowUpSelect
2431
+ onFollowUpSelect,
2432
+ onBook
1664
2433
  }) {
1665
- const transcriptRef = (0, import_react5.useRef)(null);
1666
- const welcomeTitleId = (0, import_react5.useId)();
1667
- const resolvedTheme = { ...defaultAgentRailTheme, ...theme };
2434
+ const transcriptRef = (0, import_react9.useRef)(null);
2435
+ const resolvedBrandLabel = brandLabel.trim();
2436
+ const resolvedBrandLogoUrl = brandLogoUrl?.trim();
2437
+ const [failedLogoUrl, setFailedLogoUrl] = (0, import_react9.useState)(null);
2438
+ const showBrandLogo = Boolean(resolvedBrandLogoUrl) && failedLogoUrl !== resolvedBrandLogoUrl;
2439
+ const resolvedColorScheme = useAgentColorScheme(colorScheme);
2440
+ const brandedTheme = { ...defaultAgentRailTheme, ...theme };
2441
+ const resolvedTheme = resolvedColorScheme === "dark" ? {
2442
+ ...brandedTheme,
2443
+ brand: theme?.brand ?? defaultDarkAgentRailTheme.brand,
2444
+ brandDeep: defaultDarkAgentRailTheme.brandDeep,
2445
+ brandSoft: `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
2446
+ border: defaultDarkAgentRailTheme.border,
2447
+ danger: defaultDarkAgentRailTheme.danger,
2448
+ success: defaultDarkAgentRailTheme.success,
2449
+ surface: defaultDarkAgentRailTheme.surface,
2450
+ surfaceMuted: defaultDarkAgentRailTheme.surfaceMuted,
2451
+ text: defaultDarkAgentRailTheme.text,
2452
+ textMuted: defaultDarkAgentRailTheme.textMuted,
2453
+ textSubtle: defaultDarkAgentRailTheme.textSubtle,
2454
+ visitorBubble: theme?.visitorBubble ?? theme?.brand ?? defaultDarkAgentRailTheme.visitorBubble
2455
+ } : brandedTheme;
1668
2456
  const railStyle = {
1669
2457
  "--rail-width": resolvedTheme.railMaxWidth,
1670
2458
  "--as-rail-max-width": resolvedTheme.railMaxWidth,
@@ -1682,7 +2470,8 @@ function AgentRail({
1682
2470
  "--as-success": resolvedTheme.success,
1683
2471
  "--as-danger": resolvedTheme.danger,
1684
2472
  "--as-font-body": resolvedTheme.fontBody,
1685
- "--as-font-display": resolvedTheme.fontDisplay
2473
+ "--as-font-display": resolvedTheme.fontDisplay,
2474
+ colorScheme: resolvedColorScheme
1686
2475
  };
1687
2476
  const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming";
1688
2477
  const showActivity = state.toolSteps.length > 0;
@@ -1693,7 +2482,7 @@ function AgentRail({
1693
2482
  const greeting = state.messages.find(
1694
2483
  (message) => message.role === "agent" && message.id === "greeting"
1695
2484
  );
1696
- const visibleMessages = hasVisitorMessages2 ? state.messages.filter((message) => message.id !== "greeting") : [];
2485
+ const visibleMessages = hasVisitorMessages2 ? state.messages : [];
1697
2486
  const lastMessage = visibleMessages.at(-1);
1698
2487
  const completedAnswer = showActivity && state.phase === "complete" && lastMessage?.role === "agent" ? lastMessage : null;
1699
2488
  const transcriptMessages = completedAnswer ? visibleMessages.slice(0, -1) : visibleMessages;
@@ -1703,8 +2492,14 @@ function AgentRail({
1703
2492
  role: "agent",
1704
2493
  streaming: true,
1705
2494
  text: state.streamingText
2495
+ } : state.pendingOffer ? {
2496
+ createdAt: 0,
2497
+ id: "pending-booking",
2498
+ role: "agent",
2499
+ streaming: false,
2500
+ text: "Pick a date and time that works for you."
1706
2501
  } : null;
1707
- (0, import_react5.useEffect)(() => {
2502
+ (0, import_react9.useEffect)(() => {
1708
2503
  const node = transcriptRef.current;
1709
2504
  if (!node) return;
1710
2505
  node.scrollTop = node.scrollHeight;
@@ -1715,10 +2510,11 @@ function AgentRail({
1715
2510
  state.followUps,
1716
2511
  state.journey
1717
2512
  ]);
1718
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2513
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1719
2514
  "aside",
1720
2515
  {
1721
2516
  className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
2517
+ "data-color-scheme": resolvedColorScheme,
1722
2518
  style: railStyle,
1723
2519
  "aria-label": "Agent conversation",
1724
2520
  "aria-modal": mobileFullscreen || expanded ? true : void 0,
@@ -1726,45 +2522,42 @@ function AgentRail({
1726
2522
  role: mobileFullscreen || expanded ? "dialog" : void 0,
1727
2523
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
1728
2524
  children: [
1729
- /* @__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: [
1730
- onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2525
+ /* @__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: [
2526
+ onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1731
2527
  "button",
1732
2528
  {
1733
2529
  type: "button",
1734
2530
  className: "agent-rail__collapse",
1735
2531
  "aria-label": "Collapse assist",
1736
2532
  onClick: onCollapse,
1737
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MinimizeIcon, {})
2533
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MinimizeIcon, {})
1738
2534
  }
1739
- ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2535
+ ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1740
2536
  "button",
1741
2537
  {
1742
2538
  type: "button",
1743
2539
  className: "agent-rail__close",
1744
2540
  "aria-label": "Close agent",
1745
2541
  onClick: onClose,
1746
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {})
2542
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, {})
1747
2543
  }
1748
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
1749
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "agent-rail__identity", children: [
1750
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: [
1751
- brandLabel.slice(0, 1).toUpperCase(),
1752
- brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1753
- "img",
1754
- {
1755
- className: "agent-rail__brand-logo",
1756
- src: brandLogoUrl,
1757
- alt: "",
1758
- onError: (event) => {
1759
- event.currentTarget.hidden = true;
1760
- }
2544
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
2545
+ resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__identity", children: [
2546
+ showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2547
+ "img",
2548
+ {
2549
+ className: "agent-rail__brand-logo",
2550
+ src: resolvedBrandLogoUrl,
2551
+ alt: "",
2552
+ onError: () => {
2553
+ setFailedLogoUrl(resolvedBrandLogoUrl ?? null);
1761
2554
  }
1762
- ) : null
1763
- ] }),
1764
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "agent-rail__brand-label", children: brandLabel })
1765
- ] }),
1766
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "agent-rail__actions", children: [
1767
- onReset ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2555
+ }
2556
+ ) }) : null,
2557
+ resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
2558
+ ] }) : null,
2559
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__actions", children: [
2560
+ onReset ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1768
2561
  "button",
1769
2562
  {
1770
2563
  type: "button",
@@ -1772,80 +2565,86 @@ function AgentRail({
1772
2565
  "aria-label": "Start a new conversation",
1773
2566
  disabled: !hasVisitorMessages2,
1774
2567
  onClick: onReset,
1775
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NewChatIcon, {})
2568
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NewChatIcon, {})
1776
2569
  }
1777
2570
  ) : null,
1778
- onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2571
+ onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1779
2572
  "button",
1780
2573
  {
1781
2574
  type: "button",
1782
2575
  className: "agent-rail__expand",
1783
- "aria-label": expanded ? "Exit focus view" : "Open focus view",
2576
+ "aria-label": expanded ? "Exit full screen" : "Open full screen",
1784
2577
  onClick: onExpandToggle,
1785
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ExpandIcon, {})
2578
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExpandIcon, {})
1786
2579
  }
1787
2580
  ) : null
1788
2581
  ] })
1789
2582
  ] }) }),
1790
- /* @__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: [
1791
- !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1792
- "section",
2583
+ /* @__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: [
2584
+ !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
2585
+ greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2586
+ MessageBubble,
2587
+ {
2588
+ message: greeting,
2589
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2590
+ onBook
2591
+ }
2592
+ ) : null,
2593
+ showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2594
+ FollowUpChips,
2595
+ {
2596
+ suggestions: state.followUps,
2597
+ disabled: isBusy,
2598
+ label: "Start here",
2599
+ onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
2600
+ }
2601
+ ) }) : null
2602
+ ] }) : null,
2603
+ transcriptMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2604
+ MessageBubble,
1793
2605
  {
1794
- className: "agent-rail__welcome",
1795
- "aria-labelledby": welcomeTitleId,
1796
- children: [
1797
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "agent-rail__welcome-mark", "aria-hidden": "true", children: [
1798
- brandLabel.slice(0, 1).toUpperCase(),
1799
- brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1800
- "img",
1801
- {
1802
- className: "agent-rail__welcome-logo",
1803
- src: brandLogoUrl,
1804
- alt: "",
1805
- onError: (event) => {
1806
- event.currentTarget.hidden = true;
1807
- }
1808
- }
1809
- ) : null
1810
- ] }),
1811
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "agent-rail__welcome-copy", children: [
1812
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { id: welcomeTitleId, children: "What can I help you find?" }),
1813
- greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: greeting.text }) : null
1814
- ] }),
1815
- showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1816
- FollowUpChips,
1817
- {
1818
- suggestions: state.followUps,
1819
- disabled: isBusy,
1820
- label: "Start here",
1821
- onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
1822
- }
1823
- ) }) : null
1824
- ]
1825
- }
1826
- ) : null,
1827
- transcriptMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MessageBubble, { message }, message.id)),
1828
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2606
+ message,
2607
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2608
+ onBook
2609
+ },
2610
+ message.id
2611
+ )),
2612
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1829
2613
  AgentActivityBubble,
1830
2614
  {
1831
- brandLabel,
1832
- brandLogoUrl,
2615
+ brandLabel: resolvedBrandLabel,
2616
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
1833
2617
  failed: state.phase === "error",
1834
2618
  steps: state.toolSteps
1835
2619
  }
1836
2620
  ) : null,
1837
- streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MessageBubble, { message: streamingMessage }) : null,
1838
- completedAnswer ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MessageBubble, { message: completedAnswer }) : null,
1839
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
1840
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
1841
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: "Something went wrong" }),
1842
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: state.error })
2621
+ completedAnswer ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2622
+ MessageBubble,
2623
+ {
2624
+ message: completedAnswer,
2625
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2626
+ onBook
2627
+ }
2628
+ ) : null,
2629
+ streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2630
+ MessageBubble,
2631
+ {
2632
+ message: streamingMessage,
2633
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2634
+ offer: state.pendingOffer,
2635
+ onBook
2636
+ }
2637
+ ) : null,
2638
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
2639
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2640
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Something went wrong" }),
2641
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: state.error })
1843
2642
  ] }),
1844
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
2643
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
1845
2644
  ] }) : null
1846
2645
  ] }) }),
1847
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
1848
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2646
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
2647
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1849
2648
  Composer,
1850
2649
  {
1851
2650
  variant: expanded || mobileFullscreen ? "dock" : "default",
@@ -1854,10 +2653,9 @@ function AgentRail({
1854
2653
  onSubmit
1855
2654
  }
1856
2655
  ),
1857
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { children: [
1858
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "AI can make mistakes." }),
1859
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-hidden": "true", children: " \xB7 " }),
1860
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: poweredByLabel })
2656
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
2657
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "AI can make mistakes. Check important info." }),
2658
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: poweredByLabel })
1861
2659
  ] }) })
1862
2660
  ] })
1863
2661
  ]
@@ -1866,9 +2664,9 @@ function AgentRail({
1866
2664
  }
1867
2665
 
1868
2666
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
1869
- var import_jsx_runtime6 = require("react/jsx-runtime");
2667
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1870
2668
  function SparklesIcon() {
1871
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2669
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1872
2670
  "svg",
1873
2671
  {
1874
2672
  className: "assist-edge-tab__sparkles",
@@ -1876,21 +2674,21 @@ function SparklesIcon() {
1876
2674
  fill: "none",
1877
2675
  "aria-hidden": "true",
1878
2676
  children: [
1879
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2677
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1880
2678
  "path",
1881
2679
  {
1882
2680
  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",
1883
2681
  fill: "currentColor"
1884
2682
  }
1885
2683
  ),
1886
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2684
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1887
2685
  "path",
1888
2686
  {
1889
2687
  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",
1890
2688
  fill: "currentColor"
1891
2689
  }
1892
2690
  ),
1893
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2691
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1894
2692
  "path",
1895
2693
  {
1896
2694
  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",
@@ -1901,8 +2699,23 @@ function SparklesIcon() {
1901
2699
  }
1902
2700
  );
1903
2701
  }
2702
+ function TabMarkIcon({ customIconUrl }) {
2703
+ const url = customIconUrl?.trim();
2704
+ if (url) {
2705
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2706
+ "img",
2707
+ {
2708
+ alt: "",
2709
+ "aria-hidden": true,
2710
+ className: "assist-edge-tab__custom-icon",
2711
+ src: url
2712
+ }
2713
+ );
2714
+ }
2715
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparklesIcon, {});
2716
+ }
1904
2717
  function ChevronLeftIcon() {
1905
- 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)(
2718
+ 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)(
1906
2719
  "path",
1907
2720
  {
1908
2721
  d: "M10 4L6 8l4 4",
@@ -1914,7 +2727,7 @@ function ChevronLeftIcon() {
1914
2727
  ) });
1915
2728
  }
1916
2729
  function ChevronDownIcon() {
1917
- 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)(
2730
+ 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)(
1918
2731
  "path",
1919
2732
  {
1920
2733
  d: "M4 6l4 4 4-4",
@@ -1926,12 +2739,12 @@ function ChevronDownIcon() {
1926
2739
  ) });
1927
2740
  }
1928
2741
  function DragDots() {
1929
- 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)) });
2742
+ 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)) });
1930
2743
  }
1931
2744
  var VARIANT_COPY = {
1932
- outline: { label: "Assist", aria: "Open Assist" },
2745
+ outline: { label: "Ask anything", aria: "Ask anything" },
1933
2746
  ask: { label: "Ask anything", aria: "Ask anything" },
1934
- fill: { label: "Assist", aria: "Open Assist" }
2747
+ fill: { label: "Ask anything", aria: "Ask anything" }
1935
2748
  };
1936
2749
  function AssistEdgeTab({
1937
2750
  variant,
@@ -1940,6 +2753,7 @@ function AssistEdgeTab({
1940
2753
  inset,
1941
2754
  visible,
1942
2755
  label,
2756
+ customIconUrl,
1943
2757
  logoUrl,
1944
2758
  brandColor,
1945
2759
  brandForeground,
@@ -1948,40 +2762,49 @@ function AssistEdgeTab({
1948
2762
  mobile = false,
1949
2763
  surfaceColor,
1950
2764
  textColor,
2765
+ colorScheme = "auto",
1951
2766
  onOpen
1952
2767
  }) {
2768
+ const resolvedColorScheme = useAgentColorScheme(colorScheme);
1953
2769
  const copy = VARIANT_COPY[variant];
1954
2770
  const visibleLabel = label?.trim() || copy.label;
2771
+ const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
2772
+ const resolvedBrandColor = brandColor ?? (resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
2773
+ const resolvedBorderColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.border : borderColor;
2774
+ const resolvedSurfaceColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.surface : surfaceColor;
2775
+ const resolvedTextColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.text : textColor;
1955
2776
  const style = {
1956
2777
  "--tab-along": `${along}%`,
1957
2778
  "--tab-inset": `${inset}px`,
1958
- ...brandColor ? { "--as-brand": brandColor } : {},
2779
+ ...resolvedBrandColor ? { "--as-brand": resolvedBrandColor } : {},
1959
2780
  ...brandForeground ? { "--as-visitor-text": brandForeground } : {},
1960
- ...borderColor ? { "--as-border": borderColor } : {},
2781
+ ...resolvedBorderColor ? { "--as-border": resolvedBorderColor } : {},
1961
2782
  ...fontFamily ? { "--as-font-display": fontFamily } : {},
1962
- ...surfaceColor ? { "--as-surface": surfaceColor } : {},
1963
- ...textColor ? { "--as-text": textColor } : {}
2783
+ ...resolvedSurfaceColor ? { "--as-surface": resolvedSurfaceColor } : {},
2784
+ ...resolvedTextColor ? { "--as-text": resolvedTextColor } : {},
2785
+ colorScheme: resolvedColorScheme
1964
2786
  };
1965
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2787
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1966
2788
  "button",
1967
2789
  {
1968
2790
  type: "button",
1969
2791
  className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
2792
+ "data-color-scheme": resolvedColorScheme,
1970
2793
  style,
1971
2794
  "aria-label": `Open ${visibleLabel}`,
1972
2795
  "aria-hidden": !visible,
1973
2796
  tabIndex: visible ? 0 : -1,
1974
2797
  onClick: onOpen,
1975
2798
  children: [
1976
- mobile ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1977
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2799
+ mobile ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2800
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1978
2801
  "span",
1979
2802
  {
1980
2803
  className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
1981
2804
  "aria-hidden": "true",
1982
2805
  children: [
1983
- visibleLabel.slice(0, 1).toUpperCase(),
1984
- logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2806
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2807
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1985
2808
  "img",
1986
2809
  {
1987
2810
  className: "assist-edge-tab__logo",
@@ -1995,14 +2818,11 @@ function AssistEdgeTab({
1995
2818
  ]
1996
2819
  }
1997
2820
  ),
1998
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "assist-edge-tab__label", children: [
1999
- "Ask ",
2000
- visibleLabel
2001
- ] })
2002
- ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2003
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2004
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SparklesIcon, {}),
2005
- logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2821
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel })
2822
+ ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2823
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2824
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2825
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2006
2826
  "img",
2007
2827
  {
2008
2828
  className: "assist-edge-tab__logo",
@@ -2014,18 +2834,18 @@ function AssistEdgeTab({
2014
2834
  }
2015
2835
  ) : null
2016
2836
  ] }),
2017
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2018
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronDownIcon, {})
2837
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2838
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {})
2019
2839
  ] }) : null,
2020
- variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2021
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronLeftIcon, {}),
2022
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2023
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DragDots, {})
2840
+ variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2841
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {}),
2842
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2843
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DragDots, {})
2024
2844
  ] }) : null,
2025
- variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2026
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2027
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SparklesIcon, {}),
2028
- logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2845
+ variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2846
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2847
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2848
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2029
2849
  "img",
2030
2850
  {
2031
2851
  className: "assist-edge-tab__logo",
@@ -2037,8 +2857,8 @@ function AssistEdgeTab({
2037
2857
  }
2038
2858
  ) : null
2039
2859
  ] }),
2040
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2041
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronLeftIcon, {})
2860
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2861
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {})
2042
2862
  ] }) : null
2043
2863
  ]
2044
2864
  }
@@ -2046,7 +2866,7 @@ function AssistEdgeTab({
2046
2866
  }
2047
2867
 
2048
2868
  // src/react/components/AgentWidget/AgentWidget.tsx
2049
- var import_jsx_runtime7 = require("react/jsx-runtime");
2869
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2050
2870
  function AgentWidget({
2051
2871
  indexId,
2052
2872
  customerId,
@@ -2062,9 +2882,9 @@ function AgentWidget({
2062
2882
  }) {
2063
2883
  const isMobile = useIsMobile();
2064
2884
  const placement = normalizeAgentPlacement(placementInput);
2065
- const railSlotRef = (0, import_react6.useRef)(null);
2066
- const [railCollapsed, setRailCollapsed] = (0, import_react6.useState)(defaultCollapsed);
2067
- const [railExpanded, setRailExpanded] = (0, import_react6.useState)(false);
2885
+ const railSlotRef = (0, import_react10.useRef)(null);
2886
+ const [railCollapsed, setRailCollapsed] = (0, import_react10.useState)(defaultCollapsed);
2887
+ const [railExpanded, setRailExpanded] = (0, import_react10.useState)(false);
2068
2888
  const pageShiftActive = shouldApplyPageShift({
2069
2889
  pageShift,
2070
2890
  isMobile,
@@ -2084,7 +2904,8 @@ function AgentWidget({
2084
2904
  runtimeOrigin,
2085
2905
  greeting: branding?.greeting
2086
2906
  });
2087
- const agentName = branding?.agentName ?? "Webless Guide";
2907
+ const agentName = branding?.agentName ?? "";
2908
+ const tabLabel = branding?.tabLabel ?? agentName;
2088
2909
  const theme = {
2089
2910
  ...branding?.fontFamily ? { fontBody: branding.fontFamily, fontDisplay: branding.fontFamily } : {},
2090
2911
  ...branding?.colors?.primary ? {
@@ -2102,8 +2923,7 @@ function AgentWidget({
2102
2923
  } : {},
2103
2924
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
2104
2925
  };
2105
- const idle = state.phase === "idle" && !hasVisitorMessages(state.messages);
2106
- (0, import_react6.useEffect)(() => {
2926
+ (0, import_react10.useEffect)(() => {
2107
2927
  if (!registerPanelController) return;
2108
2928
  registerAgentPanelController(customerId, {
2109
2929
  open: () => setRailCollapsed(false),
@@ -2118,7 +2938,7 @@ function AgentWidget({
2118
2938
  if (isMobile) setRailCollapsed(false);
2119
2939
  await submit(message);
2120
2940
  }
2121
- (0, import_react6.useEffect)(() => {
2941
+ (0, import_react10.useEffect)(() => {
2122
2942
  if (railCollapsed) return;
2123
2943
  const handleKeyDown = (event) => {
2124
2944
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -2152,64 +2972,44 @@ function AgentWidget({
2152
2972
  window.addEventListener("keydown", handleKeyDown);
2153
2973
  return () => window.removeEventListener("keydown", handleKeyDown);
2154
2974
  }, [isMobile, railCollapsed, railExpanded]);
2155
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "webless-agent-root", children: [
2156
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2975
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "webless-agent-root", children: [
2976
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2157
2977
  "div",
2158
2978
  {
2159
2979
  className: `webless-agent-root__shell${railCollapsed ? " webless-agent-root__shell--collapsed" : ""}${railExpanded ? " webless-agent-root__shell--expanded" : ""}`,
2160
- children: [
2161
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2162
- "div",
2163
- {
2164
- ref: railSlotRef,
2165
- className: "webless-agent-root__rail-slot",
2166
- inert: railCollapsed || void 0,
2167
- "aria-hidden": railCollapsed,
2168
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2169
- AgentRail,
2170
- {
2171
- theme,
2172
- brandLabel: agentName,
2173
- brandLogoUrl: branding?.logoUrl,
2174
- composerPlaceholder: branding?.composerPlaceholder ?? `Ask ${agentName}\u2026`,
2175
- poweredByLabel: branding?.poweredByLabel ?? "Powered by Webless",
2176
- state: idle ? {
2177
- ...state,
2178
- followUps: createIdleSuggestions()
2179
- } : state,
2180
- mobileFullscreen: isMobile && !railCollapsed,
2181
- expanded: railExpanded,
2182
- onCollapse: !isMobile ? () => setRailCollapsed(true) : void 0,
2183
- onClose: isMobile ? () => setRailCollapsed(true) : void 0,
2184
- onExpandToggle: !isMobile ? () => setRailExpanded((current) => !current) : void 0,
2185
- onSubmit: handleSubmit,
2186
- onReset: reset,
2187
- onRetry: () => void retry(),
2188
- onFollowUpSelect: (label) => void handleSubmit(label)
2189
- }
2190
- )
2191
- }
2192
- ),
2193
- !railCollapsed && isMobile || !isMobile && railExpanded && !railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2194
- "button",
2195
- {
2196
- type: "button",
2197
- className: `webless-agent-root__backdrop${isMobile ? " webless-agent-root__backdrop--mobile" : ""}`,
2198
- tabIndex: -1,
2199
- "aria-label": isMobile ? "Close agent" : "Exit focus view",
2200
- onClick: () => {
2201
- if (isMobile) {
2202
- setRailCollapsed(true);
2203
- } else {
2204
- setRailExpanded(false);
2205
- }
2980
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2981
+ "div",
2982
+ {
2983
+ ref: railSlotRef,
2984
+ className: "webless-agent-root__rail-slot",
2985
+ inert: railCollapsed || void 0,
2986
+ "aria-hidden": railCollapsed,
2987
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2988
+ AgentRail,
2989
+ {
2990
+ theme,
2991
+ brandLabel: agentName,
2992
+ brandLogoUrl: branding?.logoUrl,
2993
+ composerPlaceholder: branding?.composerPlaceholder ?? "Ask a question\u2026",
2994
+ poweredByLabel: branding?.poweredByLabel ?? "Powered by Webless",
2995
+ state,
2996
+ mobileFullscreen: isMobile && !railCollapsed,
2997
+ expanded: railExpanded,
2998
+ onCollapse: !isMobile ? () => setRailCollapsed(true) : void 0,
2999
+ onClose: isMobile ? () => setRailCollapsed(true) : void 0,
3000
+ onExpandToggle: !isMobile ? () => setRailExpanded((current) => !current) : void 0,
3001
+ onSubmit: handleSubmit,
3002
+ onReset: reset,
3003
+ onRetry: () => void retry(),
3004
+ onFollowUpSelect: (label) => void handleSubmit(label),
3005
+ onBook: (input) => void submit(input.displayText, { runtimeText: input.runtimeText })
2206
3006
  }
2207
- }
2208
- ) : null
2209
- ]
3007
+ )
3008
+ }
3009
+ )
2210
3010
  }
2211
3011
  ),
2212
- railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3012
+ railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2213
3013
  AssistEdgeTab,
2214
3014
  {
2215
3015
  variant: placement.variant,
@@ -2217,7 +3017,8 @@ function AgentWidget({
2217
3017
  along: placement.along,
2218
3018
  inset: placement.inset,
2219
3019
  visible: true,
2220
- label: agentName,
3020
+ label: tabLabel,
3021
+ customIconUrl: branding?.tabIconUrl,
2221
3022
  logoUrl: branding?.logoUrl,
2222
3023
  brandColor: branding?.colors?.primary,
2223
3024
  brandForeground: branding?.colors?.primaryForeground,
@@ -2246,11 +3047,12 @@ function readUnpublishedPreviewBuildId(href) {
2246
3047
  }
2247
3048
 
2248
3049
  // src/embed/AgentWidget.tsx
2249
- var import_jsx_runtime8 = require("react/jsx-runtime");
3050
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2250
3051
  function AgentWidget2({
2251
3052
  manifest
2252
3053
  }) {
2253
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3054
+ const defaultCollapsed = manifest.version !== "unpublished";
3055
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2254
3056
  AgentWidget,
2255
3057
  {
2256
3058
  indexId: manifest.indexId,
@@ -2261,7 +3063,7 @@ function AgentWidget2({
2261
3063
  placement: manifest.placement,
2262
3064
  pageShift: manifest.pageShift,
2263
3065
  branding: manifest.branding,
2264
- defaultCollapsed: true,
3066
+ defaultCollapsed,
2265
3067
  registerPanelController: true
2266
3068
  }
2267
3069
  );
@@ -2315,6 +3117,8 @@ function normalizeAgentBranding(branding) {
2315
3117
  ) : void 0;
2316
3118
  const normalized = {
2317
3119
  agentName: normalizeOptionalValue(branding.agentName),
3120
+ tabLabel: normalizeOptionalValue(branding.tabLabel),
3121
+ tabIconUrl: normalizeOptionalValue(branding.tabIconUrl),
2318
3122
  logoUrl: normalizeOptionalValue(branding.logoUrl),
2319
3123
  greeting: normalizeOptionalValue(branding.greeting),
2320
3124
  composerPlaceholder: normalizeOptionalValue(branding.composerPlaceholder),
@@ -2326,7 +3130,7 @@ function normalizeAgentBranding(branding) {
2326
3130
  }
2327
3131
 
2328
3132
  // src/embed/mount.tsx
2329
- var import_jsx_runtime9 = require("react/jsx-runtime");
3133
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2330
3134
  var mountedHandles = /* @__PURE__ */ new Map();
2331
3135
  var latestCustomerId = null;
2332
3136
  function resolveMountHost(manifest, script) {
@@ -2356,7 +3160,7 @@ function mountAgent(input) {
2356
3160
  const host = createHost(manifest.customerId);
2357
3161
  mountTarget.append(host);
2358
3162
  const root = (0, import_client5.createRoot)(host);
2359
- root.render(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentWidget2, { manifest }));
3163
+ root.render(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AgentWidget2, { manifest }));
2360
3164
  const handle = {
2361
3165
  customerId: manifest.customerId,
2362
3166
  manifest,