@webless/agent 0.11.0 → 0.12.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
@@ -34,7 +34,7 @@ __export(embed_exports, {
34
34
  module.exports = __toCommonJS(embed_exports);
35
35
 
36
36
  // src/embed/mount.tsx
37
- var import_client5 = require("react-dom/client");
37
+ var import_client7 = require("react-dom/client");
38
38
 
39
39
  // src/react/panel-controller.ts
40
40
  var controllers = /* @__PURE__ */ new Map();
@@ -64,7 +64,7 @@ function submitAgentPanel(customerId, message, options) {
64
64
  }
65
65
 
66
66
  // src/react/components/AgentWidget/AgentWidget.tsx
67
- var import_react17 = require("react");
67
+ var import_react18 = require("react");
68
68
 
69
69
  // src/react/page-shift.ts
70
70
  var import_react = require("react");
@@ -146,8 +146,168 @@ function usePageShift(input) {
146
146
  }, [active, railSlotRef]);
147
147
  }
148
148
 
149
- // src/react/hooks/useAgentChat.ts
149
+ // src/react/hooks/useAgentHandoff.ts
150
+ var import_client = require("eve/client");
150
151
  var import_react2 = require("react");
152
+ function useAgentHandoff(options) {
153
+ const [page, setPage] = (0, import_react2.useState)(null);
154
+ const [busy, setBusy] = (0, import_react2.useState)(false);
155
+ const [rejected, setRejected] = (0, import_react2.useState)(false);
156
+ const [error, setError] = (0, import_react2.useState)(null);
157
+ const callbacks = (0, import_react2.useRef)(options);
158
+ callbacks.current = options;
159
+ const pending = (0, import_react2.useRef)(null);
160
+ const inFlight = (0, import_react2.useRef)(null);
161
+ const pageRef = (0, import_react2.useRef)(null);
162
+ const refreshRef = (0, import_react2.useRef)(null);
163
+ const generation = (0, import_react2.useRef)(0);
164
+ (0, import_react2.useEffect)(() => {
165
+ const token = ++generation.current;
166
+ const controller = new AbortController();
167
+ let timer;
168
+ let reading = null;
169
+ let cursor = 0;
170
+ pageRef.current = null;
171
+ pending.current = null;
172
+ inFlight.current?.abort();
173
+ inFlight.current = null;
174
+ setRejected(false);
175
+ setPage(null);
176
+ setError(null);
177
+ setBusy(false);
178
+ if (!options.enabled || !options.sessionId) return;
179
+ const active = () => !controller.signal.aborted && generation.current === token;
180
+ const refresh = () => {
181
+ reading ??= (async () => {
182
+ do {
183
+ const next = await options.client.handoff.read({
184
+ after: cursor,
185
+ signal: AbortSignal.any([
186
+ controller.signal,
187
+ AbortSignal.timeout(15e3)
188
+ ])
189
+ });
190
+ if (!active()) return;
191
+ callbacks.current.onEvents(next.events);
192
+ if (next.status !== "ai") callbacks.current.onOwnership();
193
+ cursor = next.cursor;
194
+ pageRef.current = next;
195
+ setPage(next);
196
+ setRejected(false);
197
+ if (!pending.current) setError(null);
198
+ if (!next.hasMore) break;
199
+ } while (active());
200
+ })().finally(() => {
201
+ reading = null;
202
+ });
203
+ return reading;
204
+ };
205
+ refreshRef.current = refresh;
206
+ const poll = async () => {
207
+ try {
208
+ await refresh();
209
+ } catch (cause) {
210
+ if (active()) {
211
+ const denied = cause instanceof import_client.ClientError && cause.status === 403;
212
+ setRejected(denied);
213
+ setError(
214
+ denied ? "This preview has changed. Restart the preview chat to test the current version." : "Reconnecting to your conversation\u2026"
215
+ );
216
+ }
217
+ } finally {
218
+ if (active()) timer = setTimeout(() => void poll(), 1500);
219
+ }
220
+ };
221
+ void poll();
222
+ return () => {
223
+ controller.abort();
224
+ inFlight.current?.abort();
225
+ clearTimeout(timer);
226
+ refreshRef.current = null;
227
+ };
228
+ }, [options.client, options.enabled, options.sessionId]);
229
+ async function execute(operation) {
230
+ if (inFlight.current) return;
231
+ const controller = new AbortController();
232
+ inFlight.current = controller;
233
+ const signal = AbortSignal.any([
234
+ controller.signal,
235
+ AbortSignal.timeout(15e3)
236
+ ]);
237
+ const token = generation.current;
238
+ pending.current = operation;
239
+ setBusy(true);
240
+ setError(null);
241
+ try {
242
+ if (operation.type === "start")
243
+ await options.client.handoff.start(operation.operationId, signal);
244
+ else if (operation.type === "message") {
245
+ const { type: _, ...command } = operation;
246
+ await options.client.handoff.send(command, signal);
247
+ } else {
248
+ const { type: _, ...command } = operation;
249
+ await options.client.handoff.returnToAI(command, signal);
250
+ }
251
+ if (token !== generation.current) return;
252
+ pending.current = null;
253
+ await refreshRef.current?.();
254
+ } catch {
255
+ if (token === generation.current)
256
+ setError(
257
+ "Could not confirm delivery. Retry to check the same request."
258
+ );
259
+ } finally {
260
+ if (inFlight.current === controller) inFlight.current = null;
261
+ if (token === generation.current) setBusy(false);
262
+ }
263
+ }
264
+ const binding = () => {
265
+ const current = pageRef.current;
266
+ if (!current?.handoffId) throw new Error("No active human conversation.");
267
+ return { handoffId: current.handoffId, epoch: current.epoch };
268
+ };
269
+ return {
270
+ page,
271
+ busy,
272
+ error,
273
+ hasPending: Boolean(pending.current),
274
+ canReset: !busy && !pending.current && (rejected || page?.status === "ai" || page?.status === "failed" || !options.sessionId),
275
+ blocksAI: Boolean(options.enabled && options.sessionId && !page) || busy || Boolean(pending.current) || Boolean(page && page.status !== "ai"),
276
+ start: () => execute(
277
+ pending.current ?? { type: "start", operationId: crypto.randomUUID() }
278
+ ),
279
+ send: (message) => {
280
+ if (pending.current)
281
+ throw new Error(
282
+ "Retry the pending request before sending another message."
283
+ );
284
+ return execute({
285
+ type: "message",
286
+ operationId: crypto.randomUUID(),
287
+ ...binding(),
288
+ message
289
+ });
290
+ },
291
+ returnToAI: () => execute(
292
+ pending.current ?? {
293
+ type: "return",
294
+ operationId: crypto.randomUUID(),
295
+ ...binding()
296
+ }
297
+ ),
298
+ retry: async () => {
299
+ if (pending.current) return execute(pending.current);
300
+ try {
301
+ await refreshRef.current?.();
302
+ } catch {
303
+ setError("Reconnecting to your conversation\u2026");
304
+ }
305
+ }
306
+ };
307
+ }
308
+
309
+ // src/react/hooks/useAgentChat.ts
310
+ var import_react3 = require("react");
151
311
 
152
312
  // src/runtime/tool-ui.ts
153
313
  var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
@@ -289,16 +449,16 @@ function parseStep(value) {
289
449
  if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
290
450
  return null;
291
451
  }
292
- const id = boundedString(value.id, 80);
452
+ const id2 = boundedString(value.id, 80);
293
453
  const label = boundedString(value.label, 160);
294
454
  const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
295
- if (!id || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(id) || !label || value.description !== void 0 && !description || !Array.isArray(value.fieldPaths) || value.fieldPaths.length < 1 || value.fieldPaths.length > 32 || value.fieldPaths.some(
455
+ if (!id2 || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(id2) || !label || value.description !== void 0 && !description || !Array.isArray(value.fieldPaths) || value.fieldPaths.length < 1 || value.fieldPaths.length > 32 || value.fieldPaths.some(
296
456
  (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
297
457
  )) {
298
458
  return null;
299
459
  }
300
460
  return {
301
- id,
461
+ id: id2,
302
462
  label,
303
463
  fieldPaths: value.fieldPaths,
304
464
  ...description ? { description } : {}
@@ -335,14 +495,14 @@ function parseAgentToolUiSurface(value) {
335
495
  ])) {
336
496
  return null;
337
497
  }
338
- const id = boundedString(value.id, 200);
498
+ const id2 = boundedString(value.id, 200);
339
499
  const title = boundedString(value.title, 200);
340
500
  const toolSlug = boundedString(value.toolSlug, 200);
341
501
  const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
342
502
  const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
343
503
  const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
344
504
  const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
345
- if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
505
+ if (!id2 || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
346
506
  return null;
347
507
  }
348
508
  const fields = value.fields.map(parseField);
@@ -362,7 +522,7 @@ function parseAgentToolUiSurface(value) {
362
522
  }
363
523
  return {
364
524
  schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
365
- id,
525
+ id: id2,
366
526
  title,
367
527
  toolSlug,
368
528
  fields,
@@ -473,10 +633,205 @@ function completeConnectedToolWork(item, result) {
473
633
  }
474
634
 
475
635
  // src/runtime/client.ts
476
- var import_client2 = require("eve/client");
636
+ var import_client4 = require("eve/client");
637
+
638
+ // src/runtime/handoff.ts
639
+ var import_client3 = require("eve/client");
640
+
641
+ // src/runtime/generated/handoff-contract.ts
642
+ var import_zod = require("zod");
643
+ var id = import_zod.z.string().min(1).max(200);
644
+ var sequence = import_zod.z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
645
+ var text = import_zod.z.string().trim().min(1).max(16e3);
646
+ var agentRuntimeHandoffVersion = "webless.ai/agent-runtime-handoff/v1";
647
+ var agentRuntimeHandoffStatusSchema = import_zod.z.enum([
648
+ "ai",
649
+ "requesting",
650
+ "queued",
651
+ "human",
652
+ "resolved",
653
+ "failed"
654
+ ]);
655
+ var agentRuntimeHandoffActorSchema = import_zod.z.strictObject({
656
+ id,
657
+ name: import_zod.z.string().trim().min(1).max(200)
658
+ });
659
+ var eventBase = {
660
+ id,
661
+ sequence: sequence.refine((value) => value > 0),
662
+ handoffId: id,
663
+ epoch: sequence.refine((value) => value > 0),
664
+ createdAt: import_zod.z.iso.datetime()
665
+ };
666
+ var agentRuntimeHandoffEventSchema = import_zod.z.discriminatedUnion("type", [
667
+ import_zod.z.strictObject({
668
+ ...eventBase,
669
+ type: import_zod.z.literal("status"),
670
+ status: agentRuntimeHandoffStatusSchema,
671
+ actor: agentRuntimeHandoffActorSchema.nullable()
672
+ }),
673
+ import_zod.z.strictObject({
674
+ ...eventBase,
675
+ type: import_zod.z.literal("visitor.message"),
676
+ message: text,
677
+ operationId: id
678
+ }),
679
+ import_zod.z.strictObject({
680
+ ...eventBase,
681
+ type: import_zod.z.literal("human.message"),
682
+ message: text,
683
+ actor: agentRuntimeHandoffActorSchema
684
+ })
685
+ ]);
686
+ var agentRuntimeHandoffReadRequestSchema = import_zod.z.strictObject({
687
+ after: sequence.default(0)
688
+ });
689
+ var agentRuntimeHandoffPageSchema = import_zod.z.strictObject({
690
+ apiVersion: import_zod.z.literal(agentRuntimeHandoffVersion),
691
+ sessionId: id,
692
+ enabled: import_zod.z.boolean(),
693
+ status: agentRuntimeHandoffStatusSchema,
694
+ handoffId: id.nullable(),
695
+ epoch: sequence,
696
+ after: sequence,
697
+ cursor: sequence,
698
+ hasMore: import_zod.z.boolean(),
699
+ events: import_zod.z.array(agentRuntimeHandoffEventSchema).max(100)
700
+ }).superRefine((page, ctx) => {
701
+ let cursor = page.after;
702
+ let previousEpoch = 0;
703
+ const statuses = /* @__PURE__ */ new Map();
704
+ const ids = /* @__PURE__ */ new Set();
705
+ const handoffs = /* @__PURE__ */ new Map();
706
+ const terminalEpochs = /* @__PURE__ */ new Set();
707
+ let currentStatus;
708
+ for (const event of page.events) {
709
+ if (event.epoch < previousEpoch)
710
+ ctx.addIssue({
711
+ code: "custom",
712
+ message: "Handoff epochs cannot decrease."
713
+ });
714
+ previousEpoch = event.epoch;
715
+ if (ids.has(event.id))
716
+ ctx.addIssue({
717
+ code: "custom",
718
+ message: "Duplicate handoff event ID."
719
+ });
720
+ ids.add(event.id);
721
+ const handoffId = handoffs.get(event.epoch);
722
+ if (handoffId !== void 0 && handoffId !== event.handoffId || event.epoch === page.epoch && event.handoffId !== page.handoffId)
723
+ ctx.addIssue({
724
+ code: "custom",
725
+ message: "Inconsistent handoff epoch identity."
726
+ });
727
+ handoffs.set(event.epoch, event.handoffId);
728
+ if (terminalEpochs.has(event.epoch) && (event.type !== "status" || event.status !== "ai"))
729
+ ctx.addIssue({
730
+ code: "custom",
731
+ message: "Handoff event follows resolution."
732
+ });
733
+ if (event.type === "status") {
734
+ if (event.status === "human" && event.actor === null)
735
+ ctx.addIssue({
736
+ code: "custom",
737
+ message: "Human ownership requires a representative."
738
+ });
739
+ const previous = statuses.get(event.epoch);
740
+ const transitions = {
741
+ ai: [],
742
+ requesting: ["queued", "resolved", "failed"],
743
+ queued: ["human", "resolved", "failed"],
744
+ human: ["resolved", "failed"],
745
+ resolved: ["ai"],
746
+ failed: []
747
+ };
748
+ if (previous !== void 0 && !transitions[previous].includes(event.status))
749
+ ctx.addIssue({
750
+ code: "custom",
751
+ message: "Invalid handoff ownership transition."
752
+ });
753
+ statuses.set(event.epoch, event.status);
754
+ if (["resolved", "failed", "ai"].includes(event.status))
755
+ terminalEpochs.add(event.epoch);
756
+ if (event.epoch === page.epoch) currentStatus = event.status;
757
+ }
758
+ if (event.sequence !== cursor + 1) {
759
+ ctx.addIssue({
760
+ code: "custom",
761
+ message: "Handoff event gap or replay."
762
+ });
763
+ }
764
+ cursor = event.sequence;
765
+ if (event.epoch > page.epoch) {
766
+ ctx.addIssue({
767
+ code: "custom",
768
+ message: "Event exceeds current epoch."
769
+ });
770
+ }
771
+ }
772
+ if (!page.hasMore && currentStatus !== void 0 && currentStatus !== page.status)
773
+ ctx.addIssue({
774
+ code: "custom",
775
+ message: "Handoff ownership contradicts its final status event."
776
+ });
777
+ if (page.cursor !== cursor || page.hasMore && page.events.length === 0) {
778
+ ctx.addIssue({ code: "custom", message: "Invalid handoff page cursor." });
779
+ }
780
+ if (page.epoch === 0 !== (page.handoffId === null)) {
781
+ ctx.addIssue({ code: "custom", message: "Invalid handoff identity." });
782
+ }
783
+ if (page.handoffId === null && page.status !== "ai") {
784
+ ctx.addIssue({
785
+ code: "custom",
786
+ message: "Handoff identity is required."
787
+ });
788
+ }
789
+ });
790
+ var agentRuntimeHandoffStartSchema = import_zod.z.strictObject({
791
+ operationId: id
792
+ });
793
+ var agentRuntimeHandoffCommandSchema = import_zod.z.strictObject({
794
+ operationId: id,
795
+ handoffId: id,
796
+ epoch: sequence.refine((value) => value > 0)
797
+ });
798
+ var agentRuntimeHandoffMessageSchema = agentRuntimeHandoffCommandSchema.extend({ message: text });
799
+ var agentRuntimeHandoffBindingSchema = import_zod.z.strictObject({
800
+ tenantId: id,
801
+ indexId: id,
802
+ visitorSubject: id,
803
+ sessionId: id,
804
+ handoffId: id,
805
+ epoch: sequence.refine((value) => value > 0)
806
+ });
807
+ var providerEventBase = {
808
+ apiVersion: import_zod.z.literal(agentRuntimeHandoffVersion),
809
+ binding: agentRuntimeHandoffBindingSchema,
810
+ eventId: id,
811
+ sequence: sequence.refine((value) => value > 0)
812
+ };
813
+ var agentRuntimeHandoffProviderEventSchema = import_zod.z.discriminatedUnion(
814
+ "type",
815
+ [
816
+ import_zod.z.strictObject({ ...providerEventBase, type: import_zod.z.literal("queued") }),
817
+ import_zod.z.strictObject({
818
+ ...providerEventBase,
819
+ type: import_zod.z.literal("assigned"),
820
+ actor: agentRuntimeHandoffActorSchema
821
+ }),
822
+ import_zod.z.strictObject({
823
+ ...providerEventBase,
824
+ type: import_zod.z.literal("human.message"),
825
+ actor: agentRuntimeHandoffActorSchema,
826
+ message: text
827
+ }),
828
+ import_zod.z.strictObject({ ...providerEventBase, type: import_zod.z.literal("resolved") }),
829
+ import_zod.z.strictObject({ ...providerEventBase, type: import_zod.z.literal("failed") })
830
+ ]
831
+ );
477
832
 
478
833
  // src/runtime/capability.ts
479
- var import_client = require("eve/client");
834
+ var import_client2 = require("eve/client");
480
835
  var MAX_REFRESH_SKEW_MS = 3e4;
481
836
  var LOCAL_LOOPBACK_ORIGINS = [
482
837
  "http://127.0.0.1:3010",
@@ -621,7 +976,7 @@ async function withCapabilityRefresh(capability, request) {
621
976
  try {
622
977
  return await request();
623
978
  } catch (error) {
624
- if (!(error instanceof import_client.ClientError) || error.status !== 401) {
979
+ if (!(error instanceof import_client2.ClientError) || error.status !== 401) {
625
980
  throw error;
626
981
  }
627
982
  capability.invalidate();
@@ -629,6 +984,75 @@ async function withCapabilityRefresh(capability, request) {
629
984
  }
630
985
  }
631
986
 
987
+ // src/runtime/handoff.ts
988
+ function createHandoffClient(options) {
989
+ const target = () => {
990
+ const sessionId = options.getSessionId();
991
+ if (!sessionId)
992
+ throw new Error(
993
+ "Start a conversation before requesting a representative."
994
+ );
995
+ return {
996
+ sessionId,
997
+ path: `/webless/v1/session/${encodeURIComponent(sessionId)}/handoff`
998
+ };
999
+ };
1000
+ const request = (path, init) => withCapabilityRefresh(options.capability, async () => {
1001
+ const response = await options.getClient().fetch(path, {
1002
+ ...init,
1003
+ cache: "no-store",
1004
+ redirect: "error"
1005
+ });
1006
+ if (!response.ok) {
1007
+ throw new import_client3.ClientError(
1008
+ response.status,
1009
+ await response.text(),
1010
+ response.headers
1011
+ );
1012
+ }
1013
+ return response;
1014
+ });
1015
+ const command = async (suffix, body, signal) => {
1016
+ const { path } = target();
1017
+ await request(path + suffix, {
1018
+ method: "POST",
1019
+ headers: { "content-type": "application/json" },
1020
+ body: JSON.stringify(body),
1021
+ signal
1022
+ });
1023
+ };
1024
+ return {
1025
+ async read(input = {}) {
1026
+ const { after } = agentRuntimeHandoffReadRequestSchema.parse({
1027
+ after: input.after
1028
+ });
1029
+ const { sessionId, path } = target();
1030
+ const response = await request(`${path}?after=${after}`, {
1031
+ signal: input.signal
1032
+ });
1033
+ const value = await response.json();
1034
+ const page = agentRuntimeHandoffPageSchema.parse(value);
1035
+ if (page.sessionId !== sessionId || page.after !== after) {
1036
+ throw new Error(
1037
+ "The handoff response does not belong to this conversation or cursor."
1038
+ );
1039
+ }
1040
+ return page;
1041
+ },
1042
+ start: (operationId, signal) => command(
1043
+ "",
1044
+ agentRuntimeHandoffStartSchema.parse({ operationId }),
1045
+ signal
1046
+ ),
1047
+ send: (input, signal) => command(
1048
+ "/messages",
1049
+ agentRuntimeHandoffMessageSchema.parse(input),
1050
+ signal
1051
+ ),
1052
+ returnToAI: (input, signal) => command("/return", agentRuntimeHandoffCommandSchema.parse(input), signal)
1053
+ };
1054
+ }
1055
+
632
1056
  // src/runtime/config.ts
633
1057
  var DEFAULT_RUNTIME_ORIGIN = "https://runtime.staging.webless.ai";
634
1058
  var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
@@ -1219,6 +1643,11 @@ var AgentSession = class {
1219
1643
  version,
1220
1644
  visitorSessionId
1221
1645
  });
1646
+ this.handoff = createHandoffClient({
1647
+ getClient: () => this.ensureClient(),
1648
+ getSessionId: () => this.getActiveSessionId(),
1649
+ capability: this.capability
1650
+ });
1222
1651
  }
1223
1652
  indexId;
1224
1653
  version;
@@ -1231,6 +1660,7 @@ var AgentSession = class {
1231
1660
  activeResponse;
1232
1661
  childStreams;
1233
1662
  capability;
1663
+ handoff;
1234
1664
  getActiveSessionId() {
1235
1665
  return this.session?.state.sessionId ?? loadPersistedAgentSession(this.visitorSessionId, this.storeOptions)?.sessionId;
1236
1666
  }
@@ -1305,7 +1735,7 @@ var AgentSession = class {
1305
1735
  }
1306
1736
  this.activeResponse = void 0;
1307
1737
  this.session = void 0;
1308
- this.client = new import_client2.Client({
1738
+ this.client = new import_client4.Client({
1309
1739
  auth: { bearer: () => this.capability.getAccessToken() },
1310
1740
  host: config.host,
1311
1741
  redirect: "error"
@@ -1341,7 +1771,7 @@ var AgentSession = class {
1341
1771
  () => activeSession.send(message, { signal })
1342
1772
  );
1343
1773
  } catch (error) {
1344
- if (error instanceof import_client2.ClientError && error.status === 409 && error.code === "session_not_active") {
1774
+ if (error instanceof import_client4.ClientError && error.status === 409 && error.code === "session_not_active") {
1345
1775
  clearPersistedAgentSession(this.visitorSessionId, this.storeOptions);
1346
1776
  this.session = void 0;
1347
1777
  session = void 0;
@@ -1515,10 +1945,10 @@ var AgentSession = class {
1515
1945
  }
1516
1946
  this.session = session;
1517
1947
  const inputResponses = responses.map(
1518
- ({ requestId, optionId, text: text2 }) => ({
1948
+ ({ requestId, optionId, text: text3 }) => ({
1519
1949
  requestId,
1520
1950
  ...optionId ? { optionId } : {},
1521
- ...text2 ? { text: text2 } : {}
1951
+ ...text3 ? { text: text3 } : {}
1522
1952
  })
1523
1953
  );
1524
1954
  const response = await withCapabilityRefresh(
@@ -1608,6 +2038,7 @@ function createAgentClient(options) {
1608
2038
  );
1609
2039
  return {
1610
2040
  indexId,
2041
+ handoff: session.handoff,
1611
2042
  version,
1612
2043
  runtimeOrigin,
1613
2044
  visitorSessionId,
@@ -1639,7 +2070,7 @@ function createAgentClient(options) {
1639
2070
  }
1640
2071
 
1641
2072
  // src/runtime/errors.ts
1642
- var import_client3 = require("eve/client");
2073
+ var import_client5 = require("eve/client");
1643
2074
  var TRANSIENT_AGENT_ERROR_MESSAGE = "The agent run stopped before the action finished. Please try again.";
1644
2075
  var PREVIEW_AUTHORIZATION_ERROR_MESSAGE = "This preview could not be authorized. Open the latest preview from Webless.";
1645
2076
  function isTransientRuntimeMessage(message) {
@@ -1651,7 +2082,7 @@ function isPreviewAuthorizationMessage(message) {
1651
2082
  return normalized.includes("unpublished preview authorization") || normalized.includes("unpublished preview grant") || normalized.includes("agent studio preview grant") || normalized.includes("preview authorization");
1652
2083
  }
1653
2084
  function formatAgentError(error) {
1654
- if (error instanceof import_client3.ClientError) {
2085
+ if (error instanceof import_client5.ClientError) {
1655
2086
  if (error.status === 401 && error.code === "index_required") {
1656
2087
  return "Missing indexId \u2014 pass a published index id to createAgentClient().";
1657
2088
  }
@@ -1685,14 +2116,14 @@ function formatAgentError(error) {
1685
2116
  var COMPOSER_FORM_SKIP_DISMISSAL = "I'd like to keep chatting without sharing the requested details for now. Please don't ask for them again unless I choose to proceed with something that needs them.";
1686
2117
  function visitorMessageDisplayText(message) {
1687
2118
  if (message.role !== "visitor") return message.text;
1688
- const text2 = message.text.trim();
1689
- if (!text2) return message.text;
1690
- if (text2.startsWith(COMPOSER_FORM_SKIP_DISMISSAL)) {
1691
- const afterDismissal = text2.slice(COMPOSER_FORM_SKIP_DISMISSAL.length).trim().replace(/^\n+/, "").trim();
2119
+ const text3 = message.text.trim();
2120
+ if (!text3) return message.text;
2121
+ if (text3.startsWith(COMPOSER_FORM_SKIP_DISMISSAL)) {
2122
+ const afterDismissal = text3.slice(COMPOSER_FORM_SKIP_DISMISSAL.length).trim().replace(/^\n+/, "").trim();
1692
2123
  if (afterDismissal) return afterDismissal;
1693
2124
  }
1694
2125
  const runtime = message.runtimeText?.trim();
1695
- if (runtime && text2 === runtime && runtime.includes(COMPOSER_FORM_SKIP_DISMISSAL)) {
2126
+ if (runtime && text3 === runtime && runtime.includes(COMPOSER_FORM_SKIP_DISMISSAL)) {
1696
2127
  const afterDismissal = runtime.slice(
1697
2128
  runtime.indexOf(COMPOSER_FORM_SKIP_DISMISSAL) + COMPOSER_FORM_SKIP_DISMISSAL.length
1698
2129
  ).trim().replace(/^\n+/, "").trim();
@@ -1766,13 +2197,13 @@ function parseVisitorFormFields(value) {
1766
2197
  const seen = /* @__PURE__ */ new Set();
1767
2198
  for (const item of value) {
1768
2199
  const record2 = asRecord(item);
1769
- const id = asString(record2?.id).toLowerCase().replace(/[^a-z0-9_-]/g, "");
2200
+ const id2 = asString(record2?.id).toLowerCase().replace(/[^a-z0-9_-]/g, "");
1770
2201
  const kind = asString(record2?.kind);
1771
- if (!record2 || !id || seen.has(id) || !isFieldKind2(kind)) continue;
1772
- seen.add(id);
1773
- const label = asString(record2.label) || id;
2202
+ if (!record2 || !id2 || seen.has(id2) || !isFieldKind2(kind)) continue;
2203
+ seen.add(id2);
2204
+ const label = asString(record2.label) || id2;
1774
2205
  fields.push({
1775
- id,
2206
+ id: id2,
1776
2207
  kind,
1777
2208
  label,
1778
2209
  placeholder: asString(record2.placeholder) || label,
@@ -1808,52 +2239,52 @@ function formatComposerFormMessage(form, values) {
1808
2239
  return value ? `${field.label}: ${value}` : "";
1809
2240
  }).filter(Boolean).join("\n");
1810
2241
  }
1811
- function looksLikeFieldCollection(text2) {
2242
+ function looksLikeFieldCollection(text3) {
1812
2243
  return /\b(please|need|send|share|enter|provide|add|collect|what|which|use)\b/i.test(
1813
- text2
1814
- ) || /:\s*$/m.test(text2) || /^[-*•]\s+/m.test(text2);
2244
+ text3
2245
+ ) || /:\s*$/m.test(text3) || /^[-*•]\s+/m.test(text3);
1815
2246
  }
1816
- function looksLikeBookingCopy(text2) {
2247
+ function looksLikeBookingCopy(text3) {
1817
2248
  return /book(ing)? (card|a demo|this time)|pick a (date|time)|available (times|slots)|calendly/i.test(
1818
- text2
2249
+ text3
1819
2250
  );
1820
2251
  }
1821
- function echoedLabeledFieldIds(text2) {
2252
+ function echoedLabeledFieldIds(text3) {
1822
2253
  const ids = /* @__PURE__ */ new Set();
1823
- if (/\bname\s*:\s+\S+/i.test(text2)) ids.add("name");
1824
- if (/\be-?mail\s*:\s+\S+/i.test(text2)) ids.add("email");
1825
- if (/\bphone\s*:\s+\S+/i.test(text2)) ids.add("phone");
1826
- if (/\bcompany\s*:\s+\S+/i.test(text2)) ids.add("company");
2254
+ if (/\bname\s*:\s+\S+/i.test(text3)) ids.add("name");
2255
+ if (/\be-?mail\s*:\s+\S+/i.test(text3)) ids.add("email");
2256
+ if (/\bphone\s*:\s+\S+/i.test(text3)) ids.add("phone");
2257
+ if (/\bcompany\s*:\s+\S+/i.test(text3)) ids.add("company");
1827
2258
  return ids;
1828
2259
  }
1829
- function matchLibraryFields(text2) {
2260
+ function matchLibraryFields(text3) {
1830
2261
  return FIELD_LIBRARY.flatMap((field) => {
1831
- if (field.exclude?.test(text2)) {
1832
- const leftover = text2.replace(field.exclude, " ");
2262
+ if (field.exclude?.test(text3)) {
2263
+ const leftover = text3.replace(field.exclude, " ");
1833
2264
  if (!field.patterns.some((pattern) => pattern.test(leftover))) return [];
1834
- } else if (!field.patterns.some((pattern) => pattern.test(text2))) {
2265
+ } else if (!field.patterns.some((pattern) => pattern.test(text3))) {
1835
2266
  return [];
1836
2267
  }
1837
2268
  const { patterns: _patterns, exclude: _exclude, ...next } = field;
1838
2269
  return [next];
1839
2270
  }).slice(0, MAX_FORM_FIELDS);
1840
2271
  }
1841
- function looksLikeCompletedActionRecap(text2) {
1842
- const confirmingCreate = /\bshould i create this\b/i.test(text2);
1843
- const echoingFilledFields = /\b(?:name|email|phone|company)\s*:\s+\S+/i.test(text2) && /[^\s@]+@[^\s@]+\.[^\s@]+/i.test(text2);
2272
+ function looksLikeCompletedActionRecap(text3) {
2273
+ const confirmingCreate = /\bshould i create this\b/i.test(text3);
2274
+ const echoingFilledFields = /\b(?:name|email|phone|company)\s*:\s+\S+/i.test(text3) && /[^\s@]+@[^\s@]+\.[^\s@]+/i.test(text3);
1844
2275
  if (echoingFilledFields) {
1845
- if (looksLikeFieldCollection(text2)) {
1846
- const echoed = echoedLabeledFieldIds(text2);
1847
- if (matchLibraryFields(text2).some((field) => !echoed.has(field.id))) {
2276
+ if (looksLikeFieldCollection(text3)) {
2277
+ const echoed = echoedLabeledFieldIds(text3);
2278
+ if (matchLibraryFields(text3).some((field) => !echoed.has(field.id))) {
1848
2279
  return false;
1849
2280
  }
1850
2281
  }
1851
2282
  return true;
1852
2283
  }
1853
- return confirmingCreate && !looksLikeFieldCollection(text2);
2284
+ return confirmingCreate && !looksLikeFieldCollection(text3);
1854
2285
  }
1855
- function inferComposerForm(text2) {
1856
- const cleaned = text2.trim();
2286
+ function inferComposerForm(text3) {
2287
+ const cleaned = text3.trim();
1857
2288
  if (!cleaned || looksLikeBookingCopy(cleaned) || looksLikeCompletedActionRecap(cleaned) || !looksLikeFieldCollection(cleaned)) {
1858
2289
  return null;
1859
2290
  }
@@ -1868,8 +2299,8 @@ function resolveComposerForm(input) {
1868
2299
  if (input.enabled === false || input.hasBookingOffer || input.hasPendingConfirmation) {
1869
2300
  return null;
1870
2301
  }
1871
- const text2 = input.agentText.trim();
1872
- if (!text2) return null;
2302
+ const text3 = input.agentText.trim();
2303
+ if (!text3) return null;
1873
2304
  const card = input.cards?.find((item) => item.type === "visitor_form");
1874
2305
  if (card?.fields && card.fields.length >= MIN_FORM_FIELDS) {
1875
2306
  return {
@@ -1877,7 +2308,7 @@ function resolveComposerForm(input) {
1877
2308
  fields: card.fields.slice(0, MAX_FORM_FIELDS)
1878
2309
  };
1879
2310
  }
1880
- return inferComposerForm(text2);
2311
+ return inferComposerForm(text3);
1881
2312
  }
1882
2313
 
1883
2314
  // src/react/lib/tool-card.ts
@@ -1891,9 +2322,9 @@ function preferBookingOffer(current, next) {
1891
2322
  }
1892
2323
  return next;
1893
2324
  }
1894
- function looksLikeBookingReady(text2) {
2325
+ function looksLikeBookingReady(text3) {
1895
2326
  return /demo option|options are ready|pick a (date|time)|available (times|slots)|book(ing)? (card|the demo)|\bschedule\b/i.test(
1896
- text2
2327
+ text3
1897
2328
  );
1898
2329
  }
1899
2330
  function bookingOfferIdentityKey(offer) {
@@ -2003,29 +2434,29 @@ function bookingCardFromActionOutput(output) {
2003
2434
  const data = asRecord2(record2?.output) ?? asRecord2(record2?.data) ?? record2;
2004
2435
  return parseToolCard(data);
2005
2436
  }
2006
- function ensureBookingOfferText(text2, offer) {
2007
- if (!offer) return text2;
2008
- if (extractToolCards(text2).some((card) => card.type === "booking_offer")) {
2009
- return text2;
2437
+ function ensureBookingOfferText(text3, offer) {
2438
+ if (!offer) return text3;
2439
+ if (extractToolCards(text3).some((card) => card.type === "booking_offer")) {
2440
+ return text3;
2010
2441
  }
2011
- const visible = stripToolCards(text2).trim() || text2.trim();
2442
+ const visible = stripToolCards(text3).trim() || text3.trim();
2012
2443
  return `${visible}
2013
2444
 
2014
2445
  ${formatBookingOfferFence(offer)}`;
2015
2446
  }
2016
- function hideToolCardFences(text2) {
2017
- return text2.replace(/```(?:webless-tool-card|json)\s*[\s\S]*?```/gi, "").replace(/```(?:webless-tool-card|json)[\s\S]*$/i, "").replace(/\n{3,}/g, "\n\n").trim();
2447
+ function hideToolCardFences(text3) {
2448
+ return text3.replace(/```(?:webless-tool-card|json)\s*[\s\S]*?```/gi, "").replace(/```(?:webless-tool-card|json)[\s\S]*$/i, "").replace(/\n{3,}/g, "\n\n").trim();
2018
2449
  }
2019
2450
  var BOOKING_CARD_FALLBACK = "Pick a date and time that works for you.";
2020
- function looksLikeBookingAvailabilityDump(text2) {
2021
- const cleaned = text2.trim();
2451
+ function looksLikeBookingAvailabilityDump(text3) {
2452
+ const cleaned = text3.trim();
2022
2453
  if (!cleaned) return false;
2023
2454
  const isoCount = (cleaned.match(/\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/g) ?? []).length;
2024
2455
  const utcCount = (cleaned.match(/\bUTC\b/g) ?? []).length;
2025
2456
  return isoCount >= 2 || utcCount >= 2 || /api\.calendly\.com|calendly\.com\//i.test(cleaned) || /webless-tool-card/i.test(cleaned);
2026
2457
  }
2027
- function sanitizeBookingOfferCopy(text2) {
2028
- const cleaned = hideToolCardFences(text2);
2458
+ function sanitizeBookingOfferCopy(text3) {
2459
+ const cleaned = hideToolCardFences(text3);
2029
2460
  if (!cleaned || looksLikeBookingAvailabilityDump(cleaned)) {
2030
2461
  return BOOKING_CARD_FALLBACK;
2031
2462
  }
@@ -2038,9 +2469,9 @@ function visitorTimeZone() {
2038
2469
  return "UTC";
2039
2470
  }
2040
2471
  }
2041
- function extractToolCards(text2) {
2472
+ function extractToolCards(text3) {
2042
2473
  const cards = [];
2043
- for (const match of text2.matchAll(FENCE_PATTERN)) {
2474
+ for (const match of text3.matchAll(FENCE_PATTERN)) {
2044
2475
  try {
2045
2476
  const card = parseToolCard(JSON.parse(match[1] ?? ""));
2046
2477
  if (card) cards.push(card);
@@ -2049,8 +2480,8 @@ function extractToolCards(text2) {
2049
2480
  }
2050
2481
  return cards;
2051
2482
  }
2052
- function stripToolCards(text2) {
2053
- return text2.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
2483
+ function stripToolCards(text3) {
2484
+ return text3.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
2054
2485
  }
2055
2486
  function localDateKey(date) {
2056
2487
  if (Number.isNaN(date.getTime())) return "";
@@ -2163,7 +2594,7 @@ function visitorBookingPrefix(booking) {
2163
2594
  function record(value) {
2164
2595
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2165
2596
  }
2166
- function text(value, max = 500) {
2597
+ function text2(value, max = 500) {
2167
2598
  return typeof value === "string" && value.trim().length > 0 && value.trim().length <= max;
2168
2599
  }
2169
2600
  function safeSearchUrl(value) {
@@ -2184,7 +2615,7 @@ function parseAgentSearchReferences(value) {
2184
2615
  const source = record(value2);
2185
2616
  if (!source || Object.keys(source).some(
2186
2617
  (key) => !["id", "title", "url"].includes(key)
2187
- ) || !text(source.id, Infinity) || !text(source.title) || source.url !== void 0 && typeof source.url !== "string")
2618
+ ) || !text2(source.id, Infinity) || !text2(source.title) || source.url !== void 0 && typeof source.url !== "string")
2188
2619
  return null;
2189
2620
  const url = safeSearchUrl(source.url);
2190
2621
  sources.push({
@@ -2196,7 +2627,7 @@ function parseAgentSearchReferences(value) {
2196
2627
  let cta;
2197
2628
  if (data.cta !== void 0) {
2198
2629
  const action = record(data.cta);
2199
- if (!action || Object.keys(action).some((key) => !["label", "url"].includes(key)) || !text(action.label) || action.url !== void 0 && typeof action.url !== "string")
2630
+ if (!action || Object.keys(action).some((key) => !["label", "url"].includes(key)) || !text2(action.label) || action.url !== void 0 && typeof action.url !== "string")
2200
2631
  return null;
2201
2632
  const url = safeSearchUrl(action.url);
2202
2633
  cta = { label: action.label.trim(), ...url ? { url } : {} };
@@ -2215,7 +2646,7 @@ function parseAgentSearchDiscoveryOutput(value) {
2215
2646
  const data = record(decoded);
2216
2647
  if (!data || Object.keys(data).some(
2217
2648
  (key) => !["answer", "sources", "cta", "suggestions"].includes(key)
2218
- ) || !text(data.answer, 5e4) || !Array.isArray(data.suggestions) || data.suggestions.length > 8 || !data.suggestions.every((item) => text(item)))
2649
+ ) || !text2(data.answer, 5e4) || !Array.isArray(data.suggestions) || data.suggestions.length > 8 || !data.suggestions.every((item) => text2(item)))
2219
2650
  return null;
2220
2651
  const references = parseAgentSearchReferences(data);
2221
2652
  return references ? {
@@ -2233,9 +2664,21 @@ function conversationKey(storageKeyPrefix, visitorSessionId) {
2233
2664
  function parseMessage(value) {
2234
2665
  if (typeof value !== "object" || value === null) return null;
2235
2666
  const record2 = value;
2236
- if (typeof record2.id !== "string" || record2.role !== "agent" && record2.role !== "visitor" || typeof record2.text !== "string" || typeof record2.createdAt !== "number" || !Number.isFinite(record2.createdAt)) {
2667
+ if (typeof record2.id !== "string" || record2.role !== "agent" && record2.role !== "visitor" && record2.role !== "human" || typeof record2.text !== "string" || typeof record2.createdAt !== "number" || !Number.isFinite(record2.createdAt)) {
2237
2668
  return null;
2238
2669
  }
2670
+ if (record2.role === "human") {
2671
+ const person = record2.representative;
2672
+ if (typeof person !== "object" || person === null || !("id" in person) || !("name" in person) || typeof person.id !== "string" || typeof person.name !== "string")
2673
+ return null;
2674
+ return {
2675
+ id: record2.id,
2676
+ role: "human",
2677
+ text: record2.text,
2678
+ createdAt: record2.createdAt,
2679
+ representative: { id: person.id, name: person.name }
2680
+ };
2681
+ }
2239
2682
  if (record2.role === "visitor") {
2240
2683
  return {
2241
2684
  id: record2.id,
@@ -2530,10 +2973,10 @@ function safeText(value) {
2530
2973
  return value.trim().slice(0, MAX_TEXT_LENGTH);
2531
2974
  }
2532
2975
  function safeHref(value) {
2533
- const text2 = safeText(value);
2534
- if (!text2) return "";
2976
+ const text3 = safeText(value);
2977
+ if (!text3) return "";
2535
2978
  try {
2536
- const url = new URL(text2);
2979
+ const url = new URL(text3);
2537
2980
  return url.protocol === "https:" ? url.toString() : "";
2538
2981
  } catch {
2539
2982
  return "";
@@ -2896,15 +3339,15 @@ function appendChatCollectiblePrompts(messages, requests) {
2896
3339
  }
2897
3340
  return next;
2898
3341
  }
2899
- function chatInputResponseForText(requests, text2) {
2900
- const trimmed = text2.trim();
3342
+ function chatInputResponseForText(requests, text3) {
3343
+ const trimmed = text3.trim();
2901
3344
  if (!trimmed) return null;
2902
3345
  const pending = requests.find(isChatCollectibleInputRequest);
2903
3346
  if (!pending) return null;
2904
3347
  return { requestId: pending.requestId, text: trimmed };
2905
3348
  }
2906
- function normalizeAssistantDedupeKey(text2) {
2907
- return text2.trim().replace(/\s+/g, " ").toLowerCase();
3349
+ function normalizeAssistantDedupeKey(text3) {
3350
+ return text3.trim().replace(/\s+/g, " ").toLowerCase();
2908
3351
  }
2909
3352
  function isNearDuplicateAssistantText(left, right) {
2910
3353
  const a = normalizeAssistantDedupeKey(left);
@@ -3017,6 +3460,7 @@ function isJsonRecord(value) {
3017
3460
  return value !== null && typeof value === "object" && !Array.isArray(value);
3018
3461
  }
3019
3462
  function useAgentChat({
3463
+ handoffEnabled = false,
3020
3464
  customerId,
3021
3465
  getUnpublishedPreviewGrant,
3022
3466
  indexId,
@@ -3028,13 +3472,13 @@ function useAgentChat({
3028
3472
  greeting,
3029
3473
  toolResultRegistry
3030
3474
  }) {
3031
- const initialState = (0, import_react2.useMemo)(
3475
+ const initialState = (0, import_react3.useMemo)(
3032
3476
  () => createInitialState(greeting?.trim() || DEFAULT_GREETING),
3033
3477
  [greeting]
3034
3478
  );
3035
- const previewGrantProviderRef = (0, import_react2.useRef)(getUnpublishedPreviewGrant);
3479
+ const previewGrantProviderRef = (0, import_react3.useRef)(getUnpublishedPreviewGrant);
3036
3480
  previewGrantProviderRef.current = getUnpublishedPreviewGrant;
3037
- const toolResultRegistryRef = (0, import_react2.useRef)(toolResultRegistry);
3481
+ const toolResultRegistryRef = (0, import_react3.useRef)(toolResultRegistry);
3038
3482
  toolResultRegistryRef.current = toolResultRegistry;
3039
3483
  const resolveUnpublishedPreviewGrant = () => {
3040
3484
  const provider = previewGrantProviderRef.current;
@@ -3047,7 +3491,7 @@ function useAgentChat({
3047
3491
  }
3048
3492
  return provider();
3049
3493
  };
3050
- const resolvedStorageKeyPrefix = (0, import_react2.useMemo)(
3494
+ const resolvedStorageKeyPrefix = (0, import_react3.useMemo)(
3051
3495
  () => storageKeyPrefix?.trim() || buildAgentStorageKeyPrefix({
3052
3496
  customerId,
3053
3497
  indexId,
@@ -3056,23 +3500,23 @@ function useAgentChat({
3056
3500
  }),
3057
3501
  [customerId, indexId, runtimeOrigin, storageKeyPrefix, version]
3058
3502
  );
3059
- const visitorId = (0, import_react2.useMemo)(
3503
+ const visitorId = (0, import_react3.useMemo)(
3060
3504
  () => visitorSessionId?.trim() || getOrCreateVisitorSessionId({
3061
3505
  storageKeyPrefix: resolvedStorageKeyPrefix
3062
3506
  }),
3063
3507
  [resolvedStorageKeyPrefix, visitorSessionId]
3064
3508
  );
3065
- const [state, setState] = (0, import_react2.useState)(
3509
+ const [state, setState] = (0, import_react3.useState)(
3066
3510
  () => stateFromConversation(
3067
3511
  loadPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId),
3068
3512
  initialState
3069
3513
  )
3070
3514
  );
3071
- const pendingBookingRef = (0, import_react2.useRef)(
3515
+ const pendingBookingRef = (0, import_react3.useRef)(
3072
3516
  loadPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId)
3073
3517
  );
3074
- const runRef = (0, import_react2.useRef)(null);
3075
- const clientRef = (0, import_react2.useRef)(
3518
+ const runRef = (0, import_react3.useRef)(null);
3519
+ const clientRef = (0, import_react3.useRef)(
3076
3520
  createAgentClient({
3077
3521
  locationConsent: true,
3078
3522
  customerId,
@@ -3086,8 +3530,8 @@ function useAgentChat({
3086
3530
  })
3087
3531
  );
3088
3532
  const identityKey = `${customerId ?? ""}|${indexId}|${version ?? "published"}|${runtimeOrigin ?? ""}|${previewBuildId ?? ""}|${resolvedStorageKeyPrefix}|${visitorId}|${greeting ?? ""}`;
3089
- const identityRef = (0, import_react2.useRef)(identityKey);
3090
- (0, import_react2.useEffect)(() => {
3533
+ const identityRef = (0, import_react3.useRef)(identityKey);
3534
+ (0, import_react3.useEffect)(() => {
3091
3535
  if (identityRef.current === identityKey) {
3092
3536
  return;
3093
3537
  }
@@ -3126,7 +3570,7 @@ function useAgentChat({
3126
3570
  version,
3127
3571
  visitorId
3128
3572
  ]);
3129
- (0, import_react2.useEffect)(() => {
3573
+ (0, import_react3.useEffect)(() => {
3130
3574
  if (!hasVisitorMessages(state.messages)) return;
3131
3575
  savePersistedAgentConversation(resolvedStorageKeyPrefix, visitorId, {
3132
3576
  messages: state.messages,
@@ -3146,16 +3590,77 @@ function useAgentChat({
3146
3590
  state.toolResults,
3147
3591
  visitorId
3148
3592
  ]);
3149
- const reset = (0, import_react2.useCallback)(() => {
3593
+ const handoff = useAgentHandoff({
3594
+ enabled: handoffEnabled,
3595
+ client: clientRef.current,
3596
+ sessionId: clientRef.current.getActiveSessionId(),
3597
+ onOwnership: () => {
3598
+ runRef.current?.abort();
3599
+ runRef.current = null;
3600
+ setState(
3601
+ (prev) => prev.phase === "complete" && !prev.streamingText && !prev.pendingInputs?.length && !prev.toolSteps.length ? prev : {
3602
+ ...prev,
3603
+ phase: "complete",
3604
+ streamingText: "",
3605
+ pendingInputs: [],
3606
+ toolSteps: [],
3607
+ toolResults: [],
3608
+ pendingOffer: null,
3609
+ error: null
3610
+ }
3611
+ );
3612
+ },
3613
+ onEvents: (events) => {
3614
+ setState((prev) => {
3615
+ const seen = new Set(prev.messages.map((message) => message.id));
3616
+ const messages = [];
3617
+ for (const event of events) {
3618
+ if (event.type === "status" || seen.has(`handoff-${event.id}`))
3619
+ continue;
3620
+ messages.push(
3621
+ event.type === "human.message" ? {
3622
+ id: `handoff-${event.id}`,
3623
+ role: "human",
3624
+ text: event.message,
3625
+ createdAt: Date.parse(event.createdAt),
3626
+ representative: event.actor
3627
+ } : {
3628
+ id: `handoff-${event.id}`,
3629
+ role: "visitor",
3630
+ text: event.message,
3631
+ createdAt: Date.parse(event.createdAt)
3632
+ }
3633
+ );
3634
+ }
3635
+ return messages.length ? {
3636
+ ...prev,
3637
+ messages: [...prev.messages, ...messages].sort(
3638
+ (a, b) => a.createdAt - b.createdAt
3639
+ )
3640
+ } : prev;
3641
+ });
3642
+ }
3643
+ });
3644
+ const handoffBlocksAI = (0, import_react3.useRef)(handoff.blocksAI);
3645
+ handoffBlocksAI.current = handoff.blocksAI;
3646
+ const reset = (0, import_react3.useCallback)(() => {
3647
+ if (handoff.blocksAI && !handoff.canReset) return;
3150
3648
  runRef.current?.abort();
3151
3649
  runRef.current = null;
3152
3650
  clientRef.current.reset();
3153
3651
  pendingBookingRef.current = null;
3154
3652
  clearPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId);
3155
3653
  setState(initialState);
3156
- }, [initialState, resolvedStorageKeyPrefix, visitorId]);
3157
- const runTurn = (0, import_react2.useCallback)(
3654
+ }, [
3655
+ handoff.blocksAI,
3656
+ handoff.canReset,
3657
+ initialState,
3658
+ resolvedStorageKeyPrefix,
3659
+ visitorId
3660
+ ]);
3661
+ const runTurn = (0, import_react3.useCallback)(
3158
3662
  async (input) => {
3663
+ if (handoffBlocksAI.current) return null;
3159
3664
  const {
3160
3665
  controller,
3161
3666
  initialText = "",
@@ -3279,6 +3784,7 @@ function useAgentChat({
3279
3784
  signal
3280
3785
  });
3281
3786
  if (resume && finalText === null) {
3787
+ if (!isActiveRun() || handoffBlocksAI.current) return null;
3282
3788
  finalText = await clientRef.current.sendTurn(visitorText, {
3283
3789
  handlers,
3284
3790
  signal
@@ -3311,8 +3817,12 @@ function useAgentChat({
3311
3817
  messages: appendAgentTurnMessage(
3312
3818
  prev.messages,
3313
3819
  displayText,
3314
- (prev.toolResults ?? []).filter((result) => result.kind === "search"),
3315
- (prev.toolResults ?? []).filter((result) => result.kind !== "search" && result.kind !== "input")
3820
+ (prev.toolResults ?? []).filter(
3821
+ (result) => result.kind === "search"
3822
+ ),
3823
+ (prev.toolResults ?? []).filter(
3824
+ (result) => result.kind !== "search" && result.kind !== "input"
3825
+ )
3316
3826
  ),
3317
3827
  toolResults: (prev.toolResults ?? []).filter(
3318
3828
  (result) => result.kind === "input"
@@ -3353,7 +3863,7 @@ function useAgentChat({
3353
3863
  },
3354
3864
  [resolvedStorageKeyPrefix, visitorId]
3355
3865
  );
3356
- const rememberBooking = (0, import_react2.useCallback)(
3866
+ const rememberBooking = (0, import_react3.useCallback)(
3357
3867
  (booking) => {
3358
3868
  const current = pendingBookingRef.current;
3359
3869
  if (current?.eventUri === booking.eventUri && current.inviteeEmail === booking.inviteeEmail && current.inviteeUri === booking.inviteeUri) {
@@ -3364,7 +3874,7 @@ function useAgentChat({
3364
3874
  },
3365
3875
  [resolvedStorageKeyPrefix, visitorId]
3366
3876
  );
3367
- const forgetBooking = (0, import_react2.useCallback)(
3877
+ const forgetBooking = (0, import_react3.useCallback)(
3368
3878
  (eventUri) => {
3369
3879
  const current = pendingBookingRef.current;
3370
3880
  if (!current) return;
@@ -3374,12 +3884,21 @@ function useAgentChat({
3374
3884
  },
3375
3885
  [resolvedStorageKeyPrefix, visitorId]
3376
3886
  );
3377
- const submit = (0, import_react2.useCallback)(
3887
+ const submit = (0, import_react3.useCallback)(
3378
3888
  async (visitorText, options) => {
3379
3889
  const trimmed = visitorText.trim();
3380
3890
  const outgoing = options?.runtimeText ?? visitorText;
3381
3891
  if (!outgoing.trim()) return null;
3382
3892
  const declinedComposerFormId = options?.declinedComposerFormId?.trim();
3893
+ if (handoff.blocksAI) {
3894
+ if (!["requesting", "queued", "human"].includes(
3895
+ handoff.page?.status ?? ""
3896
+ ) || handoff.busy)
3897
+ return null;
3898
+ if (!trimmed) return null;
3899
+ await handoff.send(trimmed);
3900
+ return null;
3901
+ }
3383
3902
  const chatResponse = chatInputResponseForText(
3384
3903
  state.pendingInputs ?? [],
3385
3904
  outgoing.trim()
@@ -3481,9 +4000,10 @@ ${outgoing}` : outgoing;
3481
4000
  visitorText: runtimeText
3482
4001
  });
3483
4002
  },
3484
- [runTurn, state.pendingInputs]
4003
+ [handoff, runTurn, state.pendingInputs]
3485
4004
  );
3486
- const retry = (0, import_react2.useCallback)(async () => {
4005
+ const retry = (0, import_react3.useCallback)(async () => {
4006
+ if (handoffBlocksAI.current) return;
3487
4007
  const visitorMessage = [...state.messages].reverse().find((message) => message.role === "visitor");
3488
4008
  if (!visitorMessage) return;
3489
4009
  if (runRef.current) {
@@ -3516,7 +4036,8 @@ ${outgoing}` : outgoing;
3516
4036
  visitorText: visitorTurnText(visitorMessage)
3517
4037
  });
3518
4038
  }, [runTurn, state.messages]);
3519
- const regenerate = (0, import_react2.useCallback)(async () => {
4039
+ const regenerate = (0, import_react3.useCallback)(async () => {
4040
+ if (handoffBlocksAI.current) return null;
3520
4041
  let lastVisitorIndex = -1;
3521
4042
  for (let index = state.messages.length - 1; index >= 0; index -= 1) {
3522
4043
  if (state.messages[index]?.role === "visitor") {
@@ -3559,7 +4080,7 @@ ${outgoing}` : outgoing;
3559
4080
  visitorText: visitorTurnText(visitorMessage)
3560
4081
  });
3561
4082
  }, [runTurn, state.messages]);
3562
- const respondToToolInput = (0, import_react2.useCallback)(
4083
+ const respondToToolInput = (0, import_react3.useCallback)(
3563
4084
  async (surface, values) => {
3564
4085
  await submit(`${surface.title} submitted`, {
3565
4086
  runtimeText: [
@@ -3572,9 +4093,9 @@ ${outgoing}` : outgoing;
3572
4093
  },
3573
4094
  [submit]
3574
4095
  );
3575
- const respondToInput = (0, import_react2.useCallback)(
4096
+ const respondToInput = (0, import_react3.useCallback)(
3576
4097
  async (response) => {
3577
- if (runRef.current) return;
4098
+ if (handoffBlocksAI.current || runRef.current) return;
3578
4099
  const pending = state.pendingInputs?.find(
3579
4100
  (request) => request.requestId === response.requestId
3580
4101
  );
@@ -3602,7 +4123,8 @@ ${outgoing}` : outgoing;
3602
4123
  },
3603
4124
  [respondToToolInput, runTurn, state.pendingInputs]
3604
4125
  );
3605
- (0, import_react2.useEffect)(() => {
4126
+ (0, import_react3.useEffect)(() => {
4127
+ if (handoff.blocksAI) return;
3606
4128
  const conversation = loadPersistedAgentConversation(
3607
4129
  resolvedStorageKeyPrefix,
3608
4130
  visitorId
@@ -3624,14 +4146,21 @@ ${outgoing}` : outgoing;
3624
4146
  }
3625
4147
  controller.abort();
3626
4148
  };
3627
- }, [identityKey, resolvedStorageKeyPrefix, runTurn, visitorId]);
3628
- (0, import_react2.useEffect)(() => {
4149
+ }, [
4150
+ handoff.blocksAI,
4151
+ identityKey,
4152
+ resolvedStorageKeyPrefix,
4153
+ runTurn,
4154
+ visitorId
4155
+ ]);
4156
+ (0, import_react3.useEffect)(() => {
3629
4157
  return () => {
3630
4158
  runRef.current?.abort();
3631
4159
  runRef.current = null;
3632
4160
  };
3633
4161
  }, []);
3634
4162
  return {
4163
+ handoff,
3635
4164
  state,
3636
4165
  reset,
3637
4166
  retry,
@@ -3653,12 +4182,12 @@ function isAgentBusy(phase) {
3653
4182
  }
3654
4183
 
3655
4184
  // src/react/hooks/useIsMobile.ts
3656
- var import_react3 = require("react");
4185
+ var import_react4 = require("react");
3657
4186
  function useIsMobile(breakpoint = 767) {
3658
- const [isMobile, setIsMobile] = (0, import_react3.useState)(
4187
+ const [isMobile, setIsMobile] = (0, import_react4.useState)(
3659
4188
  () => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
3660
4189
  );
3661
- (0, import_react3.useEffect)(() => {
4190
+ (0, import_react4.useEffect)(() => {
3662
4191
  const media = window.matchMedia(`(max-width: ${breakpoint}px)`);
3663
4192
  const onChange = () => setIsMobile(media.matches);
3664
4193
  onChange();
@@ -3686,7 +4215,7 @@ function normalizeAgentPlacement(placement) {
3686
4215
  }
3687
4216
 
3688
4217
  // src/react/components/AgentRail/AgentRail.tsx
3689
- var import_react15 = require("react");
4218
+ var import_react16 = require("react");
3690
4219
 
3691
4220
  // src/react/types/conversation.ts
3692
4221
  var defaultAgentRailTheme = {
@@ -3769,7 +4298,7 @@ function agentThemeStyle(theme, resolvedColorScheme) {
3769
4298
  }
3770
4299
 
3771
4300
  // src/react/hooks/useAgentColorScheme.ts
3772
- var import_react4 = require("react");
4301
+ var import_react5 = require("react");
3773
4302
  var DARK_MODE_QUERY = "(prefers-color-scheme: dark)";
3774
4303
  function subscribeToDarkMode(onChange) {
3775
4304
  if (typeof window === "undefined" || !window.matchMedia) {
@@ -3787,7 +4316,7 @@ function getPrefersDarkMode() {
3787
4316
  return typeof window !== "undefined" && Boolean(window.matchMedia?.(DARK_MODE_QUERY).matches);
3788
4317
  }
3789
4318
  function useAgentColorScheme(colorScheme = "auto") {
3790
- const prefersDarkMode = (0, import_react4.useSyncExternalStore)(
4319
+ const prefersDarkMode = (0, import_react5.useSyncExternalStore)(
3791
4320
  subscribeToDarkMode,
3792
4321
  getPrefersDarkMode,
3793
4322
  () => false
@@ -3799,7 +4328,7 @@ function resolveAgentColorScheme(colorScheme = "auto", prefersDarkMode) {
3799
4328
  }
3800
4329
 
3801
4330
  // src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
3802
- var import_react5 = require("react");
4331
+ var import_react6 = require("react");
3803
4332
  var import_jsx_runtime = require("react/jsx-runtime");
3804
4333
  function joinLabels(labels) {
3805
4334
  if (labels.length <= 1) return labels[0] ?? "";
@@ -3918,7 +4447,7 @@ function AgentActivityBubble({
3918
4447
  const statusText = workSummary(steps, failed, brandLabel);
3919
4448
  const softReview = statusText === AGENT_SOFT_REVIEW_STATUS;
3920
4449
  const receiptId = steps.map((step) => step.id).join(":");
3921
- const [expandedReceiptId, setExpandedReceiptId] = (0, import_react5.useState)(
4450
+ const [expandedReceiptId, setExpandedReceiptId] = (0, import_react6.useState)(
3922
4451
  null
3923
4452
  );
3924
4453
  const detailsOpen = !active && expandedReceiptId === receiptId;
@@ -3969,17 +4498,17 @@ function AgentActivityBubble({
3969
4498
  }
3970
4499
 
3971
4500
  // src/react/components/AnswerReceiptDialog/AnswerReceiptDialog.tsx
3972
- var import_react7 = require("react");
4501
+ var import_react8 = require("react");
3973
4502
  var import_react_dom = require("react-dom");
3974
4503
 
3975
4504
  // src/react/components/AgentRail/AgentRailOverlayContext.tsx
3976
- var import_react6 = require("react");
3977
- var AgentRailOverlayContext = (0, import_react6.createContext)(null);
4505
+ var import_react7 = require("react");
4506
+ var AgentRailOverlayContext = (0, import_react7.createContext)(null);
3978
4507
  function useAgentRailPortalRoots() {
3979
- return (0, import_react6.useContext)(AgentRailOverlayContext);
4508
+ return (0, import_react7.useContext)(AgentRailOverlayContext);
3980
4509
  }
3981
4510
  function useAgentRailMenuPortalRoot() {
3982
- return (0, import_react6.useContext)(AgentRailOverlayContext)?.railRef ?? null;
4511
+ return (0, import_react7.useContext)(AgentRailOverlayContext)?.railRef ?? null;
3983
4512
  }
3984
4513
 
3985
4514
  // src/react/components/AnswerReceiptDialog/AnswerReceiptDialog.tsx
@@ -4003,10 +4532,10 @@ function AnswerReceiptDialog({
4003
4532
  }) {
4004
4533
  const portalRoots = useAgentRailPortalRoots();
4005
4534
  const overlayRoot = portalRoots?.overlayRef ?? null;
4006
- const cardRef = (0, import_react7.useRef)(null);
4007
- const closeButtonRef = (0, import_react7.useRef)(null);
4008
- const previouslyFocusedRef = (0, import_react7.useRef)(null);
4009
- (0, import_react7.useEffect)(() => {
4535
+ const cardRef = (0, import_react8.useRef)(null);
4536
+ const closeButtonRef = (0, import_react8.useRef)(null);
4537
+ const previouslyFocusedRef = (0, import_react8.useRef)(null);
4538
+ (0, import_react8.useEffect)(() => {
4010
4539
  previouslyFocusedRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
4011
4540
  closeButtonRef.current?.focus({ preventScroll: true });
4012
4541
  const handleKeyDown = (event) => {
@@ -4091,7 +4620,7 @@ function AnswerReceiptDialog({
4091
4620
  }
4092
4621
 
4093
4622
  // src/react/components/Composer/Composer.tsx
4094
- var import_react8 = require("react");
4623
+ var import_react9 = require("react");
4095
4624
  var import_jsx_runtime3 = require("react/jsx-runtime");
4096
4625
  var FORM_SUBMITTED_MESSAGE = "Shared my details";
4097
4626
  function SendIcon() {
@@ -4153,17 +4682,17 @@ function Composer({
4153
4682
  allowFormResume = true,
4154
4683
  onSubmit
4155
4684
  }) {
4156
- const [value, setValue] = (0, import_react8.useState)("");
4157
- const [draft, setDraft] = (0, import_react8.useState)(() => createDraft(form));
4158
- const inputRef = (0, import_react8.useRef)(null);
4159
- const firstFieldRef = (0, import_react8.useRef)(null);
4160
- const formRef = (0, import_react8.useRef)(null);
4161
- const formId = (0, import_react8.useId)();
4685
+ const [value, setValue] = (0, import_react9.useState)("");
4686
+ const [draft, setDraft] = (0, import_react9.useState)(() => createDraft(form));
4687
+ const inputRef = (0, import_react9.useRef)(null);
4688
+ const firstFieldRef = (0, import_react9.useRef)(null);
4689
+ const formRef = (0, import_react9.useRef)(null);
4690
+ const formId = (0, import_react9.useId)();
4162
4691
  if (form && form.id !== draft.form?.id) setDraft(createDraft(form));
4163
4692
  const savedForm = allowFormResume && !draft.submitted ? draft.form : null;
4164
4693
  const activeForm = !disabled && draft.expanded ? savedForm : null;
4165
4694
  const canSend = !disabled && Boolean(value.trim() || activeForm);
4166
- (0, import_react8.useEffect)(() => {
4695
+ (0, import_react9.useEffect)(() => {
4167
4696
  if (activeForm) firstFieldRef.current?.focus();
4168
4697
  else if ((savedForm || draft.submitted) && !disabled)
4169
4698
  inputRef.current?.focus();
@@ -4424,7 +4953,7 @@ function FollowUpChips({
4424
4953
  }
4425
4954
 
4426
4955
  // src/react/components/MessageBubble/MessageBubble.tsx
4427
- var import_react10 = require("react");
4956
+ var import_react11 = require("react");
4428
4957
 
4429
4958
  // src/react/components/SearchReferences/SearchReferences.tsx
4430
4959
  var import_jsx_runtime5 = require("react/jsx-runtime");
@@ -4515,7 +5044,7 @@ function SearchReferences({
4515
5044
  }
4516
5045
 
4517
5046
  // src/react/components/BookingCard/BookingCard.tsx
4518
- var import_react9 = require("react");
5047
+ var import_react10 = require("react");
4519
5048
  var import_jsx_runtime6 = require("react/jsx-runtime");
4520
5049
  var BOOKING_STEPS = [
4521
5050
  { id: "date", label: "Date" },
@@ -4570,27 +5099,27 @@ function InteractiveBookingCard({
4570
5099
  offer,
4571
5100
  onBook
4572
5101
  }) {
4573
- const fieldId = (0, import_react9.useId)();
5102
+ const fieldId = (0, import_react10.useId)();
4574
5103
  const defaultType = offer.eventTypes[0]?.uri ?? offer.slots[0]?.eventTypeUri ?? "";
4575
- const [step, setStep] = (0, import_react9.useState)("date");
4576
- const [eventTypeUri, setEventTypeUri] = (0, import_react9.useState)(defaultType);
4577
- const [selectedDate, setSelectedDate] = (0, import_react9.useState)("");
4578
- const [startTime, setStartTime] = (0, import_react9.useState)("");
4579
- const [name, setName] = (0, import_react9.useState)("");
4580
- const [email, setEmail] = (0, import_react9.useState)("");
4581
- const activeStepRef = (0, import_react9.useRef)(null);
4582
- const previousStepRef = (0, import_react9.useRef)(step);
5104
+ const [step, setStep] = (0, import_react10.useState)("date");
5105
+ const [eventTypeUri, setEventTypeUri] = (0, import_react10.useState)(defaultType);
5106
+ const [selectedDate, setSelectedDate] = (0, import_react10.useState)("");
5107
+ const [startTime, setStartTime] = (0, import_react10.useState)("");
5108
+ const [name, setName] = (0, import_react10.useState)("");
5109
+ const [email, setEmail] = (0, import_react10.useState)("");
5110
+ const activeStepRef = (0, import_react10.useRef)(null);
5111
+ const previousStepRef = (0, import_react10.useRef)(step);
4583
5112
  const stepIndex = BOOKING_STEPS.findIndex((item) => item.id === step);
4584
- (0, import_react9.useEffect)(() => {
5113
+ (0, import_react10.useEffect)(() => {
4585
5114
  if (previousStepRef.current === step) return;
4586
5115
  previousStepRef.current = step;
4587
5116
  activeStepRef.current?.scrollIntoView({ block: "nearest" });
4588
5117
  }, [step]);
4589
- const slots = (0, import_react9.useMemo)(
5118
+ const slots = (0, import_react10.useMemo)(
4590
5119
  () => bookingSlotsForEventType(offer.slots, eventTypeUri),
4591
5120
  [eventTypeUri, offer.slots]
4592
5121
  );
4593
- const availableByDate = (0, import_react9.useMemo)(() => {
5122
+ const availableByDate = (0, import_react10.useMemo)(() => {
4594
5123
  const next = /* @__PURE__ */ new Map();
4595
5124
  for (const slot of slots) {
4596
5125
  const key = slotDateKey(slot.startTime);
@@ -4598,7 +5127,7 @@ function InteractiveBookingCard({
4598
5127
  }
4599
5128
  return next;
4600
5129
  }, [slots]);
4601
- const [visibleMonth, setVisibleMonth] = (0, import_react9.useState)(
5130
+ const [visibleMonth, setVisibleMonth] = (0, import_react10.useState)(
4602
5131
  () => firstAvailableBookingMonth(slots)
4603
5132
  );
4604
5133
  function selectEventType(nextType) {
@@ -4611,7 +5140,7 @@ function InteractiveBookingCard({
4611
5140
  )
4612
5141
  );
4613
5142
  }
4614
- const daySlots = (0, import_react9.useMemo)(
5143
+ const daySlots = (0, import_react10.useMemo)(
4615
5144
  () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
4616
5145
  [selectedDate, slots]
4617
5146
  );
@@ -4620,7 +5149,7 @@ function InteractiveBookingCard({
4620
5149
  );
4621
5150
  const selectedSample = availableByDate.get(selectedDate) ?? startTime;
4622
5151
  const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
4623
- const weekdays = (0, import_react9.useMemo)(() => weekdayLabels(), []);
5152
+ const weekdays = (0, import_react10.useMemo)(() => weekdayLabels(), []);
4624
5153
  const cells = calendarCells(visibleMonth.year, visibleMonth.month);
4625
5154
  const canPrevMonth = [...availableByDate.keys()].some((key) => {
4626
5155
  const month = monthFromKey(key);
@@ -4906,8 +5435,8 @@ function resolveMessageUrl(safeUrl, baseUrl) {
4906
5435
  // src/react/components/MessageBubble/MessageBubble.tsx
4907
5436
  var import_styles = require("streamdown/styles.css");
4908
5437
  var import_jsx_runtime7 = require("react/jsx-runtime");
4909
- function normalizeDedupeText(text2) {
4910
- return text2.trim().replace(/\s+/g, " ").toLowerCase();
5438
+ function normalizeDedupeText(text3) {
5439
+ return text3.trim().replace(/\s+/g, " ").toLowerCase();
4911
5440
  }
4912
5441
  function paragraphsAreNearDuplicates(first, second) {
4913
5442
  const left = normalizeDedupeText(first);
@@ -4923,8 +5452,8 @@ function paragraphsShareOpening(first, second) {
4923
5452
  if (!opening || opening.length < 20) return false;
4924
5453
  return second.trim().startsWith(opening);
4925
5454
  }
4926
- function collapseRepeatedText(text2) {
4927
- const trimmed = text2.trim();
5455
+ function collapseRepeatedText(text3) {
5456
+ const trimmed = text3.trim();
4928
5457
  if (trimmed.length < 40) return trimmed;
4929
5458
  const paragraphs = trimmed.split(/\n{2,}/u).map((part) => part.trim()).filter(Boolean);
4930
5459
  if (paragraphs.length === 2 && (paragraphsAreNearDuplicates(paragraphs[0], paragraphs[1]) || paragraphsShareOpening(paragraphs[0], paragraphs[1]))) {
@@ -4945,13 +5474,14 @@ function MessageBubble({
4945
5474
  message,
4946
5475
  brandLogoUrl,
4947
5476
  bookingDisabled = false,
5477
+ showProvenance = false,
4948
5478
  bookingReadOnly = false,
4949
5479
  linkBaseUrl,
4950
5480
  offer,
4951
5481
  onBook
4952
5482
  }) {
4953
5483
  const resolvedLogoUrl = brandLogoUrl?.trim();
4954
- const [failedLogoUrl, setFailedLogoUrl] = (0, import_react10.useState)(null);
5484
+ const [failedLogoUrl, setFailedLogoUrl] = (0, import_react11.useState)(null);
4955
5485
  const showBrandLogo = Boolean(resolvedLogoUrl) && failedLogoUrl !== resolvedLogoUrl;
4956
5486
  const cards = message.role === "agent" ? extractToolCards(message.text) : [];
4957
5487
  const extractedOffers = cards.filter(
@@ -4966,7 +5496,34 @@ function MessageBubble({
4966
5496
  if (message.role === "visitor") {
4967
5497
  const visitorText = visitorMessageDisplayText(message).trim();
4968
5498
  if (!visitorText) return null;
4969
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "message-bubble__text", children: visitorText }) });
5499
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5500
+ "article",
5501
+ {
5502
+ className: "message-bubble message-bubble--visitor",
5503
+ "aria-label": "Message from visitor",
5504
+ children: [
5505
+ showProvenance ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__speaker", children: "You \xB7 Visitor" }) : null,
5506
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "message-bubble__text", children: visitorText })
5507
+ ]
5508
+ }
5509
+ );
5510
+ }
5511
+ if (message.role === "human") {
5512
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5513
+ "article",
5514
+ {
5515
+ className: "message-bubble message-bubble--human",
5516
+ "aria-label": `Message from ${message.representative.name}`,
5517
+ children: [
5518
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("strong", { className: "message-bubble__representative", children: [
5519
+ message.representative.name,
5520
+ " ",
5521
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Human representative" })
5522
+ ] }),
5523
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "message-bubble__text", children: message.text })
5524
+ ]
5525
+ }
5526
+ );
4970
5527
  }
4971
5528
  const citations = message.citations ?? [];
4972
5529
  const agentText = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__text", children: [
@@ -5003,40 +5560,48 @@ function MessageBubble({
5003
5560
  ] });
5004
5561
  if (!displayText && !message.searchResults?.length && offers.length === 0)
5005
5562
  return null;
5006
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
5007
- displayText || message.searchResults?.length ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__agent-row", children: [
5008
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5009
- "img",
5010
- {
5011
- src: resolvedLogoUrl,
5012
- alt: "",
5013
- onError: () => {
5014
- setFailedLogoUrl(resolvedLogoUrl ?? null);
5015
- }
5016
- }
5017
- ) }),
5018
- agentText
5019
- ] }) : agentText : null,
5020
- offers.map((nextOffer, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5021
- BookingCard,
5022
- {
5023
- disabled: bookingDisabled,
5024
- readOnly: bookingReadOnly,
5025
- offer: nextOffer,
5026
- onBook
5027
- },
5028
- `${bookingOfferIdentityKey(nextOffer)}-${index}`
5029
- ))
5030
- ] });
5563
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5564
+ "article",
5565
+ {
5566
+ className: "message-bubble message-bubble--agent",
5567
+ "aria-label": "Message from AI assistant",
5568
+ children: [
5569
+ showProvenance ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__speaker", children: "AI assistant" }) : null,
5570
+ displayText || message.searchResults?.length ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__agent-row", children: [
5571
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5572
+ "img",
5573
+ {
5574
+ src: resolvedLogoUrl,
5575
+ alt: "",
5576
+ onError: () => {
5577
+ setFailedLogoUrl(resolvedLogoUrl ?? null);
5578
+ }
5579
+ }
5580
+ ) }),
5581
+ agentText
5582
+ ] }) : agentText : null,
5583
+ offers.map((nextOffer, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5584
+ BookingCard,
5585
+ {
5586
+ disabled: bookingDisabled,
5587
+ readOnly: bookingReadOnly,
5588
+ offer: nextOffer,
5589
+ onBook
5590
+ },
5591
+ `${bookingOfferIdentityKey(nextOffer)}-${index}`
5592
+ ))
5593
+ ]
5594
+ }
5595
+ );
5031
5596
  }
5032
5597
 
5033
5598
  // src/react/components/MessageActions/MessageActions.tsx
5034
- var import_react11 = require("react");
5599
+ var import_react12 = require("react");
5035
5600
  var import_react_dom2 = require("react-dom");
5036
5601
 
5037
5602
  // src/react/lib/speech.ts
5038
- function toSpeechText(text2) {
5039
- let out = hideToolCardFences(text2);
5603
+ function toSpeechText(text3) {
5604
+ let out = hideToolCardFences(text3);
5040
5605
  out = out.replace(/```[\s\S]*?```/g, " ");
5041
5606
  out = out.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
5042
5607
  out = out.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1");
@@ -5371,12 +5936,12 @@ function StopIcon() {
5371
5936
  }
5372
5937
  ) });
5373
5938
  }
5374
- async function writeToClipboard(text2) {
5939
+ async function writeToClipboard(text3) {
5375
5940
  try {
5376
- await navigator.clipboard.writeText(text2);
5941
+ await navigator.clipboard.writeText(text3);
5377
5942
  } catch {
5378
5943
  const textarea = document.createElement("textarea");
5379
- textarea.value = text2;
5944
+ textarea.value = text3;
5380
5945
  textarea.style.position = "fixed";
5381
5946
  textarea.style.opacity = "0";
5382
5947
  document.body.appendChild(textarea);
@@ -5427,26 +5992,26 @@ function MessageActions({
5427
5992
  speechText
5428
5993
  }) {
5429
5994
  const menuPortalRoot = useAgentRailMenuPortalRoot();
5430
- const [copied, setCopied] = (0, import_react11.useState)(false);
5431
- const [rating, setRating] = (0, import_react11.useState)(null);
5432
- const [menuOpen, setMenuOpen] = (0, import_react11.useState)(false);
5433
- const [menuPosition, setMenuPosition] = (0, import_react11.useState)(null);
5434
- const [speaking, setSpeaking] = (0, import_react11.useState)(false);
5435
- const [speechSupported, setSpeechSupported] = (0, import_react11.useState)(null);
5436
- const copyTimerRef = (0, import_react11.useRef)(null);
5437
- const menuRef = (0, import_react11.useRef)(null);
5438
- const portaledMenuRef = (0, import_react11.useRef)(null);
5439
- const moreButtonRef = (0, import_react11.useRef)(null);
5995
+ const [copied, setCopied] = (0, import_react12.useState)(false);
5996
+ const [rating, setRating] = (0, import_react12.useState)(null);
5997
+ const [menuOpen, setMenuOpen] = (0, import_react12.useState)(false);
5998
+ const [menuPosition, setMenuPosition] = (0, import_react12.useState)(null);
5999
+ const [speaking, setSpeaking] = (0, import_react12.useState)(false);
6000
+ const [speechSupported, setSpeechSupported] = (0, import_react12.useState)(null);
6001
+ const copyTimerRef = (0, import_react12.useRef)(null);
6002
+ const menuRef = (0, import_react12.useRef)(null);
6003
+ const portaledMenuRef = (0, import_react12.useRef)(null);
6004
+ const moreButtonRef = (0, import_react12.useRef)(null);
5440
6005
  const answeredLabel = formatAnsweredAt(answeredAt ?? 0);
5441
6006
  const resolvedSpeechText = speechText ?? toSpeechText(copyText);
5442
6007
  const canOpenReceipt = Boolean(receiptSteps) && Boolean(onOpenReceipt);
5443
6008
  const readAloudEligible = Boolean(readAloud && resolvedSpeechText);
5444
6009
  const canReadAloud = readAloudEligible && speechSupported === true;
5445
6010
  const showMenu = canOpenReceipt || (speechSupported === null ? readAloudEligible : canReadAloud);
5446
- (0, import_react11.useLayoutEffect)(() => {
6011
+ (0, import_react12.useLayoutEffect)(() => {
5447
6012
  setSpeechSupported(isSpeechSupported());
5448
6013
  }, []);
5449
- (0, import_react11.useLayoutEffect)(() => {
6014
+ (0, import_react12.useLayoutEffect)(() => {
5450
6015
  if (!menuOpen || !moreButtonRef.current || !portaledMenuRef.current) {
5451
6016
  setMenuPosition(null);
5452
6017
  return;
@@ -5463,7 +6028,7 @@ function MessageActions({
5463
6028
  })
5464
6029
  );
5465
6030
  }, [menuOpen, menuPortalRoot]);
5466
- (0, import_react11.useEffect)(() => {
6031
+ (0, import_react12.useEffect)(() => {
5467
6032
  const unsubscribe = subscribeSpeechInterrupts(() => setSpeaking(false));
5468
6033
  return () => {
5469
6034
  unsubscribe();
@@ -5473,7 +6038,7 @@ function MessageActions({
5473
6038
  stopSpeech();
5474
6039
  };
5475
6040
  }, []);
5476
- (0, import_react11.useEffect)(() => {
6041
+ (0, import_react12.useEffect)(() => {
5477
6042
  if (!menuOpen) return;
5478
6043
  const handlePointerDown = (event) => {
5479
6044
  const target = event.target;
@@ -5642,7 +6207,7 @@ function MessageActions({
5642
6207
  }
5643
6208
 
5644
6209
  // src/react/components/HumanInputCard/HumanInputCard.tsx
5645
- var import_react13 = require("react");
6210
+ var import_react14 = require("react");
5646
6211
 
5647
6212
  // src/react/components/ConfirmationCard/ConfirmationCard.tsx
5648
6213
  var import_jsx_runtime9 = require("react/jsx-runtime");
@@ -5684,7 +6249,7 @@ function ConfirmationCard({
5684
6249
  }
5685
6250
 
5686
6251
  // src/react/components/ToolInputCard/ToolInputCard.tsx
5687
- var import_react12 = require("react");
6252
+ var import_react13 = require("react");
5688
6253
  var import_jsx_runtime10 = require("react/jsx-runtime");
5689
6254
  function isRecord5(value) {
5690
6255
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -5807,13 +6372,13 @@ function valuesMatch(left, right) {
5807
6372
  }
5808
6373
  function FieldDescription({
5809
6374
  field,
5810
- id
6375
+ id: id2
5811
6376
  }) {
5812
- return field.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id, className: "tool-input-card__description", children: field.description }) : null;
6377
+ return field.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: id2, className: "tool-input-card__description", children: field.description }) : null;
5813
6378
  }
5814
6379
  function ChoiceField({
5815
6380
  field,
5816
- id,
6381
+ id: id2,
5817
6382
  value,
5818
6383
  disabled,
5819
6384
  describedBy,
@@ -5831,7 +6396,7 @@ function ChoiceField({
5831
6396
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
5832
6397
  "select",
5833
6398
  {
5834
- id,
6399
+ id: id2,
5835
6400
  value: selectedIndex >= 0 ? String(selectedIndex) : "",
5836
6401
  disabled,
5837
6402
  required: field.required,
@@ -5854,7 +6419,7 @@ function ChoiceField({
5854
6419
  {
5855
6420
  className: "tool-input-card__choices",
5856
6421
  role: multiple ? "group" : "radiogroup",
5857
- "aria-labelledby": `${id}-label`,
6422
+ "aria-labelledby": `${id2}-label`,
5858
6423
  "aria-describedby": describedBy,
5859
6424
  "aria-invalid": Boolean(error),
5860
6425
  "aria-required": field.required,
@@ -5865,7 +6430,7 @@ function ChoiceField({
5865
6430
  "input",
5866
6431
  {
5867
6432
  type: multiple ? "checkbox" : "radio",
5868
- name: id,
6433
+ name: id2,
5869
6434
  value: String(index),
5870
6435
  checked,
5871
6436
  disabled,
@@ -5893,22 +6458,22 @@ function ToolField({
5893
6458
  disabled,
5894
6459
  error,
5895
6460
  field,
5896
- id,
6461
+ id: id2,
5897
6462
  value,
5898
6463
  onBlur,
5899
6464
  onChange
5900
6465
  }) {
5901
6466
  const describedBy = [
5902
- field.description ? `${id}-description` : "",
5903
- error ? `${id}-error` : ""
6467
+ field.description ? `${id2}-description` : "",
6468
+ error ? `${id2}-error` : ""
5904
6469
  ].filter(Boolean).join(" ");
5905
6470
  if (field.kind === "checkbox" || field.kind === "confirmation") {
5906
6471
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tool-input-card__field", children: [
5907
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "tool-input-card__check", htmlFor: id, children: [
6472
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "tool-input-card__check", htmlFor: id2, children: [
5908
6473
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5909
6474
  "input",
5910
6475
  {
5911
- id,
6476
+ id: id2,
5912
6477
  type: "checkbox",
5913
6478
  checked: value === true,
5914
6479
  disabled,
@@ -5920,18 +6485,18 @@ function ToolField({
5920
6485
  ),
5921
6486
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
5922
6487
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: field.label }),
5923
- field.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id}-description`, children: field.description }) : null
6488
+ field.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id2}-description`, children: field.description }) : null
5924
6489
  ] })
5925
6490
  ] }),
5926
- error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id}-error`, className: "tool-input-card__error", children: error }) : null
6491
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id2}-error`, className: "tool-input-card__error", children: error }) : null
5927
6492
  ] });
5928
6493
  }
5929
- const label = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { id: `${id}-label`, htmlFor: id, children: [
6494
+ const label = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { id: `${id2}-label`, htmlFor: id2, children: [
5930
6495
  field.label,
5931
6496
  field.required ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: " *" }) : null
5932
6497
  ] });
5933
6498
  const common = {
5934
- id,
6499
+ id: id2,
5935
6500
  disabled,
5936
6501
  required: field.required,
5937
6502
  "aria-describedby": describedBy || void 0,
@@ -5944,7 +6509,7 @@ function ToolField({
5944
6509
  ChoiceField,
5945
6510
  {
5946
6511
  field,
5947
- id,
6512
+ id: id2,
5948
6513
  value,
5949
6514
  disabled,
5950
6515
  describedBy: describedBy || void 0,
@@ -5981,7 +6546,7 @@ function ToolField({
5981
6546
  onChange: (event) => onChange(Number(event.target.value))
5982
6547
  }
5983
6548
  ),
5984
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("output", { htmlFor: id, children: numericValue })
6549
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("output", { htmlFor: id2, children: numericValue })
5985
6550
  ] });
5986
6551
  } else {
5987
6552
  const type = field.kind === "date-time" ? "datetime-local" : field.kind === "calendar" ? "date" : field.kind;
@@ -6005,9 +6570,9 @@ function ToolField({
6005
6570
  }
6006
6571
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tool-input-card__field", children: [
6007
6572
  label,
6008
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(FieldDescription, { field, id: `${id}-description` }),
6573
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(FieldDescription, { field, id: `${id2}-description` }),
6009
6574
  control,
6010
- error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id}-error`, className: "tool-input-card__error", children: error }) : null
6575
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id2}-error`, className: "tool-input-card__error", children: error }) : null
6011
6576
  ] });
6012
6577
  }
6013
6578
  function ToolInputCard({
@@ -6015,11 +6580,11 @@ function ToolInputCard({
6015
6580
  surface,
6016
6581
  onSubmit
6017
6582
  }) {
6018
- const [values, setValues] = (0, import_react12.useState)(
6583
+ const [values, setValues] = (0, import_react13.useState)(
6019
6584
  () => initialValues(surface)
6020
6585
  );
6021
- const [touched, setTouched] = (0, import_react12.useState)(() => /* @__PURE__ */ new Set());
6022
- const [submitted, setSubmitted] = (0, import_react12.useState)(false);
6586
+ const [touched, setTouched] = (0, import_react13.useState)(() => /* @__PURE__ */ new Set());
6587
+ const [submitted, setSubmitted] = (0, import_react13.useState)(false);
6023
6588
  const errors = Object.fromEntries(
6024
6589
  surface.fields.map((field) => [
6025
6590
  field.path,
@@ -6112,7 +6677,7 @@ function HumanInputCard({
6112
6677
  request,
6113
6678
  onRespond
6114
6679
  }) {
6115
- const [text2, setText] = (0, import_react13.useState)("");
6680
+ const [text3, setText] = (0, import_react14.useState)("");
6116
6681
  const options = request.options ?? [];
6117
6682
  const showText = request.display === "text" || request.allowFreeform && options.length === 0;
6118
6683
  if (request.ui) {
@@ -6137,7 +6702,7 @@ function HumanInputCard({
6137
6702
  }
6138
6703
  function submitText(event) {
6139
6704
  event.preventDefault();
6140
- const value = text2.trim();
6705
+ const value = text3.trim();
6141
6706
  if (!value || disabled) return;
6142
6707
  onRespond?.({ requestId: request.requestId, text: value });
6143
6708
  }
@@ -6155,12 +6720,12 @@ function HumanInputCard({
6155
6720
  "input",
6156
6721
  {
6157
6722
  id: `human-input-text-${request.requestId}`,
6158
- value: text2,
6723
+ value: text3,
6159
6724
  disabled,
6160
6725
  onChange: (event) => setText(event.target.value)
6161
6726
  }
6162
6727
  ),
6163
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "submit", disabled: disabled || !text2.trim(), children: "Send" })
6728
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "submit", disabled: disabled || !text3.trim(), children: "Send" })
6164
6729
  ] })
6165
6730
  ] }) : null,
6166
6731
  !showText && options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "human-input-card__unavailable", role: "status", children: "This request can\u2019t be answered here." }) : null
@@ -6170,7 +6735,7 @@ function HumanInputCard({
6170
6735
  }
6171
6736
 
6172
6737
  // src/react/components/LocationConsent/LocationConsent.tsx
6173
- var import_react14 = require("react");
6738
+ var import_react15 = require("react");
6174
6739
 
6175
6740
  // src/runtime/location-consent.ts
6176
6741
  function isLocationConsentRequest(request) {
@@ -6218,16 +6783,16 @@ function LocationConsent({
6218
6783
  request,
6219
6784
  onRespond
6220
6785
  }) {
6221
- const [preference, setPreference] = (0, import_react14.useState)("unset");
6222
- const [locating, setLocating] = (0, import_react14.useState)(false);
6223
- const [error, setError] = (0, import_react14.useState)(null);
6224
- const respond = (0, import_react14.useRef)(onRespond);
6225
- const answered = (0, import_react14.useRef)(null);
6786
+ const [preference, setPreference] = (0, import_react15.useState)("unset");
6787
+ const [locating, setLocating] = (0, import_react15.useState)(false);
6788
+ const [error, setError] = (0, import_react15.useState)(null);
6789
+ const respond = (0, import_react15.useRef)(onRespond);
6790
+ const answered = (0, import_react15.useRef)(null);
6226
6791
  const requestId = request?.requestId;
6227
- (0, import_react14.useEffect)(() => {
6792
+ (0, import_react15.useEffect)(() => {
6228
6793
  respond.current = onRespond;
6229
6794
  }, [onRespond]);
6230
- (0, import_react14.useEffect)(() => {
6795
+ (0, import_react15.useEffect)(() => {
6231
6796
  if (!requestId || preference === "unset" || answered.current === requestId || !respond.current)
6232
6797
  return;
6233
6798
  if (preference === "off") {
@@ -6569,6 +7134,7 @@ function ChevronDownIcon() {
6569
7134
  var DEFAULT_DISCLAIMER_LABEL = "Agent can make mistakes. Check important info.";
6570
7135
  function AgentRail({
6571
7136
  state,
7137
+ handoff,
6572
7138
  theme,
6573
7139
  colorScheme = "auto",
6574
7140
  brandLabel = "",
@@ -6594,44 +7160,51 @@ function AgentRail({
6594
7160
  onInputResponse,
6595
7161
  onToolInput
6596
7162
  }) {
6597
- const railRef = (0, import_react15.useRef)(null);
6598
- const overlayRef = (0, import_react15.useRef)(null);
6599
- const transcriptRef = (0, import_react15.useRef)(null);
6600
- const responseRef = (0, import_react15.useRef)(null);
6601
- const threadRef = (0, import_react15.useRef)(null);
6602
- const lastScrolledVisitorIdRef = (0, import_react15.useRef)(void 0);
6603
- const pinnedToBottomRef = (0, import_react15.useRef)(true);
6604
- const smoothScrollToLatestRef = (0, import_react15.useRef)(false);
6605
- const lockedTranscriptScrollTopRef = (0, import_react15.useRef)(null);
6606
- const [showJumpToLatest, setShowJumpToLatest] = (0, import_react15.useState)(false);
6607
- const [receiptOpen, setReceiptOpen] = (0, import_react15.useState)(false);
7163
+ const railRef = (0, import_react16.useRef)(null);
7164
+ const overlayRef = (0, import_react16.useRef)(null);
7165
+ const transcriptRef = (0, import_react16.useRef)(null);
7166
+ const responseRef = (0, import_react16.useRef)(null);
7167
+ const threadRef = (0, import_react16.useRef)(null);
7168
+ const lastScrolledVisitorIdRef = (0, import_react16.useRef)(void 0);
7169
+ const pinnedToBottomRef = (0, import_react16.useRef)(true);
7170
+ const smoothScrollToLatestRef = (0, import_react16.useRef)(false);
7171
+ const lockedTranscriptScrollTopRef = (0, import_react16.useRef)(null);
7172
+ const [showJumpToLatest, setShowJumpToLatest] = (0, import_react16.useState)(false);
7173
+ const [receiptOpen, setReceiptOpen] = (0, import_react16.useState)(false);
6608
7174
  const resolvedBrandLabel = brandLabel.trim();
6609
7175
  const resolvedBrandLogoUrl = brandLogoUrl?.trim();
6610
7176
  const resolvedDisclaimerLabel = disclaimerLabel === void 0 ? DEFAULT_DISCLAIMER_LABEL : disclaimerLabel;
6611
- const [failedLogoUrl, setFailedLogoUrl] = (0, import_react15.useState)(null);
7177
+ const [failedLogoUrl, setFailedLogoUrl] = (0, import_react16.useState)(null);
6612
7178
  const showBrandLogo = Boolean(resolvedBrandLogoUrl) && failedLogoUrl !== resolvedBrandLogoUrl;
6613
7179
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
6614
7180
  const railStyle = agentThemeStyle(theme, resolvedColorScheme);
6615
- const locationRequest = state.pendingInputs?.find(isLocationConsentRequest);
7181
+ const handoffActive = Boolean(handoff?.blocksAI);
7182
+ const handoffStatus = handoff?.page?.status;
7183
+ const locationRequest = handoffActive ? void 0 : state.pendingInputs?.find(isLocationConsentRequest);
6616
7184
  const pendingInputRequests = (state.pendingInputs ?? []).filter(
6617
- (request) => !isLocationConsentRequest(request) && shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
7185
+ (request) => !handoffActive && !isLocationConsentRequest(request) && shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
6618
7186
  );
6619
7187
  const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming" || state.phase === "waiting-input" && (pendingInputRequests.length > 0 || Boolean(locationRequest));
6620
- const semanticSurfaceDisabled = isBusy && state.phase !== "waiting-input";
7188
+ const semanticSurfaceDisabled = handoffActive || isBusy && state.phase !== "waiting-input";
6621
7189
  const visitorToolResults = (state.toolResults ?? []).filter(
6622
7190
  isRenderableVisitorToolResult
6623
7191
  );
6624
7192
  const activeVisitorToolInput = [...visitorToolResults].reverse().find((result) => result.kind === "input");
6625
7193
  const visibleVisitorToolResults = activeVisitorToolInput ? [activeVisitorToolInput] : visitorToolResults;
6626
- const activityActive = state.toolSteps.some((step) => step.state === "active");
7194
+ const activityActive = state.toolSteps.some(
7195
+ (step) => step.state === "active"
7196
+ );
6627
7197
  const showActivity = state.toolSteps.length > 0 && pendingInputRequests.length === 0 && !state.pendingOffer && (activityActive || isBusy && !state.streamingText);
6628
- const activitySteps = activityActive || !isBusy || state.streamingText ? state.toolSteps : [...state.toolSteps, {
6629
- id: "preparing-answer",
6630
- kind: "tool",
6631
- label: "Preparing your answer",
6632
- detail: "Preparing your answer",
6633
- state: "active"
6634
- }];
7198
+ const activitySteps = activityActive || !isBusy || state.streamingText ? state.toolSteps : [
7199
+ ...state.toolSteps,
7200
+ {
7201
+ id: "preparing-answer",
7202
+ kind: "tool",
7203
+ label: "Preparing your answer",
7204
+ detail: "Preparing your answer",
7205
+ state: "active"
7206
+ }
7207
+ ];
6635
7208
  const hasVisitorMessages2 = state.messages.some(
6636
7209
  (message) => message.role === "visitor"
6637
7210
  );
@@ -6660,7 +7233,7 @@ function AgentRail({
6660
7233
  }
6661
7234
  }
6662
7235
  const lastIsAgent = lastMessage?.role === "agent";
6663
- const showMessageActions = state.phase === "complete" && lastIsAgent && hasVisitorMessages2 && Boolean(hideToolCardFences(lastMessage.text).trim());
7236
+ const showMessageActions = !handoffActive && state.phase === "complete" && lastIsAgent && hasVisitorMessages2 && Boolean(hideToolCardFences(lastMessage.text).trim());
6664
7237
  const streamingMessage = state.streamingText && !lastIsAgent ? {
6665
7238
  createdAt: 0,
6666
7239
  id: "streaming-response",
@@ -6702,7 +7275,7 @@ function AgentRail({
6702
7275
  ...visibleVisitorToolResults
6703
7276
  ].reverse().find((result) => result.kind !== "input")?.id;
6704
7277
  const receiptSteps = answerReceipt && state.toolSteps.length > 0 ? state.toolSteps : void 0;
6705
- (0, import_react15.useEffect)(() => {
7278
+ (0, import_react16.useEffect)(() => {
6706
7279
  if (state.phase !== "complete") {
6707
7280
  setReceiptOpen(false);
6708
7281
  }
@@ -6724,7 +7297,7 @@ function AgentRail({
6724
7297
  onFollowUpSelect?.(label);
6725
7298
  }
6726
7299
  const latestVisitorId = visibleMessages[lastVisitorIndex]?.id;
6727
- (0, import_react15.useEffect)(() => {
7300
+ (0, import_react16.useEffect)(() => {
6728
7301
  const node = transcriptRef.current;
6729
7302
  if (!node) return;
6730
7303
  if (latestVisitorId !== lastScrolledVisitorIdRef.current) {
@@ -6754,7 +7327,7 @@ function AgentRail({
6754
7327
  state.followUps,
6755
7328
  state.journey
6756
7329
  ]);
6757
- (0, import_react15.useEffect)(() => {
7330
+ (0, import_react16.useEffect)(() => {
6758
7331
  const node = transcriptRef.current;
6759
7332
  if (!node) return;
6760
7333
  const handleScroll = () => {
@@ -6768,7 +7341,7 @@ function AgentRail({
6768
7341
  handleScroll();
6769
7342
  return () => node.removeEventListener("scroll", handleScroll);
6770
7343
  }, []);
6771
- (0, import_react15.useEffect)(() => {
7344
+ (0, import_react16.useEffect)(() => {
6772
7345
  if (!receiptOpen) {
6773
7346
  lockedTranscriptScrollTopRef.current = null;
6774
7347
  return;
@@ -6820,249 +7393,302 @@ function AgentRail({
6820
7393
  autoFocus: mobileFullscreen || expanded,
6821
7394
  role: mobileFullscreen || expanded ? "dialog" : void 0,
6822
7395
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
6823
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
6824
- AgentRailOverlayContext.Provider,
6825
- {
6826
- value: { railRef, overlayRef },
6827
- children: [
6828
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__surface", inert: receiptOpen || void 0, children: [
6829
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("header", { className: "agent-rail__header", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__brand-row", children: [
6830
- onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6831
- "button",
6832
- {
6833
- type: "button",
6834
- className: "agent-rail__collapse",
6835
- "aria-label": "Collapse assist",
6836
- onClick: onCollapse,
6837
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MinimizeIcon, {})
6838
- }
6839
- ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6840
- "button",
6841
- {
6842
- type: "button",
6843
- className: "agent-rail__close",
6844
- "aria-label": "Close agent",
6845
- onClick: onClose,
6846
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CloseIcon2, {})
7396
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(AgentRailOverlayContext.Provider, { value: { railRef, overlayRef }, children: [
7397
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__surface", inert: receiptOpen || void 0, children: [
7398
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("header", { className: "agent-rail__header", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__brand-row", children: [
7399
+ onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7400
+ "button",
7401
+ {
7402
+ type: "button",
7403
+ className: "agent-rail__collapse",
7404
+ "aria-label": "Collapse assist",
7405
+ onClick: onCollapse,
7406
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MinimizeIcon, {})
7407
+ }
7408
+ ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7409
+ "button",
7410
+ {
7411
+ type: "button",
7412
+ className: "agent-rail__close",
7413
+ "aria-label": "Close agent",
7414
+ onClick: onClose,
7415
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CloseIcon2, {})
7416
+ }
7417
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
7418
+ resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "agent-rail__identity", children: [
7419
+ showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7420
+ "img",
7421
+ {
7422
+ className: "agent-rail__brand-logo",
7423
+ src: resolvedBrandLogoUrl,
7424
+ alt: "",
7425
+ onError: () => {
7426
+ setFailedLogoUrl(resolvedBrandLogoUrl ?? null);
6847
7427
  }
6848
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
6849
- resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "agent-rail__identity", children: [
6850
- showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6851
- "img",
6852
- {
6853
- className: "agent-rail__brand-logo",
6854
- src: resolvedBrandLogoUrl,
6855
- alt: "",
6856
- onError: () => {
6857
- setFailedLogoUrl(resolvedBrandLogoUrl ?? null);
6858
- }
6859
- }
6860
- ) }) : null,
6861
- resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
6862
- ] }) : null,
6863
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "agent-rail__actions", children: [
6864
- onReset ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6865
- "button",
6866
- {
6867
- type: "button",
6868
- className: "agent-rail__new-chat",
6869
- "aria-label": "Start a new conversation",
6870
- disabled: !hasVisitorMessages2,
6871
- onClick: handleReset,
6872
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(NewChatIcon, {})
6873
- }
6874
- ) : null,
6875
- onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6876
- "button",
7428
+ }
7429
+ ) }) : null,
7430
+ resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
7431
+ ] }) : null,
7432
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "agent-rail__actions", children: [
7433
+ onReset ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7434
+ "button",
7435
+ {
7436
+ type: "button",
7437
+ className: "agent-rail__new-chat",
7438
+ "aria-label": "Start a new conversation",
7439
+ disabled: !hasVisitorMessages2 || handoffActive && !handoff?.canReset,
7440
+ onClick: handleReset,
7441
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(NewChatIcon, {})
7442
+ }
7443
+ ) : null,
7444
+ onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7445
+ "button",
7446
+ {
7447
+ type: "button",
7448
+ className: "agent-rail__expand",
7449
+ "aria-label": expanded ? "Exit full screen" : "Open full screen",
7450
+ onClick: onExpandToggle,
7451
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ExpandIcon, {})
7452
+ }
7453
+ ) : null
7454
+ ] })
7455
+ ] }) }),
7456
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { ref: threadRef, className: "agent-rail__thread", children: [
7457
+ !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
7458
+ greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7459
+ MessageBubble,
7460
+ {
7461
+ showProvenance: Boolean(
7462
+ handoff?.page?.enabled || handoffActive
7463
+ ),
7464
+ message: greeting,
7465
+ bookingDisabled: isBusy || handoffActive,
7466
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
7467
+ onBook
7468
+ }
7469
+ ) : null,
7470
+ showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7471
+ FollowUpChips,
7472
+ {
7473
+ suggestions: state.followUps,
7474
+ disabled: isBusy || handoffActive,
7475
+ label: "Start here",
7476
+ onSelect: (suggestion) => handleFollowUpSelect(suggestion.label)
7477
+ }
7478
+ ) }) : null,
7479
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7480
+ AgentActivityBubble,
7481
+ {
7482
+ brandLabel: resolvedBrandLabel,
7483
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
7484
+ failed: state.phase === "error",
7485
+ steps: activitySteps
7486
+ }
7487
+ ) : null,
7488
+ visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7489
+ VisitorToolResultView,
7490
+ {
7491
+ result,
7492
+ disabled: semanticSurfaceDisabled,
7493
+ onToolInput
7494
+ },
7495
+ result.id
7496
+ )),
7497
+ pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7498
+ HumanInputCard,
7499
+ {
7500
+ request,
7501
+ onRespond: onInputResponse
7502
+ },
7503
+ request.requestId
7504
+ ))
7505
+ ] }) : null,
7506
+ transcriptMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
7507
+ "div",
7508
+ {
7509
+ ref: index === lastVisitorIndex + 1 ? responseRef : void 0,
7510
+ className: "agent-rail__turn-block",
7511
+ children: [
7512
+ message.role === "agent" ? message.toolResults?.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7513
+ VisitorToolResultView,
6877
7514
  {
6878
- type: "button",
6879
- className: "agent-rail__expand",
6880
- "aria-label": expanded ? "Exit full screen" : "Open full screen",
6881
- onClick: onExpandToggle,
6882
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ExpandIcon, {})
6883
- }
6884
- ) : null
6885
- ] })
6886
- ] }) }),
6887
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { ref: threadRef, className: "agent-rail__thread", children: [
6888
- !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
6889
- greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7515
+ result
7516
+ },
7517
+ result.id
7518
+ )) : null,
7519
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6890
7520
  MessageBubble,
6891
7521
  {
6892
- message: greeting,
6893
- bookingDisabled: isBusy,
7522
+ showProvenance: Boolean(
7523
+ handoff?.page?.enabled || handoffActive
7524
+ ),
7525
+ message,
7526
+ bookingDisabled: isBusy || handoffActive,
6894
7527
  brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
7528
+ offer: index === transcriptMessages.length - 1 ? state.pendingOffer : void 0,
6895
7529
  onBook
6896
7530
  }
6897
- ) : null,
6898
- showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6899
- FollowUpChips,
7531
+ ),
7532
+ index === lastAgentIndex && showMessageActions && message.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7533
+ MessageActions,
6900
7534
  {
6901
- suggestions: state.followUps,
6902
- disabled: isBusy,
6903
- label: "Start here",
6904
- onSelect: (suggestion) => handleFollowUpSelect(suggestion.label)
6905
- }
6906
- ) }) : null,
6907
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6908
- AgentActivityBubble,
6909
- {
6910
- brandLabel: resolvedBrandLabel,
6911
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6912
- failed: state.phase === "error",
6913
- steps: activitySteps
7535
+ answeredAt: message.createdAt,
7536
+ copyText: hideToolCardFences(message.text).trim() || message.text,
7537
+ readAloud,
7538
+ receiptSteps,
7539
+ onOpenReceipt: receiptSteps ? openReceipt : void 0,
7540
+ onRegenerate: onRegenerate ? handleRegenerate : void 0,
7541
+ onFeedback: onFeedback ? (rating) => onFeedback(rating, message) : void 0
6914
7542
  }
6915
7543
  ) : null,
6916
- visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6917
- VisitorToolResultView,
6918
- {
6919
- result,
6920
- disabled: semanticSurfaceDisabled,
6921
- onToolInput
6922
- },
6923
- result.id
6924
- )),
6925
- pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6926
- HumanInputCard,
6927
- {
6928
- request,
6929
- onRespond: onInputResponse
6930
- },
6931
- request.requestId
6932
- ))
6933
- ] }) : null,
6934
- transcriptMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
6935
- "div",
6936
- {
6937
- ref: index === lastVisitorIndex + 1 ? responseRef : void 0,
6938
- className: "agent-rail__turn-block",
6939
- children: [
6940
- message.role === "agent" ? message.toolResults?.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VisitorToolResultView, { result }, result.id)) : null,
6941
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6942
- MessageBubble,
6943
- {
6944
- message,
6945
- bookingDisabled: isBusy,
6946
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6947
- offer: index === transcriptMessages.length - 1 ? state.pendingOffer : void 0,
6948
- onBook
6949
- }
6950
- ),
6951
- index === lastAgentIndex && showMessageActions && message.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6952
- MessageActions,
6953
- {
6954
- answeredAt: message.createdAt,
6955
- copyText: hideToolCardFences(message.text).trim() || message.text,
6956
- readAloud,
6957
- receiptSteps,
6958
- onOpenReceipt: receiptSteps ? openReceipt : void 0,
6959
- onRegenerate: onRegenerate ? handleRegenerate : void 0,
6960
- onFeedback: onFeedback ? (rating) => onFeedback(rating, message) : void 0
6961
- }
6962
- ) : null,
6963
- index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
6964
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6965
- AgentActivityBubble,
6966
- {
6967
- brandLabel: resolvedBrandLabel,
6968
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6969
- failed: state.phase === "error",
6970
- steps: activitySteps
6971
- }
6972
- ) : null,
6973
- visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6974
- VisitorToolResultView,
6975
- {
6976
- result,
6977
- disabled: semanticSurfaceDisabled,
6978
- onToolInput
6979
- },
6980
- result.id
6981
- )),
6982
- pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6983
- HumanInputCard,
6984
- {
6985
- request,
6986
- onRespond: onInputResponse
6987
- },
6988
- request.requestId
6989
- ))
6990
- ] }) : null
6991
- ]
6992
- },
6993
- message.role === "agent" && transcriptMessages[index - 1]?.role === "visitor" ? `answer-${transcriptMessages[index - 1]?.id}` : message.id
6994
- )),
6995
- waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(BookingCardLoader, {}) : null,
6996
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
6997
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
6998
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: "Something went wrong" }),
6999
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: state.error })
7000
- ] }),
7001
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
7002
- ] }) : null
7003
- ] }) }),
7004
- showDisclaimerLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "agent-rail__disclaimer", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: disclaimerLink ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7005
- "a",
7544
+ index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
7545
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7546
+ AgentActivityBubble,
7547
+ {
7548
+ brandLabel: resolvedBrandLabel,
7549
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
7550
+ failed: state.phase === "error",
7551
+ steps: activitySteps
7552
+ }
7553
+ ) : null,
7554
+ visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7555
+ VisitorToolResultView,
7556
+ {
7557
+ result,
7558
+ disabled: semanticSurfaceDisabled,
7559
+ onToolInput
7560
+ },
7561
+ result.id
7562
+ )),
7563
+ pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7564
+ HumanInputCard,
7565
+ {
7566
+ request,
7567
+ onRespond: onInputResponse
7568
+ },
7569
+ request.requestId
7570
+ ))
7571
+ ] }) : null
7572
+ ]
7573
+ },
7574
+ message.role === "agent" && transcriptMessages[index - 1]?.role === "visitor" ? `answer-${transcriptMessages[index - 1]?.id}` : message.id
7575
+ )),
7576
+ waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(BookingCardLoader, {}) : null,
7577
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
7578
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
7579
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: "Something went wrong" }),
7580
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: state.error })
7581
+ ] }),
7582
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
7583
+ ] }) : null
7584
+ ] }) }),
7585
+ showDisclaimerLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "agent-rail__disclaimer", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: disclaimerLink ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7586
+ "a",
7587
+ {
7588
+ href: disclaimerLink,
7589
+ rel: "noopener noreferrer",
7590
+ target: "_blank",
7591
+ children: resolvedDisclaimerLabel
7592
+ }
7593
+ ) : resolvedDisclaimerLabel }) }) : null,
7594
+ handoff && (handoff.page?.enabled || handoffActive) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__handoff", children: [
7595
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "status", "aria-live": "polite", children: !handoff.page ? "Checking conversation\u2026" : handoffStatus === "human" ? "Connected with support" : handoffStatus === "queued" ? "Waiting for a representative" : handoffStatus === "requesting" ? "Connecting you to support\u2026" : handoffStatus === "resolved" ? "Your conversation with support has ended" : handoffStatus === "failed" ? "We couldn\u2019t connect you to support" : "Need a person?" }),
7596
+ handoffStatus === "ai" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7597
+ "button",
7598
+ {
7599
+ type: "button",
7600
+ disabled: handoff.busy || handoff.hasPending || isBusy,
7601
+ onClick: () => void handoff.start(),
7602
+ children: "Talk to a person"
7603
+ }
7604
+ ) : null,
7605
+ handoffStatus === "resolved" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7606
+ "button",
7607
+ {
7608
+ type: "button",
7609
+ disabled: handoff.busy || handoff.hasPending,
7610
+ onClick: () => void handoff.returnToAI(),
7611
+ children: "Return to AI"
7612
+ }
7613
+ ) : null,
7614
+ handoffStatus === "failed" && onReset ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7615
+ "button",
7616
+ {
7617
+ type: "button",
7618
+ disabled: !handoff.canReset,
7619
+ onClick: handleReset,
7620
+ children: "Start a new conversation"
7621
+ }
7622
+ ) : null,
7623
+ handoff.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { role: "alert", children: [
7624
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: handoff.error }),
7625
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7626
+ "button",
7006
7627
  {
7007
- href: disclaimerLink,
7008
- rel: "noopener noreferrer",
7009
- target: "_blank",
7010
- children: resolvedDisclaimerLabel
7628
+ type: "button",
7629
+ disabled: handoff.busy,
7630
+ onClick: () => void handoff.retry(),
7631
+ children: "Retry"
7011
7632
  }
7012
- ) : resolvedDisclaimerLabel }) }) : null,
7013
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
7014
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7015
- LocationConsent,
7016
- {
7017
- request: locationRequest,
7018
- onRespond: onInputResponse
7019
- },
7020
- state.messages.find((message) => message.role === "visitor")?.id ?? "empty"
7021
- ),
7022
- showJumpToLatest ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7023
- "button",
7024
- {
7025
- type: "button",
7026
- className: "agent-rail__jump-to-latest",
7027
- "aria-label": "Jump to the latest message",
7028
- title: "Jump to the latest message",
7029
- onClick: scrollToLatest,
7030
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChevronDownIcon, {})
7031
- }
7032
- ) : null,
7033
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7034
- Composer,
7035
- {
7036
- variant: expanded || mobileFullscreen ? "dock" : "default",
7037
- disabled: isBusy,
7038
- form: composerForm && lastMessage ? { ...composerForm, id: `${lastMessage.id}:${composerForm.id}` } : null,
7039
- allowFormResume: !state.pendingOffer && !waitingForBooking && !hasPendingConfirmation,
7040
- placeholder: composerPlaceholder,
7041
- onSubmit: handleSubmit
7042
- },
7043
- `${state.messages.find((message) => message.role === "visitor")?.id ?? "empty"}:${latestResultId ?? ""}`
7044
- ),
7045
- poweredByLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: poweredByLabel }) }) }) : null
7046
- ] })
7047
- ] }),
7048
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: overlayRef, className: "agent-rail__overlay" }),
7049
- receiptOpen && receiptSteps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7050
- AnswerReceiptDialog,
7633
+ )
7634
+ ] }) : null
7635
+ ] }) : null,
7636
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
7637
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7638
+ LocationConsent,
7639
+ {
7640
+ request: locationRequest,
7641
+ onRespond: onInputResponse
7642
+ },
7643
+ state.messages.find((message) => message.role === "visitor")?.id ?? "empty"
7644
+ ),
7645
+ showJumpToLatest ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7646
+ "button",
7051
7647
  {
7052
- brandLabel: resolvedBrandLabel,
7053
- onClose: () => setReceiptOpen(false),
7054
- steps: receiptSteps
7648
+ type: "button",
7649
+ className: "agent-rail__jump-to-latest",
7650
+ "aria-label": "Jump to the latest message",
7651
+ title: "Jump to the latest message",
7652
+ onClick: scrollToLatest,
7653
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChevronDownIcon, {})
7055
7654
  }
7056
- ) : null
7057
- ]
7058
- }
7059
- )
7655
+ ) : null,
7656
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7657
+ Composer,
7658
+ {
7659
+ variant: expanded || mobileFullscreen ? "dock" : "default",
7660
+ disabled: isBusy || Boolean(handoff?.busy || handoff?.hasPending) || handoffActive && !["requesting", "queued", "human"].includes(
7661
+ handoffStatus ?? ""
7662
+ ),
7663
+ form: !handoffActive && composerForm && lastMessage ? {
7664
+ ...composerForm,
7665
+ id: `${lastMessage.id}:${composerForm.id}`
7666
+ } : null,
7667
+ allowFormResume: !handoffActive && !state.pendingOffer && !waitingForBooking && !hasPendingConfirmation,
7668
+ placeholder: handoffActive ? "Message support\u2026" : composerPlaceholder,
7669
+ onSubmit: handleSubmit
7670
+ },
7671
+ `${state.messages.find((message) => message.role === "visitor")?.id ?? "empty"}:${latestResultId ?? ""}`
7672
+ ),
7673
+ poweredByLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: poweredByLabel }) }) }) : null
7674
+ ] })
7675
+ ] }),
7676
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: overlayRef, className: "agent-rail__overlay" }),
7677
+ receiptOpen && receiptSteps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7678
+ AnswerReceiptDialog,
7679
+ {
7680
+ brandLabel: resolvedBrandLabel,
7681
+ onClose: () => setReceiptOpen(false),
7682
+ steps: receiptSteps
7683
+ }
7684
+ ) : null
7685
+ ] })
7060
7686
  }
7061
7687
  );
7062
7688
  }
7063
7689
 
7064
7690
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
7065
- var import_react16 = require("react");
7691
+ var import_react17 = require("react");
7066
7692
  var import_jsx_runtime19 = require("react/jsx-runtime");
7067
7693
  function ChatSparkIcon() {
7068
7694
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
@@ -7143,8 +7769,8 @@ function TabMarkIcon({
7143
7769
  }) {
7144
7770
  const custom = customIconUrl?.trim();
7145
7771
  const logo = logoUrl?.trim();
7146
- const [customFailed, setCustomFailed] = (0, import_react16.useState)(false);
7147
- const [logoFailed, setLogoFailed] = (0, import_react16.useState)(false);
7772
+ const [customFailed, setCustomFailed] = (0, import_react17.useState)(false);
7773
+ const [logoFailed, setLogoFailed] = (0, import_react17.useState)(false);
7148
7774
  if (custom && !customFailed) {
7149
7775
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7150
7776
  "img",
@@ -7332,8 +7958,8 @@ function findPrecedingVisitorText(messages, agentMessageId) {
7332
7958
  }
7333
7959
  return void 0;
7334
7960
  }
7335
- function normalizeAgentFeedbackAnswerText(text2) {
7336
- const normalized = text2.replace(/\s+/g, " ").trim();
7961
+ function normalizeAgentFeedbackAnswerText(text3) {
7962
+ const normalized = text3.replace(/\s+/g, " ").trim();
7337
7963
  if (!normalized) return void 0;
7338
7964
  return normalized.length > 4e3 ? `${normalized.slice(0, 3997)}...` : normalized;
7339
7965
  }
@@ -7393,9 +8019,9 @@ function AgentWidget({
7393
8019
  }) {
7394
8020
  const isMobile = useIsMobile();
7395
8021
  const placement = normalizeAgentPlacement(placementInput);
7396
- const railSlotRef = (0, import_react17.useRef)(null);
7397
- const [railCollapsed, setRailCollapsed] = (0, import_react17.useState)(defaultCollapsed);
7398
- const [railExpanded, setRailExpanded] = (0, import_react17.useState)(false);
8022
+ const railSlotRef = (0, import_react18.useRef)(null);
8023
+ const [railCollapsed, setRailCollapsed] = (0, import_react18.useState)(defaultCollapsed);
8024
+ const [railExpanded, setRailExpanded] = (0, import_react18.useState)(false);
7399
8025
  const pageShiftActive = shouldApplyPageShift({
7400
8026
  pageShift,
7401
8027
  isMobile,
@@ -7448,7 +8074,7 @@ function AgentWidget({
7448
8074
  } : {},
7449
8075
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
7450
8076
  };
7451
- (0, import_react17.useEffect)(() => {
8077
+ (0, import_react18.useEffect)(() => {
7452
8078
  if (!registerPanelController) return;
7453
8079
  registerAgentPanelController(customerId, {
7454
8080
  open: () => setRailCollapsed(false),
@@ -7483,7 +8109,7 @@ function AgentWidget({
7483
8109
  })
7484
8110
  );
7485
8111
  }
7486
- (0, import_react17.useEffect)(() => {
8112
+ (0, import_react18.useEffect)(() => {
7487
8113
  if (railCollapsed) return;
7488
8114
  const handleKeyDown = (event) => {
7489
8115
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -7765,7 +8391,7 @@ function mountAgent(input) {
7765
8391
  const mountTarget = resolveMountHost(manifest, input.script ?? null);
7766
8392
  const host = createHost(manifest.customerId);
7767
8393
  mountTarget.append(host);
7768
- const root = (0, import_client5.createRoot)(host);
8394
+ const root = (0, import_client7.createRoot)(host);
7769
8395
  root.render(/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AgentWidget2, { manifest }));
7770
8396
  const handle = {
7771
8397
  customerId: manifest.customerId,
@@ -7792,9 +8418,9 @@ function mountAgent(input) {
7792
8418
  return handle;
7793
8419
  }
7794
8420
  function unmountAgent(customerId) {
7795
- const id = customerId ?? latestCustomerId;
7796
- if (!id) return false;
7797
- const handle = mountedHandles.get(id);
8421
+ const id2 = customerId ?? latestCustomerId;
8422
+ if (!id2) return false;
8423
+ const handle = mountedHandles.get(id2);
7798
8424
  if (!handle) return false;
7799
8425
  handle.unmount();
7800
8426
  return true;