@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/react.cjs CHANGED
@@ -46,7 +46,7 @@ __export(react_exports, {
46
46
  module.exports = __toCommonJS(react_exports);
47
47
 
48
48
  // src/react/components/AgentWidget/AgentWidget.tsx
49
- var import_react17 = require("react");
49
+ var import_react18 = require("react");
50
50
 
51
51
  // src/react/page-shift.ts
52
52
  var import_react = require("react");
@@ -128,8 +128,168 @@ function usePageShift(input) {
128
128
  }, [active, railSlotRef]);
129
129
  }
130
130
 
131
- // src/react/hooks/useAgentChat.ts
131
+ // src/react/hooks/useAgentHandoff.ts
132
+ var import_client = require("eve/client");
132
133
  var import_react2 = require("react");
134
+ function useAgentHandoff(options) {
135
+ const [page, setPage] = (0, import_react2.useState)(null);
136
+ const [busy, setBusy] = (0, import_react2.useState)(false);
137
+ const [rejected, setRejected] = (0, import_react2.useState)(false);
138
+ const [error, setError] = (0, import_react2.useState)(null);
139
+ const callbacks = (0, import_react2.useRef)(options);
140
+ callbacks.current = options;
141
+ const pending = (0, import_react2.useRef)(null);
142
+ const inFlight = (0, import_react2.useRef)(null);
143
+ const pageRef = (0, import_react2.useRef)(null);
144
+ const refreshRef = (0, import_react2.useRef)(null);
145
+ const generation = (0, import_react2.useRef)(0);
146
+ (0, import_react2.useEffect)(() => {
147
+ const token = ++generation.current;
148
+ const controller = new AbortController();
149
+ let timer;
150
+ let reading = null;
151
+ let cursor = 0;
152
+ pageRef.current = null;
153
+ pending.current = null;
154
+ inFlight.current?.abort();
155
+ inFlight.current = null;
156
+ setRejected(false);
157
+ setPage(null);
158
+ setError(null);
159
+ setBusy(false);
160
+ if (!options.enabled || !options.sessionId) return;
161
+ const active = () => !controller.signal.aborted && generation.current === token;
162
+ const refresh = () => {
163
+ reading ??= (async () => {
164
+ do {
165
+ const next = await options.client.handoff.read({
166
+ after: cursor,
167
+ signal: AbortSignal.any([
168
+ controller.signal,
169
+ AbortSignal.timeout(15e3)
170
+ ])
171
+ });
172
+ if (!active()) return;
173
+ callbacks.current.onEvents(next.events);
174
+ if (next.status !== "ai") callbacks.current.onOwnership();
175
+ cursor = next.cursor;
176
+ pageRef.current = next;
177
+ setPage(next);
178
+ setRejected(false);
179
+ if (!pending.current) setError(null);
180
+ if (!next.hasMore) break;
181
+ } while (active());
182
+ })().finally(() => {
183
+ reading = null;
184
+ });
185
+ return reading;
186
+ };
187
+ refreshRef.current = refresh;
188
+ const poll = async () => {
189
+ try {
190
+ await refresh();
191
+ } catch (cause) {
192
+ if (active()) {
193
+ const denied = cause instanceof import_client.ClientError && cause.status === 403;
194
+ setRejected(denied);
195
+ setError(
196
+ denied ? "This preview has changed. Restart the preview chat to test the current version." : "Reconnecting to your conversation\u2026"
197
+ );
198
+ }
199
+ } finally {
200
+ if (active()) timer = setTimeout(() => void poll(), 1500);
201
+ }
202
+ };
203
+ void poll();
204
+ return () => {
205
+ controller.abort();
206
+ inFlight.current?.abort();
207
+ clearTimeout(timer);
208
+ refreshRef.current = null;
209
+ };
210
+ }, [options.client, options.enabled, options.sessionId]);
211
+ async function execute(operation) {
212
+ if (inFlight.current) return;
213
+ const controller = new AbortController();
214
+ inFlight.current = controller;
215
+ const signal = AbortSignal.any([
216
+ controller.signal,
217
+ AbortSignal.timeout(15e3)
218
+ ]);
219
+ const token = generation.current;
220
+ pending.current = operation;
221
+ setBusy(true);
222
+ setError(null);
223
+ try {
224
+ if (operation.type === "start")
225
+ await options.client.handoff.start(operation.operationId, signal);
226
+ else if (operation.type === "message") {
227
+ const { type: _, ...command } = operation;
228
+ await options.client.handoff.send(command, signal);
229
+ } else {
230
+ const { type: _, ...command } = operation;
231
+ await options.client.handoff.returnToAI(command, signal);
232
+ }
233
+ if (token !== generation.current) return;
234
+ pending.current = null;
235
+ await refreshRef.current?.();
236
+ } catch {
237
+ if (token === generation.current)
238
+ setError(
239
+ "Could not confirm delivery. Retry to check the same request."
240
+ );
241
+ } finally {
242
+ if (inFlight.current === controller) inFlight.current = null;
243
+ if (token === generation.current) setBusy(false);
244
+ }
245
+ }
246
+ const binding = () => {
247
+ const current = pageRef.current;
248
+ if (!current?.handoffId) throw new Error("No active human conversation.");
249
+ return { handoffId: current.handoffId, epoch: current.epoch };
250
+ };
251
+ return {
252
+ page,
253
+ busy,
254
+ error,
255
+ hasPending: Boolean(pending.current),
256
+ canReset: !busy && !pending.current && (rejected || page?.status === "ai" || page?.status === "failed" || !options.sessionId),
257
+ blocksAI: Boolean(options.enabled && options.sessionId && !page) || busy || Boolean(pending.current) || Boolean(page && page.status !== "ai"),
258
+ start: () => execute(
259
+ pending.current ?? { type: "start", operationId: crypto.randomUUID() }
260
+ ),
261
+ send: (message) => {
262
+ if (pending.current)
263
+ throw new Error(
264
+ "Retry the pending request before sending another message."
265
+ );
266
+ return execute({
267
+ type: "message",
268
+ operationId: crypto.randomUUID(),
269
+ ...binding(),
270
+ message
271
+ });
272
+ },
273
+ returnToAI: () => execute(
274
+ pending.current ?? {
275
+ type: "return",
276
+ operationId: crypto.randomUUID(),
277
+ ...binding()
278
+ }
279
+ ),
280
+ retry: async () => {
281
+ if (pending.current) return execute(pending.current);
282
+ try {
283
+ await refreshRef.current?.();
284
+ } catch {
285
+ setError("Reconnecting to your conversation\u2026");
286
+ }
287
+ }
288
+ };
289
+ }
290
+
291
+ // src/react/hooks/useAgentChat.ts
292
+ var import_react3 = require("react");
133
293
 
134
294
  // src/runtime/tool-ui.ts
135
295
  var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
@@ -271,16 +431,16 @@ function parseStep(value) {
271
431
  if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
272
432
  return null;
273
433
  }
274
- const id = boundedString(value.id, 80);
434
+ const id2 = boundedString(value.id, 80);
275
435
  const label = boundedString(value.label, 160);
276
436
  const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
277
- 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(
437
+ 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(
278
438
  (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
279
439
  )) {
280
440
  return null;
281
441
  }
282
442
  return {
283
- id,
443
+ id: id2,
284
444
  label,
285
445
  fieldPaths: value.fieldPaths,
286
446
  ...description ? { description } : {}
@@ -317,14 +477,14 @@ function parseAgentToolUiSurface(value) {
317
477
  ])) {
318
478
  return null;
319
479
  }
320
- const id = boundedString(value.id, 200);
480
+ const id2 = boundedString(value.id, 200);
321
481
  const title = boundedString(value.title, 200);
322
482
  const toolSlug = boundedString(value.toolSlug, 200);
323
483
  const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
324
484
  const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
325
485
  const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
326
486
  const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
327
- if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
487
+ if (!id2 || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
328
488
  return null;
329
489
  }
330
490
  const fields = value.fields.map(parseField);
@@ -344,7 +504,7 @@ function parseAgentToolUiSurface(value) {
344
504
  }
345
505
  return {
346
506
  schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
347
- id,
507
+ id: id2,
348
508
  title,
349
509
  toolSlug,
350
510
  fields,
@@ -455,10 +615,205 @@ function completeConnectedToolWork(item, result) {
455
615
  }
456
616
 
457
617
  // src/runtime/client.ts
458
- var import_client2 = require("eve/client");
618
+ var import_client4 = require("eve/client");
619
+
620
+ // src/runtime/handoff.ts
621
+ var import_client3 = require("eve/client");
622
+
623
+ // src/runtime/generated/handoff-contract.ts
624
+ var import_zod = require("zod");
625
+ var id = import_zod.z.string().min(1).max(200);
626
+ var sequence = import_zod.z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
627
+ var text = import_zod.z.string().trim().min(1).max(16e3);
628
+ var agentRuntimeHandoffVersion = "webless.ai/agent-runtime-handoff/v1";
629
+ var agentRuntimeHandoffStatusSchema = import_zod.z.enum([
630
+ "ai",
631
+ "requesting",
632
+ "queued",
633
+ "human",
634
+ "resolved",
635
+ "failed"
636
+ ]);
637
+ var agentRuntimeHandoffActorSchema = import_zod.z.strictObject({
638
+ id,
639
+ name: import_zod.z.string().trim().min(1).max(200)
640
+ });
641
+ var eventBase = {
642
+ id,
643
+ sequence: sequence.refine((value) => value > 0),
644
+ handoffId: id,
645
+ epoch: sequence.refine((value) => value > 0),
646
+ createdAt: import_zod.z.iso.datetime()
647
+ };
648
+ var agentRuntimeHandoffEventSchema = import_zod.z.discriminatedUnion("type", [
649
+ import_zod.z.strictObject({
650
+ ...eventBase,
651
+ type: import_zod.z.literal("status"),
652
+ status: agentRuntimeHandoffStatusSchema,
653
+ actor: agentRuntimeHandoffActorSchema.nullable()
654
+ }),
655
+ import_zod.z.strictObject({
656
+ ...eventBase,
657
+ type: import_zod.z.literal("visitor.message"),
658
+ message: text,
659
+ operationId: id
660
+ }),
661
+ import_zod.z.strictObject({
662
+ ...eventBase,
663
+ type: import_zod.z.literal("human.message"),
664
+ message: text,
665
+ actor: agentRuntimeHandoffActorSchema
666
+ })
667
+ ]);
668
+ var agentRuntimeHandoffReadRequestSchema = import_zod.z.strictObject({
669
+ after: sequence.default(0)
670
+ });
671
+ var agentRuntimeHandoffPageSchema = import_zod.z.strictObject({
672
+ apiVersion: import_zod.z.literal(agentRuntimeHandoffVersion),
673
+ sessionId: id,
674
+ enabled: import_zod.z.boolean(),
675
+ status: agentRuntimeHandoffStatusSchema,
676
+ handoffId: id.nullable(),
677
+ epoch: sequence,
678
+ after: sequence,
679
+ cursor: sequence,
680
+ hasMore: import_zod.z.boolean(),
681
+ events: import_zod.z.array(agentRuntimeHandoffEventSchema).max(100)
682
+ }).superRefine((page, ctx) => {
683
+ let cursor = page.after;
684
+ let previousEpoch = 0;
685
+ const statuses = /* @__PURE__ */ new Map();
686
+ const ids = /* @__PURE__ */ new Set();
687
+ const handoffs = /* @__PURE__ */ new Map();
688
+ const terminalEpochs = /* @__PURE__ */ new Set();
689
+ let currentStatus;
690
+ for (const event of page.events) {
691
+ if (event.epoch < previousEpoch)
692
+ ctx.addIssue({
693
+ code: "custom",
694
+ message: "Handoff epochs cannot decrease."
695
+ });
696
+ previousEpoch = event.epoch;
697
+ if (ids.has(event.id))
698
+ ctx.addIssue({
699
+ code: "custom",
700
+ message: "Duplicate handoff event ID."
701
+ });
702
+ ids.add(event.id);
703
+ const handoffId = handoffs.get(event.epoch);
704
+ if (handoffId !== void 0 && handoffId !== event.handoffId || event.epoch === page.epoch && event.handoffId !== page.handoffId)
705
+ ctx.addIssue({
706
+ code: "custom",
707
+ message: "Inconsistent handoff epoch identity."
708
+ });
709
+ handoffs.set(event.epoch, event.handoffId);
710
+ if (terminalEpochs.has(event.epoch) && (event.type !== "status" || event.status !== "ai"))
711
+ ctx.addIssue({
712
+ code: "custom",
713
+ message: "Handoff event follows resolution."
714
+ });
715
+ if (event.type === "status") {
716
+ if (event.status === "human" && event.actor === null)
717
+ ctx.addIssue({
718
+ code: "custom",
719
+ message: "Human ownership requires a representative."
720
+ });
721
+ const previous = statuses.get(event.epoch);
722
+ const transitions = {
723
+ ai: [],
724
+ requesting: ["queued", "resolved", "failed"],
725
+ queued: ["human", "resolved", "failed"],
726
+ human: ["resolved", "failed"],
727
+ resolved: ["ai"],
728
+ failed: []
729
+ };
730
+ if (previous !== void 0 && !transitions[previous].includes(event.status))
731
+ ctx.addIssue({
732
+ code: "custom",
733
+ message: "Invalid handoff ownership transition."
734
+ });
735
+ statuses.set(event.epoch, event.status);
736
+ if (["resolved", "failed", "ai"].includes(event.status))
737
+ terminalEpochs.add(event.epoch);
738
+ if (event.epoch === page.epoch) currentStatus = event.status;
739
+ }
740
+ if (event.sequence !== cursor + 1) {
741
+ ctx.addIssue({
742
+ code: "custom",
743
+ message: "Handoff event gap or replay."
744
+ });
745
+ }
746
+ cursor = event.sequence;
747
+ if (event.epoch > page.epoch) {
748
+ ctx.addIssue({
749
+ code: "custom",
750
+ message: "Event exceeds current epoch."
751
+ });
752
+ }
753
+ }
754
+ if (!page.hasMore && currentStatus !== void 0 && currentStatus !== page.status)
755
+ ctx.addIssue({
756
+ code: "custom",
757
+ message: "Handoff ownership contradicts its final status event."
758
+ });
759
+ if (page.cursor !== cursor || page.hasMore && page.events.length === 0) {
760
+ ctx.addIssue({ code: "custom", message: "Invalid handoff page cursor." });
761
+ }
762
+ if (page.epoch === 0 !== (page.handoffId === null)) {
763
+ ctx.addIssue({ code: "custom", message: "Invalid handoff identity." });
764
+ }
765
+ if (page.handoffId === null && page.status !== "ai") {
766
+ ctx.addIssue({
767
+ code: "custom",
768
+ message: "Handoff identity is required."
769
+ });
770
+ }
771
+ });
772
+ var agentRuntimeHandoffStartSchema = import_zod.z.strictObject({
773
+ operationId: id
774
+ });
775
+ var agentRuntimeHandoffCommandSchema = import_zod.z.strictObject({
776
+ operationId: id,
777
+ handoffId: id,
778
+ epoch: sequence.refine((value) => value > 0)
779
+ });
780
+ var agentRuntimeHandoffMessageSchema = agentRuntimeHandoffCommandSchema.extend({ message: text });
781
+ var agentRuntimeHandoffBindingSchema = import_zod.z.strictObject({
782
+ tenantId: id,
783
+ indexId: id,
784
+ visitorSubject: id,
785
+ sessionId: id,
786
+ handoffId: id,
787
+ epoch: sequence.refine((value) => value > 0)
788
+ });
789
+ var providerEventBase = {
790
+ apiVersion: import_zod.z.literal(agentRuntimeHandoffVersion),
791
+ binding: agentRuntimeHandoffBindingSchema,
792
+ eventId: id,
793
+ sequence: sequence.refine((value) => value > 0)
794
+ };
795
+ var agentRuntimeHandoffProviderEventSchema = import_zod.z.discriminatedUnion(
796
+ "type",
797
+ [
798
+ import_zod.z.strictObject({ ...providerEventBase, type: import_zod.z.literal("queued") }),
799
+ import_zod.z.strictObject({
800
+ ...providerEventBase,
801
+ type: import_zod.z.literal("assigned"),
802
+ actor: agentRuntimeHandoffActorSchema
803
+ }),
804
+ import_zod.z.strictObject({
805
+ ...providerEventBase,
806
+ type: import_zod.z.literal("human.message"),
807
+ actor: agentRuntimeHandoffActorSchema,
808
+ message: text
809
+ }),
810
+ import_zod.z.strictObject({ ...providerEventBase, type: import_zod.z.literal("resolved") }),
811
+ import_zod.z.strictObject({ ...providerEventBase, type: import_zod.z.literal("failed") })
812
+ ]
813
+ );
459
814
 
460
815
  // src/runtime/capability.ts
461
- var import_client = require("eve/client");
816
+ var import_client2 = require("eve/client");
462
817
  var MAX_REFRESH_SKEW_MS = 3e4;
463
818
  var LOCAL_LOOPBACK_ORIGINS = [
464
819
  "http://127.0.0.1:3010",
@@ -603,7 +958,7 @@ async function withCapabilityRefresh(capability, request) {
603
958
  try {
604
959
  return await request();
605
960
  } catch (error) {
606
- if (!(error instanceof import_client.ClientError) || error.status !== 401) {
961
+ if (!(error instanceof import_client2.ClientError) || error.status !== 401) {
607
962
  throw error;
608
963
  }
609
964
  capability.invalidate();
@@ -611,6 +966,75 @@ async function withCapabilityRefresh(capability, request) {
611
966
  }
612
967
  }
613
968
 
969
+ // src/runtime/handoff.ts
970
+ function createHandoffClient(options) {
971
+ const target = () => {
972
+ const sessionId = options.getSessionId();
973
+ if (!sessionId)
974
+ throw new Error(
975
+ "Start a conversation before requesting a representative."
976
+ );
977
+ return {
978
+ sessionId,
979
+ path: `/webless/v1/session/${encodeURIComponent(sessionId)}/handoff`
980
+ };
981
+ };
982
+ const request = (path, init) => withCapabilityRefresh(options.capability, async () => {
983
+ const response = await options.getClient().fetch(path, {
984
+ ...init,
985
+ cache: "no-store",
986
+ redirect: "error"
987
+ });
988
+ if (!response.ok) {
989
+ throw new import_client3.ClientError(
990
+ response.status,
991
+ await response.text(),
992
+ response.headers
993
+ );
994
+ }
995
+ return response;
996
+ });
997
+ const command = async (suffix, body, signal) => {
998
+ const { path } = target();
999
+ await request(path + suffix, {
1000
+ method: "POST",
1001
+ headers: { "content-type": "application/json" },
1002
+ body: JSON.stringify(body),
1003
+ signal
1004
+ });
1005
+ };
1006
+ return {
1007
+ async read(input = {}) {
1008
+ const { after } = agentRuntimeHandoffReadRequestSchema.parse({
1009
+ after: input.after
1010
+ });
1011
+ const { sessionId, path } = target();
1012
+ const response = await request(`${path}?after=${after}`, {
1013
+ signal: input.signal
1014
+ });
1015
+ const value = await response.json();
1016
+ const page = agentRuntimeHandoffPageSchema.parse(value);
1017
+ if (page.sessionId !== sessionId || page.after !== after) {
1018
+ throw new Error(
1019
+ "The handoff response does not belong to this conversation or cursor."
1020
+ );
1021
+ }
1022
+ return page;
1023
+ },
1024
+ start: (operationId, signal) => command(
1025
+ "",
1026
+ agentRuntimeHandoffStartSchema.parse({ operationId }),
1027
+ signal
1028
+ ),
1029
+ send: (input, signal) => command(
1030
+ "/messages",
1031
+ agentRuntimeHandoffMessageSchema.parse(input),
1032
+ signal
1033
+ ),
1034
+ returnToAI: (input, signal) => command("/return", agentRuntimeHandoffCommandSchema.parse(input), signal)
1035
+ };
1036
+ }
1037
+
614
1038
  // src/runtime/config.ts
615
1039
  var DEFAULT_RUNTIME_ORIGIN = "https://runtime.staging.webless.ai";
616
1040
  var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
@@ -1201,6 +1625,11 @@ var AgentSession = class {
1201
1625
  version,
1202
1626
  visitorSessionId
1203
1627
  });
1628
+ this.handoff = createHandoffClient({
1629
+ getClient: () => this.ensureClient(),
1630
+ getSessionId: () => this.getActiveSessionId(),
1631
+ capability: this.capability
1632
+ });
1204
1633
  }
1205
1634
  indexId;
1206
1635
  version;
@@ -1213,6 +1642,7 @@ var AgentSession = class {
1213
1642
  activeResponse;
1214
1643
  childStreams;
1215
1644
  capability;
1645
+ handoff;
1216
1646
  getActiveSessionId() {
1217
1647
  return this.session?.state.sessionId ?? loadPersistedAgentSession(this.visitorSessionId, this.storeOptions)?.sessionId;
1218
1648
  }
@@ -1287,7 +1717,7 @@ var AgentSession = class {
1287
1717
  }
1288
1718
  this.activeResponse = void 0;
1289
1719
  this.session = void 0;
1290
- this.client = new import_client2.Client({
1720
+ this.client = new import_client4.Client({
1291
1721
  auth: { bearer: () => this.capability.getAccessToken() },
1292
1722
  host: config.host,
1293
1723
  redirect: "error"
@@ -1323,7 +1753,7 @@ var AgentSession = class {
1323
1753
  () => activeSession.send(message, { signal })
1324
1754
  );
1325
1755
  } catch (error) {
1326
- if (error instanceof import_client2.ClientError && error.status === 409 && error.code === "session_not_active") {
1756
+ if (error instanceof import_client4.ClientError && error.status === 409 && error.code === "session_not_active") {
1327
1757
  clearPersistedAgentSession(this.visitorSessionId, this.storeOptions);
1328
1758
  this.session = void 0;
1329
1759
  session = void 0;
@@ -1497,10 +1927,10 @@ var AgentSession = class {
1497
1927
  }
1498
1928
  this.session = session;
1499
1929
  const inputResponses = responses.map(
1500
- ({ requestId, optionId, text: text2 }) => ({
1930
+ ({ requestId, optionId, text: text3 }) => ({
1501
1931
  requestId,
1502
1932
  ...optionId ? { optionId } : {},
1503
- ...text2 ? { text: text2 } : {}
1933
+ ...text3 ? { text: text3 } : {}
1504
1934
  })
1505
1935
  );
1506
1936
  const response = await withCapabilityRefresh(
@@ -1590,6 +2020,7 @@ function createAgentClient(options) {
1590
2020
  );
1591
2021
  return {
1592
2022
  indexId,
2023
+ handoff: session.handoff,
1593
2024
  version,
1594
2025
  runtimeOrigin,
1595
2026
  visitorSessionId,
@@ -1621,7 +2052,7 @@ function createAgentClient(options) {
1621
2052
  }
1622
2053
 
1623
2054
  // src/runtime/errors.ts
1624
- var import_client3 = require("eve/client");
2055
+ var import_client5 = require("eve/client");
1625
2056
  var TRANSIENT_AGENT_ERROR_MESSAGE = "The agent run stopped before the action finished. Please try again.";
1626
2057
  var PREVIEW_AUTHORIZATION_ERROR_MESSAGE = "This preview could not be authorized. Open the latest preview from Webless.";
1627
2058
  function isTransientRuntimeMessage(message) {
@@ -1633,7 +2064,7 @@ function isPreviewAuthorizationMessage(message) {
1633
2064
  return normalized.includes("unpublished preview authorization") || normalized.includes("unpublished preview grant") || normalized.includes("agent studio preview grant") || normalized.includes("preview authorization");
1634
2065
  }
1635
2066
  function formatAgentError(error) {
1636
- if (error instanceof import_client3.ClientError) {
2067
+ if (error instanceof import_client5.ClientError) {
1637
2068
  if (error.status === 401 && error.code === "index_required") {
1638
2069
  return "Missing indexId \u2014 pass a published index id to createAgentClient().";
1639
2070
  }
@@ -1667,14 +2098,14 @@ function formatAgentError(error) {
1667
2098
  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.";
1668
2099
  function visitorMessageDisplayText(message) {
1669
2100
  if (message.role !== "visitor") return message.text;
1670
- const text2 = message.text.trim();
1671
- if (!text2) return message.text;
1672
- if (text2.startsWith(COMPOSER_FORM_SKIP_DISMISSAL)) {
1673
- const afterDismissal = text2.slice(COMPOSER_FORM_SKIP_DISMISSAL.length).trim().replace(/^\n+/, "").trim();
2101
+ const text3 = message.text.trim();
2102
+ if (!text3) return message.text;
2103
+ if (text3.startsWith(COMPOSER_FORM_SKIP_DISMISSAL)) {
2104
+ const afterDismissal = text3.slice(COMPOSER_FORM_SKIP_DISMISSAL.length).trim().replace(/^\n+/, "").trim();
1674
2105
  if (afterDismissal) return afterDismissal;
1675
2106
  }
1676
2107
  const runtime = message.runtimeText?.trim();
1677
- if (runtime && text2 === runtime && runtime.includes(COMPOSER_FORM_SKIP_DISMISSAL)) {
2108
+ if (runtime && text3 === runtime && runtime.includes(COMPOSER_FORM_SKIP_DISMISSAL)) {
1678
2109
  const afterDismissal = runtime.slice(
1679
2110
  runtime.indexOf(COMPOSER_FORM_SKIP_DISMISSAL) + COMPOSER_FORM_SKIP_DISMISSAL.length
1680
2111
  ).trim().replace(/^\n+/, "").trim();
@@ -1748,13 +2179,13 @@ function parseVisitorFormFields(value) {
1748
2179
  const seen = /* @__PURE__ */ new Set();
1749
2180
  for (const item of value) {
1750
2181
  const record2 = asRecord(item);
1751
- const id = asString(record2?.id).toLowerCase().replace(/[^a-z0-9_-]/g, "");
2182
+ const id2 = asString(record2?.id).toLowerCase().replace(/[^a-z0-9_-]/g, "");
1752
2183
  const kind = asString(record2?.kind);
1753
- if (!record2 || !id || seen.has(id) || !isFieldKind2(kind)) continue;
1754
- seen.add(id);
1755
- const label = asString(record2.label) || id;
2184
+ if (!record2 || !id2 || seen.has(id2) || !isFieldKind2(kind)) continue;
2185
+ seen.add(id2);
2186
+ const label = asString(record2.label) || id2;
1756
2187
  fields.push({
1757
- id,
2188
+ id: id2,
1758
2189
  kind,
1759
2190
  label,
1760
2191
  placeholder: asString(record2.placeholder) || label,
@@ -1790,52 +2221,52 @@ function formatComposerFormMessage(form, values) {
1790
2221
  return value ? `${field.label}: ${value}` : "";
1791
2222
  }).filter(Boolean).join("\n");
1792
2223
  }
1793
- function looksLikeFieldCollection(text2) {
2224
+ function looksLikeFieldCollection(text3) {
1794
2225
  return /\b(please|need|send|share|enter|provide|add|collect|what|which|use)\b/i.test(
1795
- text2
1796
- ) || /:\s*$/m.test(text2) || /^[-*•]\s+/m.test(text2);
2226
+ text3
2227
+ ) || /:\s*$/m.test(text3) || /^[-*•]\s+/m.test(text3);
1797
2228
  }
1798
- function looksLikeBookingCopy(text2) {
2229
+ function looksLikeBookingCopy(text3) {
1799
2230
  return /book(ing)? (card|a demo|this time)|pick a (date|time)|available (times|slots)|calendly/i.test(
1800
- text2
2231
+ text3
1801
2232
  );
1802
2233
  }
1803
- function echoedLabeledFieldIds(text2) {
2234
+ function echoedLabeledFieldIds(text3) {
1804
2235
  const ids = /* @__PURE__ */ new Set();
1805
- if (/\bname\s*:\s+\S+/i.test(text2)) ids.add("name");
1806
- if (/\be-?mail\s*:\s+\S+/i.test(text2)) ids.add("email");
1807
- if (/\bphone\s*:\s+\S+/i.test(text2)) ids.add("phone");
1808
- if (/\bcompany\s*:\s+\S+/i.test(text2)) ids.add("company");
2236
+ if (/\bname\s*:\s+\S+/i.test(text3)) ids.add("name");
2237
+ if (/\be-?mail\s*:\s+\S+/i.test(text3)) ids.add("email");
2238
+ if (/\bphone\s*:\s+\S+/i.test(text3)) ids.add("phone");
2239
+ if (/\bcompany\s*:\s+\S+/i.test(text3)) ids.add("company");
1809
2240
  return ids;
1810
2241
  }
1811
- function matchLibraryFields(text2) {
2242
+ function matchLibraryFields(text3) {
1812
2243
  return FIELD_LIBRARY.flatMap((field) => {
1813
- if (field.exclude?.test(text2)) {
1814
- const leftover = text2.replace(field.exclude, " ");
2244
+ if (field.exclude?.test(text3)) {
2245
+ const leftover = text3.replace(field.exclude, " ");
1815
2246
  if (!field.patterns.some((pattern) => pattern.test(leftover))) return [];
1816
- } else if (!field.patterns.some((pattern) => pattern.test(text2))) {
2247
+ } else if (!field.patterns.some((pattern) => pattern.test(text3))) {
1817
2248
  return [];
1818
2249
  }
1819
2250
  const { patterns: _patterns, exclude: _exclude, ...next } = field;
1820
2251
  return [next];
1821
2252
  }).slice(0, MAX_FORM_FIELDS);
1822
2253
  }
1823
- function looksLikeCompletedActionRecap(text2) {
1824
- const confirmingCreate = /\bshould i create this\b/i.test(text2);
1825
- const echoingFilledFields = /\b(?:name|email|phone|company)\s*:\s+\S+/i.test(text2) && /[^\s@]+@[^\s@]+\.[^\s@]+/i.test(text2);
2254
+ function looksLikeCompletedActionRecap(text3) {
2255
+ const confirmingCreate = /\bshould i create this\b/i.test(text3);
2256
+ const echoingFilledFields = /\b(?:name|email|phone|company)\s*:\s+\S+/i.test(text3) && /[^\s@]+@[^\s@]+\.[^\s@]+/i.test(text3);
1826
2257
  if (echoingFilledFields) {
1827
- if (looksLikeFieldCollection(text2)) {
1828
- const echoed = echoedLabeledFieldIds(text2);
1829
- if (matchLibraryFields(text2).some((field) => !echoed.has(field.id))) {
2258
+ if (looksLikeFieldCollection(text3)) {
2259
+ const echoed = echoedLabeledFieldIds(text3);
2260
+ if (matchLibraryFields(text3).some((field) => !echoed.has(field.id))) {
1830
2261
  return false;
1831
2262
  }
1832
2263
  }
1833
2264
  return true;
1834
2265
  }
1835
- return confirmingCreate && !looksLikeFieldCollection(text2);
2266
+ return confirmingCreate && !looksLikeFieldCollection(text3);
1836
2267
  }
1837
- function inferComposerForm(text2) {
1838
- const cleaned = text2.trim();
2268
+ function inferComposerForm(text3) {
2269
+ const cleaned = text3.trim();
1839
2270
  if (!cleaned || looksLikeBookingCopy(cleaned) || looksLikeCompletedActionRecap(cleaned) || !looksLikeFieldCollection(cleaned)) {
1840
2271
  return null;
1841
2272
  }
@@ -1850,8 +2281,8 @@ function resolveComposerForm(input) {
1850
2281
  if (input.enabled === false || input.hasBookingOffer || input.hasPendingConfirmation) {
1851
2282
  return null;
1852
2283
  }
1853
- const text2 = input.agentText.trim();
1854
- if (!text2) return null;
2284
+ const text3 = input.agentText.trim();
2285
+ if (!text3) return null;
1855
2286
  const card = input.cards?.find((item) => item.type === "visitor_form");
1856
2287
  if (card?.fields && card.fields.length >= MIN_FORM_FIELDS) {
1857
2288
  return {
@@ -1859,7 +2290,7 @@ function resolveComposerForm(input) {
1859
2290
  fields: card.fields.slice(0, MAX_FORM_FIELDS)
1860
2291
  };
1861
2292
  }
1862
- return inferComposerForm(text2);
2293
+ return inferComposerForm(text3);
1863
2294
  }
1864
2295
 
1865
2296
  // src/react/lib/tool-card.ts
@@ -1873,9 +2304,9 @@ function preferBookingOffer(current, next) {
1873
2304
  }
1874
2305
  return next;
1875
2306
  }
1876
- function looksLikeBookingReady(text2) {
2307
+ function looksLikeBookingReady(text3) {
1877
2308
  return /demo option|options are ready|pick a (date|time)|available (times|slots)|book(ing)? (card|the demo)|\bschedule\b/i.test(
1878
- text2
2309
+ text3
1879
2310
  );
1880
2311
  }
1881
2312
  function bookingOfferIdentityKey(offer) {
@@ -1985,29 +2416,29 @@ function bookingCardFromActionOutput(output) {
1985
2416
  const data = asRecord2(record2?.output) ?? asRecord2(record2?.data) ?? record2;
1986
2417
  return parseToolCard(data);
1987
2418
  }
1988
- function ensureBookingOfferText(text2, offer) {
1989
- if (!offer) return text2;
1990
- if (extractToolCards(text2).some((card) => card.type === "booking_offer")) {
1991
- return text2;
2419
+ function ensureBookingOfferText(text3, offer) {
2420
+ if (!offer) return text3;
2421
+ if (extractToolCards(text3).some((card) => card.type === "booking_offer")) {
2422
+ return text3;
1992
2423
  }
1993
- const visible = stripToolCards(text2).trim() || text2.trim();
2424
+ const visible = stripToolCards(text3).trim() || text3.trim();
1994
2425
  return `${visible}
1995
2426
 
1996
2427
  ${formatBookingOfferFence(offer)}`;
1997
2428
  }
1998
- function hideToolCardFences(text2) {
1999
- 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();
2429
+ function hideToolCardFences(text3) {
2430
+ 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();
2000
2431
  }
2001
2432
  var BOOKING_CARD_FALLBACK = "Pick a date and time that works for you.";
2002
- function looksLikeBookingAvailabilityDump(text2) {
2003
- const cleaned = text2.trim();
2433
+ function looksLikeBookingAvailabilityDump(text3) {
2434
+ const cleaned = text3.trim();
2004
2435
  if (!cleaned) return false;
2005
2436
  const isoCount = (cleaned.match(/\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/g) ?? []).length;
2006
2437
  const utcCount = (cleaned.match(/\bUTC\b/g) ?? []).length;
2007
2438
  return isoCount >= 2 || utcCount >= 2 || /api\.calendly\.com|calendly\.com\//i.test(cleaned) || /webless-tool-card/i.test(cleaned);
2008
2439
  }
2009
- function sanitizeBookingOfferCopy(text2) {
2010
- const cleaned = hideToolCardFences(text2);
2440
+ function sanitizeBookingOfferCopy(text3) {
2441
+ const cleaned = hideToolCardFences(text3);
2011
2442
  if (!cleaned || looksLikeBookingAvailabilityDump(cleaned)) {
2012
2443
  return BOOKING_CARD_FALLBACK;
2013
2444
  }
@@ -2020,9 +2451,9 @@ function visitorTimeZone() {
2020
2451
  return "UTC";
2021
2452
  }
2022
2453
  }
2023
- function extractToolCards(text2) {
2454
+ function extractToolCards(text3) {
2024
2455
  const cards = [];
2025
- for (const match of text2.matchAll(FENCE_PATTERN)) {
2456
+ for (const match of text3.matchAll(FENCE_PATTERN)) {
2026
2457
  try {
2027
2458
  const card = parseToolCard(JSON.parse(match[1] ?? ""));
2028
2459
  if (card) cards.push(card);
@@ -2031,8 +2462,8 @@ function extractToolCards(text2) {
2031
2462
  }
2032
2463
  return cards;
2033
2464
  }
2034
- function stripToolCards(text2) {
2035
- return text2.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
2465
+ function stripToolCards(text3) {
2466
+ return text3.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
2036
2467
  }
2037
2468
  function localDateKey(date) {
2038
2469
  if (Number.isNaN(date.getTime())) return "";
@@ -2145,7 +2576,7 @@ function visitorBookingPrefix(booking) {
2145
2576
  function record(value) {
2146
2577
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2147
2578
  }
2148
- function text(value, max = 500) {
2579
+ function text2(value, max = 500) {
2149
2580
  return typeof value === "string" && value.trim().length > 0 && value.trim().length <= max;
2150
2581
  }
2151
2582
  function safeSearchUrl(value) {
@@ -2166,7 +2597,7 @@ function parseAgentSearchReferences(value) {
2166
2597
  const source = record(value2);
2167
2598
  if (!source || Object.keys(source).some(
2168
2599
  (key) => !["id", "title", "url"].includes(key)
2169
- ) || !text(source.id, Infinity) || !text(source.title) || source.url !== void 0 && typeof source.url !== "string")
2600
+ ) || !text2(source.id, Infinity) || !text2(source.title) || source.url !== void 0 && typeof source.url !== "string")
2170
2601
  return null;
2171
2602
  const url = safeSearchUrl(source.url);
2172
2603
  sources.push({
@@ -2178,7 +2609,7 @@ function parseAgentSearchReferences(value) {
2178
2609
  let cta;
2179
2610
  if (data.cta !== void 0) {
2180
2611
  const action = record(data.cta);
2181
- if (!action || Object.keys(action).some((key) => !["label", "url"].includes(key)) || !text(action.label) || action.url !== void 0 && typeof action.url !== "string")
2612
+ if (!action || Object.keys(action).some((key) => !["label", "url"].includes(key)) || !text2(action.label) || action.url !== void 0 && typeof action.url !== "string")
2182
2613
  return null;
2183
2614
  const url = safeSearchUrl(action.url);
2184
2615
  cta = { label: action.label.trim(), ...url ? { url } : {} };
@@ -2197,7 +2628,7 @@ function parseAgentSearchDiscoveryOutput(value) {
2197
2628
  const data = record(decoded);
2198
2629
  if (!data || Object.keys(data).some(
2199
2630
  (key) => !["answer", "sources", "cta", "suggestions"].includes(key)
2200
- ) || !text(data.answer, 5e4) || !Array.isArray(data.suggestions) || data.suggestions.length > 8 || !data.suggestions.every((item) => text(item)))
2631
+ ) || !text2(data.answer, 5e4) || !Array.isArray(data.suggestions) || data.suggestions.length > 8 || !data.suggestions.every((item) => text2(item)))
2201
2632
  return null;
2202
2633
  const references = parseAgentSearchReferences(data);
2203
2634
  return references ? {
@@ -2215,9 +2646,21 @@ function conversationKey(storageKeyPrefix, visitorSessionId) {
2215
2646
  function parseMessage(value) {
2216
2647
  if (typeof value !== "object" || value === null) return null;
2217
2648
  const record2 = value;
2218
- if (typeof record2.id !== "string" || record2.role !== "agent" && record2.role !== "visitor" || typeof record2.text !== "string" || typeof record2.createdAt !== "number" || !Number.isFinite(record2.createdAt)) {
2649
+ 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)) {
2219
2650
  return null;
2220
2651
  }
2652
+ if (record2.role === "human") {
2653
+ const person = record2.representative;
2654
+ if (typeof person !== "object" || person === null || !("id" in person) || !("name" in person) || typeof person.id !== "string" || typeof person.name !== "string")
2655
+ return null;
2656
+ return {
2657
+ id: record2.id,
2658
+ role: "human",
2659
+ text: record2.text,
2660
+ createdAt: record2.createdAt,
2661
+ representative: { id: person.id, name: person.name }
2662
+ };
2663
+ }
2221
2664
  if (record2.role === "visitor") {
2222
2665
  return {
2223
2666
  id: record2.id,
@@ -2512,10 +2955,10 @@ function safeText(value) {
2512
2955
  return value.trim().slice(0, MAX_TEXT_LENGTH);
2513
2956
  }
2514
2957
  function safeHref(value) {
2515
- const text2 = safeText(value);
2516
- if (!text2) return "";
2958
+ const text3 = safeText(value);
2959
+ if (!text3) return "";
2517
2960
  try {
2518
- const url = new URL(text2);
2961
+ const url = new URL(text3);
2519
2962
  return url.protocol === "https:" ? url.toString() : "";
2520
2963
  } catch {
2521
2964
  return "";
@@ -2878,15 +3321,15 @@ function appendChatCollectiblePrompts(messages, requests) {
2878
3321
  }
2879
3322
  return next;
2880
3323
  }
2881
- function chatInputResponseForText(requests, text2) {
2882
- const trimmed = text2.trim();
3324
+ function chatInputResponseForText(requests, text3) {
3325
+ const trimmed = text3.trim();
2883
3326
  if (!trimmed) return null;
2884
3327
  const pending = requests.find(isChatCollectibleInputRequest);
2885
3328
  if (!pending) return null;
2886
3329
  return { requestId: pending.requestId, text: trimmed };
2887
3330
  }
2888
- function normalizeAssistantDedupeKey(text2) {
2889
- return text2.trim().replace(/\s+/g, " ").toLowerCase();
3331
+ function normalizeAssistantDedupeKey(text3) {
3332
+ return text3.trim().replace(/\s+/g, " ").toLowerCase();
2890
3333
  }
2891
3334
  function isNearDuplicateAssistantText(left, right) {
2892
3335
  const a = normalizeAssistantDedupeKey(left);
@@ -2999,6 +3442,7 @@ function isJsonRecord(value) {
2999
3442
  return value !== null && typeof value === "object" && !Array.isArray(value);
3000
3443
  }
3001
3444
  function useAgentChat({
3445
+ handoffEnabled = false,
3002
3446
  customerId,
3003
3447
  getUnpublishedPreviewGrant,
3004
3448
  indexId,
@@ -3010,13 +3454,13 @@ function useAgentChat({
3010
3454
  greeting,
3011
3455
  toolResultRegistry
3012
3456
  }) {
3013
- const initialState = (0, import_react2.useMemo)(
3457
+ const initialState = (0, import_react3.useMemo)(
3014
3458
  () => createInitialState(greeting?.trim() || DEFAULT_GREETING),
3015
3459
  [greeting]
3016
3460
  );
3017
- const previewGrantProviderRef = (0, import_react2.useRef)(getUnpublishedPreviewGrant);
3461
+ const previewGrantProviderRef = (0, import_react3.useRef)(getUnpublishedPreviewGrant);
3018
3462
  previewGrantProviderRef.current = getUnpublishedPreviewGrant;
3019
- const toolResultRegistryRef = (0, import_react2.useRef)(toolResultRegistry);
3463
+ const toolResultRegistryRef = (0, import_react3.useRef)(toolResultRegistry);
3020
3464
  toolResultRegistryRef.current = toolResultRegistry;
3021
3465
  const resolveUnpublishedPreviewGrant = () => {
3022
3466
  const provider = previewGrantProviderRef.current;
@@ -3029,7 +3473,7 @@ function useAgentChat({
3029
3473
  }
3030
3474
  return provider();
3031
3475
  };
3032
- const resolvedStorageKeyPrefix = (0, import_react2.useMemo)(
3476
+ const resolvedStorageKeyPrefix = (0, import_react3.useMemo)(
3033
3477
  () => storageKeyPrefix?.trim() || buildAgentStorageKeyPrefix({
3034
3478
  customerId,
3035
3479
  indexId,
@@ -3038,23 +3482,23 @@ function useAgentChat({
3038
3482
  }),
3039
3483
  [customerId, indexId, runtimeOrigin, storageKeyPrefix, version]
3040
3484
  );
3041
- const visitorId = (0, import_react2.useMemo)(
3485
+ const visitorId = (0, import_react3.useMemo)(
3042
3486
  () => visitorSessionId?.trim() || getOrCreateVisitorSessionId({
3043
3487
  storageKeyPrefix: resolvedStorageKeyPrefix
3044
3488
  }),
3045
3489
  [resolvedStorageKeyPrefix, visitorSessionId]
3046
3490
  );
3047
- const [state, setState] = (0, import_react2.useState)(
3491
+ const [state, setState] = (0, import_react3.useState)(
3048
3492
  () => stateFromConversation(
3049
3493
  loadPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId),
3050
3494
  initialState
3051
3495
  )
3052
3496
  );
3053
- const pendingBookingRef = (0, import_react2.useRef)(
3497
+ const pendingBookingRef = (0, import_react3.useRef)(
3054
3498
  loadPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId)
3055
3499
  );
3056
- const runRef = (0, import_react2.useRef)(null);
3057
- const clientRef = (0, import_react2.useRef)(
3500
+ const runRef = (0, import_react3.useRef)(null);
3501
+ const clientRef = (0, import_react3.useRef)(
3058
3502
  createAgentClient({
3059
3503
  locationConsent: true,
3060
3504
  customerId,
@@ -3068,8 +3512,8 @@ function useAgentChat({
3068
3512
  })
3069
3513
  );
3070
3514
  const identityKey = `${customerId ?? ""}|${indexId}|${version ?? "published"}|${runtimeOrigin ?? ""}|${previewBuildId ?? ""}|${resolvedStorageKeyPrefix}|${visitorId}|${greeting ?? ""}`;
3071
- const identityRef = (0, import_react2.useRef)(identityKey);
3072
- (0, import_react2.useEffect)(() => {
3515
+ const identityRef = (0, import_react3.useRef)(identityKey);
3516
+ (0, import_react3.useEffect)(() => {
3073
3517
  if (identityRef.current === identityKey) {
3074
3518
  return;
3075
3519
  }
@@ -3108,7 +3552,7 @@ function useAgentChat({
3108
3552
  version,
3109
3553
  visitorId
3110
3554
  ]);
3111
- (0, import_react2.useEffect)(() => {
3555
+ (0, import_react3.useEffect)(() => {
3112
3556
  if (!hasVisitorMessages(state.messages)) return;
3113
3557
  savePersistedAgentConversation(resolvedStorageKeyPrefix, visitorId, {
3114
3558
  messages: state.messages,
@@ -3128,16 +3572,77 @@ function useAgentChat({
3128
3572
  state.toolResults,
3129
3573
  visitorId
3130
3574
  ]);
3131
- const reset = (0, import_react2.useCallback)(() => {
3575
+ const handoff = useAgentHandoff({
3576
+ enabled: handoffEnabled,
3577
+ client: clientRef.current,
3578
+ sessionId: clientRef.current.getActiveSessionId(),
3579
+ onOwnership: () => {
3580
+ runRef.current?.abort();
3581
+ runRef.current = null;
3582
+ setState(
3583
+ (prev) => prev.phase === "complete" && !prev.streamingText && !prev.pendingInputs?.length && !prev.toolSteps.length ? prev : {
3584
+ ...prev,
3585
+ phase: "complete",
3586
+ streamingText: "",
3587
+ pendingInputs: [],
3588
+ toolSteps: [],
3589
+ toolResults: [],
3590
+ pendingOffer: null,
3591
+ error: null
3592
+ }
3593
+ );
3594
+ },
3595
+ onEvents: (events) => {
3596
+ setState((prev) => {
3597
+ const seen = new Set(prev.messages.map((message) => message.id));
3598
+ const messages = [];
3599
+ for (const event of events) {
3600
+ if (event.type === "status" || seen.has(`handoff-${event.id}`))
3601
+ continue;
3602
+ messages.push(
3603
+ event.type === "human.message" ? {
3604
+ id: `handoff-${event.id}`,
3605
+ role: "human",
3606
+ text: event.message,
3607
+ createdAt: Date.parse(event.createdAt),
3608
+ representative: event.actor
3609
+ } : {
3610
+ id: `handoff-${event.id}`,
3611
+ role: "visitor",
3612
+ text: event.message,
3613
+ createdAt: Date.parse(event.createdAt)
3614
+ }
3615
+ );
3616
+ }
3617
+ return messages.length ? {
3618
+ ...prev,
3619
+ messages: [...prev.messages, ...messages].sort(
3620
+ (a, b) => a.createdAt - b.createdAt
3621
+ )
3622
+ } : prev;
3623
+ });
3624
+ }
3625
+ });
3626
+ const handoffBlocksAI = (0, import_react3.useRef)(handoff.blocksAI);
3627
+ handoffBlocksAI.current = handoff.blocksAI;
3628
+ const reset = (0, import_react3.useCallback)(() => {
3629
+ if (handoff.blocksAI && !handoff.canReset) return;
3132
3630
  runRef.current?.abort();
3133
3631
  runRef.current = null;
3134
3632
  clientRef.current.reset();
3135
3633
  pendingBookingRef.current = null;
3136
3634
  clearPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId);
3137
3635
  setState(initialState);
3138
- }, [initialState, resolvedStorageKeyPrefix, visitorId]);
3139
- const runTurn = (0, import_react2.useCallback)(
3636
+ }, [
3637
+ handoff.blocksAI,
3638
+ handoff.canReset,
3639
+ initialState,
3640
+ resolvedStorageKeyPrefix,
3641
+ visitorId
3642
+ ]);
3643
+ const runTurn = (0, import_react3.useCallback)(
3140
3644
  async (input) => {
3645
+ if (handoffBlocksAI.current) return null;
3141
3646
  const {
3142
3647
  controller,
3143
3648
  initialText = "",
@@ -3261,6 +3766,7 @@ function useAgentChat({
3261
3766
  signal
3262
3767
  });
3263
3768
  if (resume && finalText === null) {
3769
+ if (!isActiveRun() || handoffBlocksAI.current) return null;
3264
3770
  finalText = await clientRef.current.sendTurn(visitorText, {
3265
3771
  handlers,
3266
3772
  signal
@@ -3293,8 +3799,12 @@ function useAgentChat({
3293
3799
  messages: appendAgentTurnMessage(
3294
3800
  prev.messages,
3295
3801
  displayText,
3296
- (prev.toolResults ?? []).filter((result) => result.kind === "search"),
3297
- (prev.toolResults ?? []).filter((result) => result.kind !== "search" && result.kind !== "input")
3802
+ (prev.toolResults ?? []).filter(
3803
+ (result) => result.kind === "search"
3804
+ ),
3805
+ (prev.toolResults ?? []).filter(
3806
+ (result) => result.kind !== "search" && result.kind !== "input"
3807
+ )
3298
3808
  ),
3299
3809
  toolResults: (prev.toolResults ?? []).filter(
3300
3810
  (result) => result.kind === "input"
@@ -3335,7 +3845,7 @@ function useAgentChat({
3335
3845
  },
3336
3846
  [resolvedStorageKeyPrefix, visitorId]
3337
3847
  );
3338
- const rememberBooking = (0, import_react2.useCallback)(
3848
+ const rememberBooking = (0, import_react3.useCallback)(
3339
3849
  (booking) => {
3340
3850
  const current = pendingBookingRef.current;
3341
3851
  if (current?.eventUri === booking.eventUri && current.inviteeEmail === booking.inviteeEmail && current.inviteeUri === booking.inviteeUri) {
@@ -3346,7 +3856,7 @@ function useAgentChat({
3346
3856
  },
3347
3857
  [resolvedStorageKeyPrefix, visitorId]
3348
3858
  );
3349
- const forgetBooking = (0, import_react2.useCallback)(
3859
+ const forgetBooking = (0, import_react3.useCallback)(
3350
3860
  (eventUri) => {
3351
3861
  const current = pendingBookingRef.current;
3352
3862
  if (!current) return;
@@ -3356,12 +3866,21 @@ function useAgentChat({
3356
3866
  },
3357
3867
  [resolvedStorageKeyPrefix, visitorId]
3358
3868
  );
3359
- const submit = (0, import_react2.useCallback)(
3869
+ const submit = (0, import_react3.useCallback)(
3360
3870
  async (visitorText, options) => {
3361
3871
  const trimmed = visitorText.trim();
3362
3872
  const outgoing = options?.runtimeText ?? visitorText;
3363
3873
  if (!outgoing.trim()) return null;
3364
3874
  const declinedComposerFormId = options?.declinedComposerFormId?.trim();
3875
+ if (handoff.blocksAI) {
3876
+ if (!["requesting", "queued", "human"].includes(
3877
+ handoff.page?.status ?? ""
3878
+ ) || handoff.busy)
3879
+ return null;
3880
+ if (!trimmed) return null;
3881
+ await handoff.send(trimmed);
3882
+ return null;
3883
+ }
3365
3884
  const chatResponse = chatInputResponseForText(
3366
3885
  state.pendingInputs ?? [],
3367
3886
  outgoing.trim()
@@ -3463,9 +3982,10 @@ ${outgoing}` : outgoing;
3463
3982
  visitorText: runtimeText
3464
3983
  });
3465
3984
  },
3466
- [runTurn, state.pendingInputs]
3985
+ [handoff, runTurn, state.pendingInputs]
3467
3986
  );
3468
- const retry = (0, import_react2.useCallback)(async () => {
3987
+ const retry = (0, import_react3.useCallback)(async () => {
3988
+ if (handoffBlocksAI.current) return;
3469
3989
  const visitorMessage = [...state.messages].reverse().find((message) => message.role === "visitor");
3470
3990
  if (!visitorMessage) return;
3471
3991
  if (runRef.current) {
@@ -3498,7 +4018,8 @@ ${outgoing}` : outgoing;
3498
4018
  visitorText: visitorTurnText(visitorMessage)
3499
4019
  });
3500
4020
  }, [runTurn, state.messages]);
3501
- const regenerate = (0, import_react2.useCallback)(async () => {
4021
+ const regenerate = (0, import_react3.useCallback)(async () => {
4022
+ if (handoffBlocksAI.current) return null;
3502
4023
  let lastVisitorIndex = -1;
3503
4024
  for (let index = state.messages.length - 1; index >= 0; index -= 1) {
3504
4025
  if (state.messages[index]?.role === "visitor") {
@@ -3541,7 +4062,7 @@ ${outgoing}` : outgoing;
3541
4062
  visitorText: visitorTurnText(visitorMessage)
3542
4063
  });
3543
4064
  }, [runTurn, state.messages]);
3544
- const respondToToolInput = (0, import_react2.useCallback)(
4065
+ const respondToToolInput = (0, import_react3.useCallback)(
3545
4066
  async (surface, values) => {
3546
4067
  await submit(`${surface.title} submitted`, {
3547
4068
  runtimeText: [
@@ -3554,9 +4075,9 @@ ${outgoing}` : outgoing;
3554
4075
  },
3555
4076
  [submit]
3556
4077
  );
3557
- const respondToInput = (0, import_react2.useCallback)(
4078
+ const respondToInput = (0, import_react3.useCallback)(
3558
4079
  async (response) => {
3559
- if (runRef.current) return;
4080
+ if (handoffBlocksAI.current || runRef.current) return;
3560
4081
  const pending = state.pendingInputs?.find(
3561
4082
  (request) => request.requestId === response.requestId
3562
4083
  );
@@ -3584,7 +4105,8 @@ ${outgoing}` : outgoing;
3584
4105
  },
3585
4106
  [respondToToolInput, runTurn, state.pendingInputs]
3586
4107
  );
3587
- (0, import_react2.useEffect)(() => {
4108
+ (0, import_react3.useEffect)(() => {
4109
+ if (handoff.blocksAI) return;
3588
4110
  const conversation = loadPersistedAgentConversation(
3589
4111
  resolvedStorageKeyPrefix,
3590
4112
  visitorId
@@ -3606,14 +4128,21 @@ ${outgoing}` : outgoing;
3606
4128
  }
3607
4129
  controller.abort();
3608
4130
  };
3609
- }, [identityKey, resolvedStorageKeyPrefix, runTurn, visitorId]);
3610
- (0, import_react2.useEffect)(() => {
4131
+ }, [
4132
+ handoff.blocksAI,
4133
+ identityKey,
4134
+ resolvedStorageKeyPrefix,
4135
+ runTurn,
4136
+ visitorId
4137
+ ]);
4138
+ (0, import_react3.useEffect)(() => {
3611
4139
  return () => {
3612
4140
  runRef.current?.abort();
3613
4141
  runRef.current = null;
3614
4142
  };
3615
4143
  }, []);
3616
4144
  return {
4145
+ handoff,
3617
4146
  state,
3618
4147
  reset,
3619
4148
  retry,
@@ -3642,12 +4171,12 @@ function isAgentBusy(phase) {
3642
4171
  }
3643
4172
 
3644
4173
  // src/react/hooks/useIsMobile.ts
3645
- var import_react3 = require("react");
4174
+ var import_react4 = require("react");
3646
4175
  function useIsMobile(breakpoint = 767) {
3647
- const [isMobile, setIsMobile] = (0, import_react3.useState)(
4176
+ const [isMobile, setIsMobile] = (0, import_react4.useState)(
3648
4177
  () => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
3649
4178
  );
3650
- (0, import_react3.useEffect)(() => {
4179
+ (0, import_react4.useEffect)(() => {
3651
4180
  const media = window.matchMedia(`(max-width: ${breakpoint}px)`);
3652
4181
  const onChange = () => setIsMobile(media.matches);
3653
4182
  onChange();
@@ -3684,7 +4213,7 @@ function unregisterAgentPanelController(customerId) {
3684
4213
  }
3685
4214
 
3686
4215
  // src/react/components/AgentRail/AgentRail.tsx
3687
- var import_react15 = require("react");
4216
+ var import_react16 = require("react");
3688
4217
 
3689
4218
  // src/react/types/conversation.ts
3690
4219
  var defaultAgentRailTheme = {
@@ -3767,7 +4296,7 @@ function agentThemeStyle(theme, resolvedColorScheme) {
3767
4296
  }
3768
4297
 
3769
4298
  // src/react/hooks/useAgentColorScheme.ts
3770
- var import_react4 = require("react");
4299
+ var import_react5 = require("react");
3771
4300
  var DARK_MODE_QUERY = "(prefers-color-scheme: dark)";
3772
4301
  function subscribeToDarkMode(onChange) {
3773
4302
  if (typeof window === "undefined" || !window.matchMedia) {
@@ -3785,7 +4314,7 @@ function getPrefersDarkMode() {
3785
4314
  return typeof window !== "undefined" && Boolean(window.matchMedia?.(DARK_MODE_QUERY).matches);
3786
4315
  }
3787
4316
  function useAgentColorScheme(colorScheme = "auto") {
3788
- const prefersDarkMode = (0, import_react4.useSyncExternalStore)(
4317
+ const prefersDarkMode = (0, import_react5.useSyncExternalStore)(
3789
4318
  subscribeToDarkMode,
3790
4319
  getPrefersDarkMode,
3791
4320
  () => false
@@ -3797,7 +4326,7 @@ function resolveAgentColorScheme(colorScheme = "auto", prefersDarkMode) {
3797
4326
  }
3798
4327
 
3799
4328
  // src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
3800
- var import_react5 = require("react");
4329
+ var import_react6 = require("react");
3801
4330
  var import_jsx_runtime = require("react/jsx-runtime");
3802
4331
  function joinLabels(labels) {
3803
4332
  if (labels.length <= 1) return labels[0] ?? "";
@@ -3916,7 +4445,7 @@ function AgentActivityBubble({
3916
4445
  const statusText = workSummary(steps, failed, brandLabel);
3917
4446
  const softReview = statusText === AGENT_SOFT_REVIEW_STATUS;
3918
4447
  const receiptId = steps.map((step) => step.id).join(":");
3919
- const [expandedReceiptId, setExpandedReceiptId] = (0, import_react5.useState)(
4448
+ const [expandedReceiptId, setExpandedReceiptId] = (0, import_react6.useState)(
3920
4449
  null
3921
4450
  );
3922
4451
  const detailsOpen = !active && expandedReceiptId === receiptId;
@@ -3967,17 +4496,17 @@ function AgentActivityBubble({
3967
4496
  }
3968
4497
 
3969
4498
  // src/react/components/AnswerReceiptDialog/AnswerReceiptDialog.tsx
3970
- var import_react7 = require("react");
4499
+ var import_react8 = require("react");
3971
4500
  var import_react_dom = require("react-dom");
3972
4501
 
3973
4502
  // src/react/components/AgentRail/AgentRailOverlayContext.tsx
3974
- var import_react6 = require("react");
3975
- var AgentRailOverlayContext = (0, import_react6.createContext)(null);
4503
+ var import_react7 = require("react");
4504
+ var AgentRailOverlayContext = (0, import_react7.createContext)(null);
3976
4505
  function useAgentRailPortalRoots() {
3977
- return (0, import_react6.useContext)(AgentRailOverlayContext);
4506
+ return (0, import_react7.useContext)(AgentRailOverlayContext);
3978
4507
  }
3979
4508
  function useAgentRailMenuPortalRoot() {
3980
- return (0, import_react6.useContext)(AgentRailOverlayContext)?.railRef ?? null;
4509
+ return (0, import_react7.useContext)(AgentRailOverlayContext)?.railRef ?? null;
3981
4510
  }
3982
4511
 
3983
4512
  // src/react/components/AnswerReceiptDialog/AnswerReceiptDialog.tsx
@@ -4001,10 +4530,10 @@ function AnswerReceiptDialog({
4001
4530
  }) {
4002
4531
  const portalRoots = useAgentRailPortalRoots();
4003
4532
  const overlayRoot = portalRoots?.overlayRef ?? null;
4004
- const cardRef = (0, import_react7.useRef)(null);
4005
- const closeButtonRef = (0, import_react7.useRef)(null);
4006
- const previouslyFocusedRef = (0, import_react7.useRef)(null);
4007
- (0, import_react7.useEffect)(() => {
4533
+ const cardRef = (0, import_react8.useRef)(null);
4534
+ const closeButtonRef = (0, import_react8.useRef)(null);
4535
+ const previouslyFocusedRef = (0, import_react8.useRef)(null);
4536
+ (0, import_react8.useEffect)(() => {
4008
4537
  previouslyFocusedRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
4009
4538
  closeButtonRef.current?.focus({ preventScroll: true });
4010
4539
  const handleKeyDown = (event) => {
@@ -4089,7 +4618,7 @@ function AnswerReceiptDialog({
4089
4618
  }
4090
4619
 
4091
4620
  // src/react/components/Composer/Composer.tsx
4092
- var import_react8 = require("react");
4621
+ var import_react9 = require("react");
4093
4622
  var import_jsx_runtime3 = require("react/jsx-runtime");
4094
4623
  var FORM_SUBMITTED_MESSAGE = "Shared my details";
4095
4624
  function SendIcon() {
@@ -4151,17 +4680,17 @@ function Composer({
4151
4680
  allowFormResume = true,
4152
4681
  onSubmit
4153
4682
  }) {
4154
- const [value, setValue] = (0, import_react8.useState)("");
4155
- const [draft, setDraft] = (0, import_react8.useState)(() => createDraft(form));
4156
- const inputRef = (0, import_react8.useRef)(null);
4157
- const firstFieldRef = (0, import_react8.useRef)(null);
4158
- const formRef = (0, import_react8.useRef)(null);
4159
- const formId = (0, import_react8.useId)();
4683
+ const [value, setValue] = (0, import_react9.useState)("");
4684
+ const [draft, setDraft] = (0, import_react9.useState)(() => createDraft(form));
4685
+ const inputRef = (0, import_react9.useRef)(null);
4686
+ const firstFieldRef = (0, import_react9.useRef)(null);
4687
+ const formRef = (0, import_react9.useRef)(null);
4688
+ const formId = (0, import_react9.useId)();
4160
4689
  if (form && form.id !== draft.form?.id) setDraft(createDraft(form));
4161
4690
  const savedForm = allowFormResume && !draft.submitted ? draft.form : null;
4162
4691
  const activeForm = !disabled && draft.expanded ? savedForm : null;
4163
4692
  const canSend = !disabled && Boolean(value.trim() || activeForm);
4164
- (0, import_react8.useEffect)(() => {
4693
+ (0, import_react9.useEffect)(() => {
4165
4694
  if (activeForm) firstFieldRef.current?.focus();
4166
4695
  else if ((savedForm || draft.submitted) && !disabled)
4167
4696
  inputRef.current?.focus();
@@ -4422,7 +4951,7 @@ function FollowUpChips({
4422
4951
  }
4423
4952
 
4424
4953
  // src/react/components/MessageBubble/MessageBubble.tsx
4425
- var import_react10 = require("react");
4954
+ var import_react11 = require("react");
4426
4955
 
4427
4956
  // src/react/components/SearchReferences/SearchReferences.tsx
4428
4957
  var import_jsx_runtime5 = require("react/jsx-runtime");
@@ -4513,7 +5042,7 @@ function SearchReferences({
4513
5042
  }
4514
5043
 
4515
5044
  // src/react/components/BookingCard/BookingCard.tsx
4516
- var import_react9 = require("react");
5045
+ var import_react10 = require("react");
4517
5046
  var import_jsx_runtime6 = require("react/jsx-runtime");
4518
5047
  var BOOKING_STEPS = [
4519
5048
  { id: "date", label: "Date" },
@@ -4568,27 +5097,27 @@ function InteractiveBookingCard({
4568
5097
  offer,
4569
5098
  onBook
4570
5099
  }) {
4571
- const fieldId = (0, import_react9.useId)();
5100
+ const fieldId = (0, import_react10.useId)();
4572
5101
  const defaultType = offer.eventTypes[0]?.uri ?? offer.slots[0]?.eventTypeUri ?? "";
4573
- const [step, setStep] = (0, import_react9.useState)("date");
4574
- const [eventTypeUri, setEventTypeUri] = (0, import_react9.useState)(defaultType);
4575
- const [selectedDate, setSelectedDate] = (0, import_react9.useState)("");
4576
- const [startTime, setStartTime] = (0, import_react9.useState)("");
4577
- const [name, setName] = (0, import_react9.useState)("");
4578
- const [email, setEmail] = (0, import_react9.useState)("");
4579
- const activeStepRef = (0, import_react9.useRef)(null);
4580
- const previousStepRef = (0, import_react9.useRef)(step);
5102
+ const [step, setStep] = (0, import_react10.useState)("date");
5103
+ const [eventTypeUri, setEventTypeUri] = (0, import_react10.useState)(defaultType);
5104
+ const [selectedDate, setSelectedDate] = (0, import_react10.useState)("");
5105
+ const [startTime, setStartTime] = (0, import_react10.useState)("");
5106
+ const [name, setName] = (0, import_react10.useState)("");
5107
+ const [email, setEmail] = (0, import_react10.useState)("");
5108
+ const activeStepRef = (0, import_react10.useRef)(null);
5109
+ const previousStepRef = (0, import_react10.useRef)(step);
4581
5110
  const stepIndex = BOOKING_STEPS.findIndex((item) => item.id === step);
4582
- (0, import_react9.useEffect)(() => {
5111
+ (0, import_react10.useEffect)(() => {
4583
5112
  if (previousStepRef.current === step) return;
4584
5113
  previousStepRef.current = step;
4585
5114
  activeStepRef.current?.scrollIntoView({ block: "nearest" });
4586
5115
  }, [step]);
4587
- const slots = (0, import_react9.useMemo)(
5116
+ const slots = (0, import_react10.useMemo)(
4588
5117
  () => bookingSlotsForEventType(offer.slots, eventTypeUri),
4589
5118
  [eventTypeUri, offer.slots]
4590
5119
  );
4591
- const availableByDate = (0, import_react9.useMemo)(() => {
5120
+ const availableByDate = (0, import_react10.useMemo)(() => {
4592
5121
  const next = /* @__PURE__ */ new Map();
4593
5122
  for (const slot of slots) {
4594
5123
  const key = slotDateKey(slot.startTime);
@@ -4596,7 +5125,7 @@ function InteractiveBookingCard({
4596
5125
  }
4597
5126
  return next;
4598
5127
  }, [slots]);
4599
- const [visibleMonth, setVisibleMonth] = (0, import_react9.useState)(
5128
+ const [visibleMonth, setVisibleMonth] = (0, import_react10.useState)(
4600
5129
  () => firstAvailableBookingMonth(slots)
4601
5130
  );
4602
5131
  function selectEventType(nextType) {
@@ -4609,7 +5138,7 @@ function InteractiveBookingCard({
4609
5138
  )
4610
5139
  );
4611
5140
  }
4612
- const daySlots = (0, import_react9.useMemo)(
5141
+ const daySlots = (0, import_react10.useMemo)(
4613
5142
  () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
4614
5143
  [selectedDate, slots]
4615
5144
  );
@@ -4618,7 +5147,7 @@ function InteractiveBookingCard({
4618
5147
  );
4619
5148
  const selectedSample = availableByDate.get(selectedDate) ?? startTime;
4620
5149
  const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
4621
- const weekdays = (0, import_react9.useMemo)(() => weekdayLabels(), []);
5150
+ const weekdays = (0, import_react10.useMemo)(() => weekdayLabels(), []);
4622
5151
  const cells = calendarCells(visibleMonth.year, visibleMonth.month);
4623
5152
  const canPrevMonth = [...availableByDate.keys()].some((key) => {
4624
5153
  const month = monthFromKey(key);
@@ -4904,8 +5433,8 @@ function resolveMessageUrl(safeUrl, baseUrl) {
4904
5433
  // src/react/components/MessageBubble/MessageBubble.tsx
4905
5434
  var import_styles = require("streamdown/styles.css");
4906
5435
  var import_jsx_runtime7 = require("react/jsx-runtime");
4907
- function normalizeDedupeText(text2) {
4908
- return text2.trim().replace(/\s+/g, " ").toLowerCase();
5436
+ function normalizeDedupeText(text3) {
5437
+ return text3.trim().replace(/\s+/g, " ").toLowerCase();
4909
5438
  }
4910
5439
  function paragraphsAreNearDuplicates(first, second) {
4911
5440
  const left = normalizeDedupeText(first);
@@ -4921,8 +5450,8 @@ function paragraphsShareOpening(first, second) {
4921
5450
  if (!opening || opening.length < 20) return false;
4922
5451
  return second.trim().startsWith(opening);
4923
5452
  }
4924
- function collapseRepeatedText(text2) {
4925
- const trimmed = text2.trim();
5453
+ function collapseRepeatedText(text3) {
5454
+ const trimmed = text3.trim();
4926
5455
  if (trimmed.length < 40) return trimmed;
4927
5456
  const paragraphs = trimmed.split(/\n{2,}/u).map((part) => part.trim()).filter(Boolean);
4928
5457
  if (paragraphs.length === 2 && (paragraphsAreNearDuplicates(paragraphs[0], paragraphs[1]) || paragraphsShareOpening(paragraphs[0], paragraphs[1]))) {
@@ -4943,13 +5472,14 @@ function MessageBubble({
4943
5472
  message,
4944
5473
  brandLogoUrl,
4945
5474
  bookingDisabled = false,
5475
+ showProvenance = false,
4946
5476
  bookingReadOnly = false,
4947
5477
  linkBaseUrl,
4948
5478
  offer,
4949
5479
  onBook
4950
5480
  }) {
4951
5481
  const resolvedLogoUrl = brandLogoUrl?.trim();
4952
- const [failedLogoUrl, setFailedLogoUrl] = (0, import_react10.useState)(null);
5482
+ const [failedLogoUrl, setFailedLogoUrl] = (0, import_react11.useState)(null);
4953
5483
  const showBrandLogo = Boolean(resolvedLogoUrl) && failedLogoUrl !== resolvedLogoUrl;
4954
5484
  const cards = message.role === "agent" ? extractToolCards(message.text) : [];
4955
5485
  const extractedOffers = cards.filter(
@@ -4964,7 +5494,34 @@ function MessageBubble({
4964
5494
  if (message.role === "visitor") {
4965
5495
  const visitorText = visitorMessageDisplayText(message).trim();
4966
5496
  if (!visitorText) return null;
4967
- 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 }) });
5497
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5498
+ "article",
5499
+ {
5500
+ className: "message-bubble message-bubble--visitor",
5501
+ "aria-label": "Message from visitor",
5502
+ children: [
5503
+ showProvenance ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__speaker", children: "You \xB7 Visitor" }) : null,
5504
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "message-bubble__text", children: visitorText })
5505
+ ]
5506
+ }
5507
+ );
5508
+ }
5509
+ if (message.role === "human") {
5510
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5511
+ "article",
5512
+ {
5513
+ className: "message-bubble message-bubble--human",
5514
+ "aria-label": `Message from ${message.representative.name}`,
5515
+ children: [
5516
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("strong", { className: "message-bubble__representative", children: [
5517
+ message.representative.name,
5518
+ " ",
5519
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Human representative" })
5520
+ ] }),
5521
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "message-bubble__text", children: message.text })
5522
+ ]
5523
+ }
5524
+ );
4968
5525
  }
4969
5526
  const citations = message.citations ?? [];
4970
5527
  const agentText = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__text", children: [
@@ -5001,40 +5558,48 @@ function MessageBubble({
5001
5558
  ] });
5002
5559
  if (!displayText && !message.searchResults?.length && offers.length === 0)
5003
5560
  return null;
5004
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
5005
- displayText || message.searchResults?.length ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__agent-row", children: [
5006
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5007
- "img",
5008
- {
5009
- src: resolvedLogoUrl,
5010
- alt: "",
5011
- onError: () => {
5012
- setFailedLogoUrl(resolvedLogoUrl ?? null);
5013
- }
5014
- }
5015
- ) }),
5016
- agentText
5017
- ] }) : agentText : null,
5018
- offers.map((nextOffer, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5019
- BookingCard,
5020
- {
5021
- disabled: bookingDisabled,
5022
- readOnly: bookingReadOnly,
5023
- offer: nextOffer,
5024
- onBook
5025
- },
5026
- `${bookingOfferIdentityKey(nextOffer)}-${index}`
5027
- ))
5028
- ] });
5561
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5562
+ "article",
5563
+ {
5564
+ className: "message-bubble message-bubble--agent",
5565
+ "aria-label": "Message from AI assistant",
5566
+ children: [
5567
+ showProvenance ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__speaker", children: "AI assistant" }) : null,
5568
+ displayText || message.searchResults?.length ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__agent-row", children: [
5569
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5570
+ "img",
5571
+ {
5572
+ src: resolvedLogoUrl,
5573
+ alt: "",
5574
+ onError: () => {
5575
+ setFailedLogoUrl(resolvedLogoUrl ?? null);
5576
+ }
5577
+ }
5578
+ ) }),
5579
+ agentText
5580
+ ] }) : agentText : null,
5581
+ offers.map((nextOffer, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5582
+ BookingCard,
5583
+ {
5584
+ disabled: bookingDisabled,
5585
+ readOnly: bookingReadOnly,
5586
+ offer: nextOffer,
5587
+ onBook
5588
+ },
5589
+ `${bookingOfferIdentityKey(nextOffer)}-${index}`
5590
+ ))
5591
+ ]
5592
+ }
5593
+ );
5029
5594
  }
5030
5595
 
5031
5596
  // src/react/components/MessageActions/MessageActions.tsx
5032
- var import_react11 = require("react");
5597
+ var import_react12 = require("react");
5033
5598
  var import_react_dom2 = require("react-dom");
5034
5599
 
5035
5600
  // src/react/lib/speech.ts
5036
- function toSpeechText(text2) {
5037
- let out = hideToolCardFences(text2);
5601
+ function toSpeechText(text3) {
5602
+ let out = hideToolCardFences(text3);
5038
5603
  out = out.replace(/```[\s\S]*?```/g, " ");
5039
5604
  out = out.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
5040
5605
  out = out.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1");
@@ -5369,12 +5934,12 @@ function StopIcon() {
5369
5934
  }
5370
5935
  ) });
5371
5936
  }
5372
- async function writeToClipboard(text2) {
5937
+ async function writeToClipboard(text3) {
5373
5938
  try {
5374
- await navigator.clipboard.writeText(text2);
5939
+ await navigator.clipboard.writeText(text3);
5375
5940
  } catch {
5376
5941
  const textarea = document.createElement("textarea");
5377
- textarea.value = text2;
5942
+ textarea.value = text3;
5378
5943
  textarea.style.position = "fixed";
5379
5944
  textarea.style.opacity = "0";
5380
5945
  document.body.appendChild(textarea);
@@ -5425,26 +5990,26 @@ function MessageActions({
5425
5990
  speechText
5426
5991
  }) {
5427
5992
  const menuPortalRoot = useAgentRailMenuPortalRoot();
5428
- const [copied, setCopied] = (0, import_react11.useState)(false);
5429
- const [rating, setRating] = (0, import_react11.useState)(null);
5430
- const [menuOpen, setMenuOpen] = (0, import_react11.useState)(false);
5431
- const [menuPosition, setMenuPosition] = (0, import_react11.useState)(null);
5432
- const [speaking, setSpeaking] = (0, import_react11.useState)(false);
5433
- const [speechSupported, setSpeechSupported] = (0, import_react11.useState)(null);
5434
- const copyTimerRef = (0, import_react11.useRef)(null);
5435
- const menuRef = (0, import_react11.useRef)(null);
5436
- const portaledMenuRef = (0, import_react11.useRef)(null);
5437
- const moreButtonRef = (0, import_react11.useRef)(null);
5993
+ const [copied, setCopied] = (0, import_react12.useState)(false);
5994
+ const [rating, setRating] = (0, import_react12.useState)(null);
5995
+ const [menuOpen, setMenuOpen] = (0, import_react12.useState)(false);
5996
+ const [menuPosition, setMenuPosition] = (0, import_react12.useState)(null);
5997
+ const [speaking, setSpeaking] = (0, import_react12.useState)(false);
5998
+ const [speechSupported, setSpeechSupported] = (0, import_react12.useState)(null);
5999
+ const copyTimerRef = (0, import_react12.useRef)(null);
6000
+ const menuRef = (0, import_react12.useRef)(null);
6001
+ const portaledMenuRef = (0, import_react12.useRef)(null);
6002
+ const moreButtonRef = (0, import_react12.useRef)(null);
5438
6003
  const answeredLabel = formatAnsweredAt(answeredAt ?? 0);
5439
6004
  const resolvedSpeechText = speechText ?? toSpeechText(copyText);
5440
6005
  const canOpenReceipt = Boolean(receiptSteps) && Boolean(onOpenReceipt);
5441
6006
  const readAloudEligible = Boolean(readAloud && resolvedSpeechText);
5442
6007
  const canReadAloud = readAloudEligible && speechSupported === true;
5443
6008
  const showMenu = canOpenReceipt || (speechSupported === null ? readAloudEligible : canReadAloud);
5444
- (0, import_react11.useLayoutEffect)(() => {
6009
+ (0, import_react12.useLayoutEffect)(() => {
5445
6010
  setSpeechSupported(isSpeechSupported());
5446
6011
  }, []);
5447
- (0, import_react11.useLayoutEffect)(() => {
6012
+ (0, import_react12.useLayoutEffect)(() => {
5448
6013
  if (!menuOpen || !moreButtonRef.current || !portaledMenuRef.current) {
5449
6014
  setMenuPosition(null);
5450
6015
  return;
@@ -5461,7 +6026,7 @@ function MessageActions({
5461
6026
  })
5462
6027
  );
5463
6028
  }, [menuOpen, menuPortalRoot]);
5464
- (0, import_react11.useEffect)(() => {
6029
+ (0, import_react12.useEffect)(() => {
5465
6030
  const unsubscribe = subscribeSpeechInterrupts(() => setSpeaking(false));
5466
6031
  return () => {
5467
6032
  unsubscribe();
@@ -5471,7 +6036,7 @@ function MessageActions({
5471
6036
  stopSpeech();
5472
6037
  };
5473
6038
  }, []);
5474
- (0, import_react11.useEffect)(() => {
6039
+ (0, import_react12.useEffect)(() => {
5475
6040
  if (!menuOpen) return;
5476
6041
  const handlePointerDown = (event) => {
5477
6042
  const target = event.target;
@@ -5640,7 +6205,7 @@ function MessageActions({
5640
6205
  }
5641
6206
 
5642
6207
  // src/react/components/HumanInputCard/HumanInputCard.tsx
5643
- var import_react13 = require("react");
6208
+ var import_react14 = require("react");
5644
6209
 
5645
6210
  // src/react/components/ConfirmationCard/ConfirmationCard.tsx
5646
6211
  var import_jsx_runtime9 = require("react/jsx-runtime");
@@ -5682,7 +6247,7 @@ function ConfirmationCard({
5682
6247
  }
5683
6248
 
5684
6249
  // src/react/components/ToolInputCard/ToolInputCard.tsx
5685
- var import_react12 = require("react");
6250
+ var import_react13 = require("react");
5686
6251
  var import_jsx_runtime10 = require("react/jsx-runtime");
5687
6252
  function isRecord5(value) {
5688
6253
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -5805,13 +6370,13 @@ function valuesMatch(left, right) {
5805
6370
  }
5806
6371
  function FieldDescription({
5807
6372
  field,
5808
- id
6373
+ id: id2
5809
6374
  }) {
5810
- return field.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id, className: "tool-input-card__description", children: field.description }) : null;
6375
+ return field.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: id2, className: "tool-input-card__description", children: field.description }) : null;
5811
6376
  }
5812
6377
  function ChoiceField({
5813
6378
  field,
5814
- id,
6379
+ id: id2,
5815
6380
  value,
5816
6381
  disabled,
5817
6382
  describedBy,
@@ -5829,7 +6394,7 @@ function ChoiceField({
5829
6394
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
5830
6395
  "select",
5831
6396
  {
5832
- id,
6397
+ id: id2,
5833
6398
  value: selectedIndex >= 0 ? String(selectedIndex) : "",
5834
6399
  disabled,
5835
6400
  required: field.required,
@@ -5852,7 +6417,7 @@ function ChoiceField({
5852
6417
  {
5853
6418
  className: "tool-input-card__choices",
5854
6419
  role: multiple ? "group" : "radiogroup",
5855
- "aria-labelledby": `${id}-label`,
6420
+ "aria-labelledby": `${id2}-label`,
5856
6421
  "aria-describedby": describedBy,
5857
6422
  "aria-invalid": Boolean(error),
5858
6423
  "aria-required": field.required,
@@ -5863,7 +6428,7 @@ function ChoiceField({
5863
6428
  "input",
5864
6429
  {
5865
6430
  type: multiple ? "checkbox" : "radio",
5866
- name: id,
6431
+ name: id2,
5867
6432
  value: String(index),
5868
6433
  checked,
5869
6434
  disabled,
@@ -5891,22 +6456,22 @@ function ToolField({
5891
6456
  disabled,
5892
6457
  error,
5893
6458
  field,
5894
- id,
6459
+ id: id2,
5895
6460
  value,
5896
6461
  onBlur,
5897
6462
  onChange
5898
6463
  }) {
5899
6464
  const describedBy = [
5900
- field.description ? `${id}-description` : "",
5901
- error ? `${id}-error` : ""
6465
+ field.description ? `${id2}-description` : "",
6466
+ error ? `${id2}-error` : ""
5902
6467
  ].filter(Boolean).join(" ");
5903
6468
  if (field.kind === "checkbox" || field.kind === "confirmation") {
5904
6469
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tool-input-card__field", children: [
5905
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "tool-input-card__check", htmlFor: id, children: [
6470
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "tool-input-card__check", htmlFor: id2, children: [
5906
6471
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5907
6472
  "input",
5908
6473
  {
5909
- id,
6474
+ id: id2,
5910
6475
  type: "checkbox",
5911
6476
  checked: value === true,
5912
6477
  disabled,
@@ -5918,18 +6483,18 @@ function ToolField({
5918
6483
  ),
5919
6484
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
5920
6485
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: field.label }),
5921
- field.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id}-description`, children: field.description }) : null
6486
+ field.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id2}-description`, children: field.description }) : null
5922
6487
  ] })
5923
6488
  ] }),
5924
- error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id}-error`, className: "tool-input-card__error", children: error }) : null
6489
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id2}-error`, className: "tool-input-card__error", children: error }) : null
5925
6490
  ] });
5926
6491
  }
5927
- const label = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { id: `${id}-label`, htmlFor: id, children: [
6492
+ const label = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { id: `${id2}-label`, htmlFor: id2, children: [
5928
6493
  field.label,
5929
6494
  field.required ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: " *" }) : null
5930
6495
  ] });
5931
6496
  const common = {
5932
- id,
6497
+ id: id2,
5933
6498
  disabled,
5934
6499
  required: field.required,
5935
6500
  "aria-describedby": describedBy || void 0,
@@ -5942,7 +6507,7 @@ function ToolField({
5942
6507
  ChoiceField,
5943
6508
  {
5944
6509
  field,
5945
- id,
6510
+ id: id2,
5946
6511
  value,
5947
6512
  disabled,
5948
6513
  describedBy: describedBy || void 0,
@@ -5979,7 +6544,7 @@ function ToolField({
5979
6544
  onChange: (event) => onChange(Number(event.target.value))
5980
6545
  }
5981
6546
  ),
5982
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("output", { htmlFor: id, children: numericValue })
6547
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("output", { htmlFor: id2, children: numericValue })
5983
6548
  ] });
5984
6549
  } else {
5985
6550
  const type = field.kind === "date-time" ? "datetime-local" : field.kind === "calendar" ? "date" : field.kind;
@@ -6003,9 +6568,9 @@ function ToolField({
6003
6568
  }
6004
6569
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tool-input-card__field", children: [
6005
6570
  label,
6006
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(FieldDescription, { field, id: `${id}-description` }),
6571
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(FieldDescription, { field, id: `${id2}-description` }),
6007
6572
  control,
6008
- error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id}-error`, className: "tool-input-card__error", children: error }) : null
6573
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { id: `${id2}-error`, className: "tool-input-card__error", children: error }) : null
6009
6574
  ] });
6010
6575
  }
6011
6576
  function ToolInputCard({
@@ -6013,11 +6578,11 @@ function ToolInputCard({
6013
6578
  surface,
6014
6579
  onSubmit
6015
6580
  }) {
6016
- const [values, setValues] = (0, import_react12.useState)(
6581
+ const [values, setValues] = (0, import_react13.useState)(
6017
6582
  () => initialValues(surface)
6018
6583
  );
6019
- const [touched, setTouched] = (0, import_react12.useState)(() => /* @__PURE__ */ new Set());
6020
- const [submitted, setSubmitted] = (0, import_react12.useState)(false);
6584
+ const [touched, setTouched] = (0, import_react13.useState)(() => /* @__PURE__ */ new Set());
6585
+ const [submitted, setSubmitted] = (0, import_react13.useState)(false);
6021
6586
  const errors = Object.fromEntries(
6022
6587
  surface.fields.map((field) => [
6023
6588
  field.path,
@@ -6110,7 +6675,7 @@ function HumanInputCard({
6110
6675
  request,
6111
6676
  onRespond
6112
6677
  }) {
6113
- const [text2, setText] = (0, import_react13.useState)("");
6678
+ const [text3, setText] = (0, import_react14.useState)("");
6114
6679
  const options = request.options ?? [];
6115
6680
  const showText = request.display === "text" || request.allowFreeform && options.length === 0;
6116
6681
  if (request.ui) {
@@ -6135,7 +6700,7 @@ function HumanInputCard({
6135
6700
  }
6136
6701
  function submitText(event) {
6137
6702
  event.preventDefault();
6138
- const value = text2.trim();
6703
+ const value = text3.trim();
6139
6704
  if (!value || disabled) return;
6140
6705
  onRespond?.({ requestId: request.requestId, text: value });
6141
6706
  }
@@ -6153,12 +6718,12 @@ function HumanInputCard({
6153
6718
  "input",
6154
6719
  {
6155
6720
  id: `human-input-text-${request.requestId}`,
6156
- value: text2,
6721
+ value: text3,
6157
6722
  disabled,
6158
6723
  onChange: (event) => setText(event.target.value)
6159
6724
  }
6160
6725
  ),
6161
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "submit", disabled: disabled || !text2.trim(), children: "Send" })
6726
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "submit", disabled: disabled || !text3.trim(), children: "Send" })
6162
6727
  ] })
6163
6728
  ] }) : null,
6164
6729
  !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
@@ -6168,7 +6733,7 @@ function HumanInputCard({
6168
6733
  }
6169
6734
 
6170
6735
  // src/react/components/LocationConsent/LocationConsent.tsx
6171
- var import_react14 = require("react");
6736
+ var import_react15 = require("react");
6172
6737
 
6173
6738
  // src/runtime/location-consent.ts
6174
6739
  function isLocationConsentRequest(request) {
@@ -6216,16 +6781,16 @@ function LocationConsent({
6216
6781
  request,
6217
6782
  onRespond
6218
6783
  }) {
6219
- const [preference, setPreference] = (0, import_react14.useState)("unset");
6220
- const [locating, setLocating] = (0, import_react14.useState)(false);
6221
- const [error, setError] = (0, import_react14.useState)(null);
6222
- const respond = (0, import_react14.useRef)(onRespond);
6223
- const answered = (0, import_react14.useRef)(null);
6784
+ const [preference, setPreference] = (0, import_react15.useState)("unset");
6785
+ const [locating, setLocating] = (0, import_react15.useState)(false);
6786
+ const [error, setError] = (0, import_react15.useState)(null);
6787
+ const respond = (0, import_react15.useRef)(onRespond);
6788
+ const answered = (0, import_react15.useRef)(null);
6224
6789
  const requestId = request?.requestId;
6225
- (0, import_react14.useEffect)(() => {
6790
+ (0, import_react15.useEffect)(() => {
6226
6791
  respond.current = onRespond;
6227
6792
  }, [onRespond]);
6228
- (0, import_react14.useEffect)(() => {
6793
+ (0, import_react15.useEffect)(() => {
6229
6794
  if (!requestId || preference === "unset" || answered.current === requestId || !respond.current)
6230
6795
  return;
6231
6796
  if (preference === "off") {
@@ -6567,6 +7132,7 @@ function ChevronDownIcon() {
6567
7132
  var DEFAULT_DISCLAIMER_LABEL = "Agent can make mistakes. Check important info.";
6568
7133
  function AgentRail({
6569
7134
  state,
7135
+ handoff,
6570
7136
  theme,
6571
7137
  colorScheme = "auto",
6572
7138
  brandLabel = "",
@@ -6592,44 +7158,51 @@ function AgentRail({
6592
7158
  onInputResponse,
6593
7159
  onToolInput
6594
7160
  }) {
6595
- const railRef = (0, import_react15.useRef)(null);
6596
- const overlayRef = (0, import_react15.useRef)(null);
6597
- const transcriptRef = (0, import_react15.useRef)(null);
6598
- const responseRef = (0, import_react15.useRef)(null);
6599
- const threadRef = (0, import_react15.useRef)(null);
6600
- const lastScrolledVisitorIdRef = (0, import_react15.useRef)(void 0);
6601
- const pinnedToBottomRef = (0, import_react15.useRef)(true);
6602
- const smoothScrollToLatestRef = (0, import_react15.useRef)(false);
6603
- const lockedTranscriptScrollTopRef = (0, import_react15.useRef)(null);
6604
- const [showJumpToLatest, setShowJumpToLatest] = (0, import_react15.useState)(false);
6605
- const [receiptOpen, setReceiptOpen] = (0, import_react15.useState)(false);
7161
+ const railRef = (0, import_react16.useRef)(null);
7162
+ const overlayRef = (0, import_react16.useRef)(null);
7163
+ const transcriptRef = (0, import_react16.useRef)(null);
7164
+ const responseRef = (0, import_react16.useRef)(null);
7165
+ const threadRef = (0, import_react16.useRef)(null);
7166
+ const lastScrolledVisitorIdRef = (0, import_react16.useRef)(void 0);
7167
+ const pinnedToBottomRef = (0, import_react16.useRef)(true);
7168
+ const smoothScrollToLatestRef = (0, import_react16.useRef)(false);
7169
+ const lockedTranscriptScrollTopRef = (0, import_react16.useRef)(null);
7170
+ const [showJumpToLatest, setShowJumpToLatest] = (0, import_react16.useState)(false);
7171
+ const [receiptOpen, setReceiptOpen] = (0, import_react16.useState)(false);
6606
7172
  const resolvedBrandLabel = brandLabel.trim();
6607
7173
  const resolvedBrandLogoUrl = brandLogoUrl?.trim();
6608
7174
  const resolvedDisclaimerLabel = disclaimerLabel === void 0 ? DEFAULT_DISCLAIMER_LABEL : disclaimerLabel;
6609
- const [failedLogoUrl, setFailedLogoUrl] = (0, import_react15.useState)(null);
7175
+ const [failedLogoUrl, setFailedLogoUrl] = (0, import_react16.useState)(null);
6610
7176
  const showBrandLogo = Boolean(resolvedBrandLogoUrl) && failedLogoUrl !== resolvedBrandLogoUrl;
6611
7177
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
6612
7178
  const railStyle = agentThemeStyle(theme, resolvedColorScheme);
6613
- const locationRequest = state.pendingInputs?.find(isLocationConsentRequest);
7179
+ const handoffActive = Boolean(handoff?.blocksAI);
7180
+ const handoffStatus = handoff?.page?.status;
7181
+ const locationRequest = handoffActive ? void 0 : state.pendingInputs?.find(isLocationConsentRequest);
6614
7182
  const pendingInputRequests = (state.pendingInputs ?? []).filter(
6615
- (request) => !isLocationConsentRequest(request) && shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
7183
+ (request) => !handoffActive && !isLocationConsentRequest(request) && shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
6616
7184
  );
6617
7185
  const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming" || state.phase === "waiting-input" && (pendingInputRequests.length > 0 || Boolean(locationRequest));
6618
- const semanticSurfaceDisabled = isBusy && state.phase !== "waiting-input";
7186
+ const semanticSurfaceDisabled = handoffActive || isBusy && state.phase !== "waiting-input";
6619
7187
  const visitorToolResults = (state.toolResults ?? []).filter(
6620
7188
  isRenderableVisitorToolResult
6621
7189
  );
6622
7190
  const activeVisitorToolInput = [...visitorToolResults].reverse().find((result) => result.kind === "input");
6623
7191
  const visibleVisitorToolResults = activeVisitorToolInput ? [activeVisitorToolInput] : visitorToolResults;
6624
- const activityActive = state.toolSteps.some((step) => step.state === "active");
7192
+ const activityActive = state.toolSteps.some(
7193
+ (step) => step.state === "active"
7194
+ );
6625
7195
  const showActivity = state.toolSteps.length > 0 && pendingInputRequests.length === 0 && !state.pendingOffer && (activityActive || isBusy && !state.streamingText);
6626
- const activitySteps = activityActive || !isBusy || state.streamingText ? state.toolSteps : [...state.toolSteps, {
6627
- id: "preparing-answer",
6628
- kind: "tool",
6629
- label: "Preparing your answer",
6630
- detail: "Preparing your answer",
6631
- state: "active"
6632
- }];
7196
+ const activitySteps = activityActive || !isBusy || state.streamingText ? state.toolSteps : [
7197
+ ...state.toolSteps,
7198
+ {
7199
+ id: "preparing-answer",
7200
+ kind: "tool",
7201
+ label: "Preparing your answer",
7202
+ detail: "Preparing your answer",
7203
+ state: "active"
7204
+ }
7205
+ ];
6633
7206
  const hasVisitorMessages2 = state.messages.some(
6634
7207
  (message) => message.role === "visitor"
6635
7208
  );
@@ -6658,7 +7231,7 @@ function AgentRail({
6658
7231
  }
6659
7232
  }
6660
7233
  const lastIsAgent = lastMessage?.role === "agent";
6661
- const showMessageActions = state.phase === "complete" && lastIsAgent && hasVisitorMessages2 && Boolean(hideToolCardFences(lastMessage.text).trim());
7234
+ const showMessageActions = !handoffActive && state.phase === "complete" && lastIsAgent && hasVisitorMessages2 && Boolean(hideToolCardFences(lastMessage.text).trim());
6662
7235
  const streamingMessage = state.streamingText && !lastIsAgent ? {
6663
7236
  createdAt: 0,
6664
7237
  id: "streaming-response",
@@ -6700,7 +7273,7 @@ function AgentRail({
6700
7273
  ...visibleVisitorToolResults
6701
7274
  ].reverse().find((result) => result.kind !== "input")?.id;
6702
7275
  const receiptSteps = answerReceipt && state.toolSteps.length > 0 ? state.toolSteps : void 0;
6703
- (0, import_react15.useEffect)(() => {
7276
+ (0, import_react16.useEffect)(() => {
6704
7277
  if (state.phase !== "complete") {
6705
7278
  setReceiptOpen(false);
6706
7279
  }
@@ -6722,7 +7295,7 @@ function AgentRail({
6722
7295
  onFollowUpSelect?.(label);
6723
7296
  }
6724
7297
  const latestVisitorId = visibleMessages[lastVisitorIndex]?.id;
6725
- (0, import_react15.useEffect)(() => {
7298
+ (0, import_react16.useEffect)(() => {
6726
7299
  const node = transcriptRef.current;
6727
7300
  if (!node) return;
6728
7301
  if (latestVisitorId !== lastScrolledVisitorIdRef.current) {
@@ -6752,7 +7325,7 @@ function AgentRail({
6752
7325
  state.followUps,
6753
7326
  state.journey
6754
7327
  ]);
6755
- (0, import_react15.useEffect)(() => {
7328
+ (0, import_react16.useEffect)(() => {
6756
7329
  const node = transcriptRef.current;
6757
7330
  if (!node) return;
6758
7331
  const handleScroll = () => {
@@ -6766,7 +7339,7 @@ function AgentRail({
6766
7339
  handleScroll();
6767
7340
  return () => node.removeEventListener("scroll", handleScroll);
6768
7341
  }, []);
6769
- (0, import_react15.useEffect)(() => {
7342
+ (0, import_react16.useEffect)(() => {
6770
7343
  if (!receiptOpen) {
6771
7344
  lockedTranscriptScrollTopRef.current = null;
6772
7345
  return;
@@ -6818,249 +7391,302 @@ function AgentRail({
6818
7391
  autoFocus: mobileFullscreen || expanded,
6819
7392
  role: mobileFullscreen || expanded ? "dialog" : void 0,
6820
7393
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
6821
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
6822
- AgentRailOverlayContext.Provider,
6823
- {
6824
- value: { railRef, overlayRef },
6825
- children: [
6826
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__surface", inert: receiptOpen || void 0, children: [
6827
- /* @__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: [
6828
- onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6829
- "button",
6830
- {
6831
- type: "button",
6832
- className: "agent-rail__collapse",
6833
- "aria-label": "Collapse assist",
6834
- onClick: onCollapse,
6835
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MinimizeIcon, {})
6836
- }
6837
- ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6838
- "button",
6839
- {
6840
- type: "button",
6841
- className: "agent-rail__close",
6842
- "aria-label": "Close agent",
6843
- onClick: onClose,
6844
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CloseIcon2, {})
7394
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(AgentRailOverlayContext.Provider, { value: { railRef, overlayRef }, children: [
7395
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__surface", inert: receiptOpen || void 0, children: [
7396
+ /* @__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: [
7397
+ onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7398
+ "button",
7399
+ {
7400
+ type: "button",
7401
+ className: "agent-rail__collapse",
7402
+ "aria-label": "Collapse assist",
7403
+ onClick: onCollapse,
7404
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MinimizeIcon, {})
7405
+ }
7406
+ ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7407
+ "button",
7408
+ {
7409
+ type: "button",
7410
+ className: "agent-rail__close",
7411
+ "aria-label": "Close agent",
7412
+ onClick: onClose,
7413
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CloseIcon2, {})
7414
+ }
7415
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
7416
+ resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "agent-rail__identity", children: [
7417
+ showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7418
+ "img",
7419
+ {
7420
+ className: "agent-rail__brand-logo",
7421
+ src: resolvedBrandLogoUrl,
7422
+ alt: "",
7423
+ onError: () => {
7424
+ setFailedLogoUrl(resolvedBrandLogoUrl ?? null);
6845
7425
  }
6846
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
6847
- resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "agent-rail__identity", children: [
6848
- showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6849
- "img",
6850
- {
6851
- className: "agent-rail__brand-logo",
6852
- src: resolvedBrandLogoUrl,
6853
- alt: "",
6854
- onError: () => {
6855
- setFailedLogoUrl(resolvedBrandLogoUrl ?? null);
6856
- }
6857
- }
6858
- ) }) : null,
6859
- resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
6860
- ] }) : null,
6861
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "agent-rail__actions", children: [
6862
- onReset ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6863
- "button",
6864
- {
6865
- type: "button",
6866
- className: "agent-rail__new-chat",
6867
- "aria-label": "Start a new conversation",
6868
- disabled: !hasVisitorMessages2,
6869
- onClick: handleReset,
6870
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(NewChatIcon, {})
6871
- }
6872
- ) : null,
6873
- onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6874
- "button",
7426
+ }
7427
+ ) }) : null,
7428
+ resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
7429
+ ] }) : null,
7430
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "agent-rail__actions", children: [
7431
+ onReset ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7432
+ "button",
7433
+ {
7434
+ type: "button",
7435
+ className: "agent-rail__new-chat",
7436
+ "aria-label": "Start a new conversation",
7437
+ disabled: !hasVisitorMessages2 || handoffActive && !handoff?.canReset,
7438
+ onClick: handleReset,
7439
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(NewChatIcon, {})
7440
+ }
7441
+ ) : null,
7442
+ onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7443
+ "button",
7444
+ {
7445
+ type: "button",
7446
+ className: "agent-rail__expand",
7447
+ "aria-label": expanded ? "Exit full screen" : "Open full screen",
7448
+ onClick: onExpandToggle,
7449
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ExpandIcon, {})
7450
+ }
7451
+ ) : null
7452
+ ] })
7453
+ ] }) }),
7454
+ /* @__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: [
7455
+ !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
7456
+ greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7457
+ MessageBubble,
7458
+ {
7459
+ showProvenance: Boolean(
7460
+ handoff?.page?.enabled || handoffActive
7461
+ ),
7462
+ message: greeting,
7463
+ bookingDisabled: isBusy || handoffActive,
7464
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
7465
+ onBook
7466
+ }
7467
+ ) : null,
7468
+ showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7469
+ FollowUpChips,
7470
+ {
7471
+ suggestions: state.followUps,
7472
+ disabled: isBusy || handoffActive,
7473
+ label: "Start here",
7474
+ onSelect: (suggestion) => handleFollowUpSelect(suggestion.label)
7475
+ }
7476
+ ) }) : null,
7477
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7478
+ AgentActivityBubble,
7479
+ {
7480
+ brandLabel: resolvedBrandLabel,
7481
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
7482
+ failed: state.phase === "error",
7483
+ steps: activitySteps
7484
+ }
7485
+ ) : null,
7486
+ visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7487
+ VisitorToolResultView,
7488
+ {
7489
+ result,
7490
+ disabled: semanticSurfaceDisabled,
7491
+ onToolInput
7492
+ },
7493
+ result.id
7494
+ )),
7495
+ pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7496
+ HumanInputCard,
7497
+ {
7498
+ request,
7499
+ onRespond: onInputResponse
7500
+ },
7501
+ request.requestId
7502
+ ))
7503
+ ] }) : null,
7504
+ transcriptMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
7505
+ "div",
7506
+ {
7507
+ ref: index === lastVisitorIndex + 1 ? responseRef : void 0,
7508
+ className: "agent-rail__turn-block",
7509
+ children: [
7510
+ message.role === "agent" ? message.toolResults?.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7511
+ VisitorToolResultView,
6875
7512
  {
6876
- type: "button",
6877
- className: "agent-rail__expand",
6878
- "aria-label": expanded ? "Exit full screen" : "Open full screen",
6879
- onClick: onExpandToggle,
6880
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ExpandIcon, {})
6881
- }
6882
- ) : null
6883
- ] })
6884
- ] }) }),
6885
- /* @__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: [
6886
- !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
6887
- greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7513
+ result
7514
+ },
7515
+ result.id
7516
+ )) : null,
7517
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6888
7518
  MessageBubble,
6889
7519
  {
6890
- message: greeting,
6891
- bookingDisabled: isBusy,
7520
+ showProvenance: Boolean(
7521
+ handoff?.page?.enabled || handoffActive
7522
+ ),
7523
+ message,
7524
+ bookingDisabled: isBusy || handoffActive,
6892
7525
  brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
7526
+ offer: index === transcriptMessages.length - 1 ? state.pendingOffer : void 0,
6893
7527
  onBook
6894
7528
  }
6895
- ) : null,
6896
- showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6897
- FollowUpChips,
7529
+ ),
7530
+ index === lastAgentIndex && showMessageActions && message.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7531
+ MessageActions,
6898
7532
  {
6899
- suggestions: state.followUps,
6900
- disabled: isBusy,
6901
- label: "Start here",
6902
- onSelect: (suggestion) => handleFollowUpSelect(suggestion.label)
6903
- }
6904
- ) }) : null,
6905
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6906
- AgentActivityBubble,
6907
- {
6908
- brandLabel: resolvedBrandLabel,
6909
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6910
- failed: state.phase === "error",
6911
- steps: activitySteps
7533
+ answeredAt: message.createdAt,
7534
+ copyText: hideToolCardFences(message.text).trim() || message.text,
7535
+ readAloud,
7536
+ receiptSteps,
7537
+ onOpenReceipt: receiptSteps ? openReceipt : void 0,
7538
+ onRegenerate: onRegenerate ? handleRegenerate : void 0,
7539
+ onFeedback: onFeedback ? (rating) => onFeedback(rating, message) : void 0
6912
7540
  }
6913
7541
  ) : null,
6914
- visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6915
- VisitorToolResultView,
6916
- {
6917
- result,
6918
- disabled: semanticSurfaceDisabled,
6919
- onToolInput
6920
- },
6921
- result.id
6922
- )),
6923
- pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6924
- HumanInputCard,
6925
- {
6926
- request,
6927
- onRespond: onInputResponse
6928
- },
6929
- request.requestId
6930
- ))
6931
- ] }) : null,
6932
- transcriptMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
6933
- "div",
6934
- {
6935
- ref: index === lastVisitorIndex + 1 ? responseRef : void 0,
6936
- className: "agent-rail__turn-block",
6937
- children: [
6938
- message.role === "agent" ? message.toolResults?.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VisitorToolResultView, { result }, result.id)) : null,
6939
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6940
- MessageBubble,
6941
- {
6942
- message,
6943
- bookingDisabled: isBusy,
6944
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6945
- offer: index === transcriptMessages.length - 1 ? state.pendingOffer : void 0,
6946
- onBook
6947
- }
6948
- ),
6949
- index === lastAgentIndex && showMessageActions && message.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6950
- MessageActions,
6951
- {
6952
- answeredAt: message.createdAt,
6953
- copyText: hideToolCardFences(message.text).trim() || message.text,
6954
- readAloud,
6955
- receiptSteps,
6956
- onOpenReceipt: receiptSteps ? openReceipt : void 0,
6957
- onRegenerate: onRegenerate ? handleRegenerate : void 0,
6958
- onFeedback: onFeedback ? (rating) => onFeedback(rating, message) : void 0
6959
- }
6960
- ) : null,
6961
- index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
6962
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6963
- AgentActivityBubble,
6964
- {
6965
- brandLabel: resolvedBrandLabel,
6966
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6967
- failed: state.phase === "error",
6968
- steps: activitySteps
6969
- }
6970
- ) : null,
6971
- visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6972
- VisitorToolResultView,
6973
- {
6974
- result,
6975
- disabled: semanticSurfaceDisabled,
6976
- onToolInput
6977
- },
6978
- result.id
6979
- )),
6980
- pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6981
- HumanInputCard,
6982
- {
6983
- request,
6984
- onRespond: onInputResponse
6985
- },
6986
- request.requestId
6987
- ))
6988
- ] }) : null
6989
- ]
6990
- },
6991
- message.role === "agent" && transcriptMessages[index - 1]?.role === "visitor" ? `answer-${transcriptMessages[index - 1]?.id}` : message.id
6992
- )),
6993
- waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(BookingCardLoader, {}) : null,
6994
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
6995
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
6996
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: "Something went wrong" }),
6997
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: state.error })
6998
- ] }),
6999
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
7000
- ] }) : null
7001
- ] }) }),
7002
- 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)(
7003
- "a",
7542
+ index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
7543
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7544
+ AgentActivityBubble,
7545
+ {
7546
+ brandLabel: resolvedBrandLabel,
7547
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
7548
+ failed: state.phase === "error",
7549
+ steps: activitySteps
7550
+ }
7551
+ ) : null,
7552
+ visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7553
+ VisitorToolResultView,
7554
+ {
7555
+ result,
7556
+ disabled: semanticSurfaceDisabled,
7557
+ onToolInput
7558
+ },
7559
+ result.id
7560
+ )),
7561
+ pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7562
+ HumanInputCard,
7563
+ {
7564
+ request,
7565
+ onRespond: onInputResponse
7566
+ },
7567
+ request.requestId
7568
+ ))
7569
+ ] }) : null
7570
+ ]
7571
+ },
7572
+ message.role === "agent" && transcriptMessages[index - 1]?.role === "visitor" ? `answer-${transcriptMessages[index - 1]?.id}` : message.id
7573
+ )),
7574
+ waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(BookingCardLoader, {}) : null,
7575
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
7576
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
7577
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: "Something went wrong" }),
7578
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: state.error })
7579
+ ] }),
7580
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
7581
+ ] }) : null
7582
+ ] }) }),
7583
+ 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)(
7584
+ "a",
7585
+ {
7586
+ href: disclaimerLink,
7587
+ rel: "noopener noreferrer",
7588
+ target: "_blank",
7589
+ children: resolvedDisclaimerLabel
7590
+ }
7591
+ ) : resolvedDisclaimerLabel }) }) : null,
7592
+ handoff && (handoff.page?.enabled || handoffActive) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__handoff", children: [
7593
+ /* @__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?" }),
7594
+ handoffStatus === "ai" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7595
+ "button",
7596
+ {
7597
+ type: "button",
7598
+ disabled: handoff.busy || handoff.hasPending || isBusy,
7599
+ onClick: () => void handoff.start(),
7600
+ children: "Talk to a person"
7601
+ }
7602
+ ) : null,
7603
+ handoffStatus === "resolved" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7604
+ "button",
7605
+ {
7606
+ type: "button",
7607
+ disabled: handoff.busy || handoff.hasPending,
7608
+ onClick: () => void handoff.returnToAI(),
7609
+ children: "Return to AI"
7610
+ }
7611
+ ) : null,
7612
+ handoffStatus === "failed" && onReset ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7613
+ "button",
7614
+ {
7615
+ type: "button",
7616
+ disabled: !handoff.canReset,
7617
+ onClick: handleReset,
7618
+ children: "Start a new conversation"
7619
+ }
7620
+ ) : null,
7621
+ handoff.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { role: "alert", children: [
7622
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: handoff.error }),
7623
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7624
+ "button",
7004
7625
  {
7005
- href: disclaimerLink,
7006
- rel: "noopener noreferrer",
7007
- target: "_blank",
7008
- children: resolvedDisclaimerLabel
7626
+ type: "button",
7627
+ disabled: handoff.busy,
7628
+ onClick: () => void handoff.retry(),
7629
+ children: "Retry"
7009
7630
  }
7010
- ) : resolvedDisclaimerLabel }) }) : null,
7011
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
7012
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7013
- LocationConsent,
7014
- {
7015
- request: locationRequest,
7016
- onRespond: onInputResponse
7017
- },
7018
- state.messages.find((message) => message.role === "visitor")?.id ?? "empty"
7019
- ),
7020
- showJumpToLatest ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7021
- "button",
7022
- {
7023
- type: "button",
7024
- className: "agent-rail__jump-to-latest",
7025
- "aria-label": "Jump to the latest message",
7026
- title: "Jump to the latest message",
7027
- onClick: scrollToLatest,
7028
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChevronDownIcon, {})
7029
- }
7030
- ) : null,
7031
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7032
- Composer,
7033
- {
7034
- variant: expanded || mobileFullscreen ? "dock" : "default",
7035
- disabled: isBusy,
7036
- form: composerForm && lastMessage ? { ...composerForm, id: `${lastMessage.id}:${composerForm.id}` } : null,
7037
- allowFormResume: !state.pendingOffer && !waitingForBooking && !hasPendingConfirmation,
7038
- placeholder: composerPlaceholder,
7039
- onSubmit: handleSubmit
7040
- },
7041
- `${state.messages.find((message) => message.role === "visitor")?.id ?? "empty"}:${latestResultId ?? ""}`
7042
- ),
7043
- 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
7044
- ] })
7045
- ] }),
7046
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: overlayRef, className: "agent-rail__overlay" }),
7047
- receiptOpen && receiptSteps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7048
- AnswerReceiptDialog,
7631
+ )
7632
+ ] }) : null
7633
+ ] }) : null,
7634
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
7635
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7636
+ LocationConsent,
7637
+ {
7638
+ request: locationRequest,
7639
+ onRespond: onInputResponse
7640
+ },
7641
+ state.messages.find((message) => message.role === "visitor")?.id ?? "empty"
7642
+ ),
7643
+ showJumpToLatest ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7644
+ "button",
7049
7645
  {
7050
- brandLabel: resolvedBrandLabel,
7051
- onClose: () => setReceiptOpen(false),
7052
- steps: receiptSteps
7646
+ type: "button",
7647
+ className: "agent-rail__jump-to-latest",
7648
+ "aria-label": "Jump to the latest message",
7649
+ title: "Jump to the latest message",
7650
+ onClick: scrollToLatest,
7651
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChevronDownIcon, {})
7053
7652
  }
7054
- ) : null
7055
- ]
7056
- }
7057
- )
7653
+ ) : null,
7654
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7655
+ Composer,
7656
+ {
7657
+ variant: expanded || mobileFullscreen ? "dock" : "default",
7658
+ disabled: isBusy || Boolean(handoff?.busy || handoff?.hasPending) || handoffActive && !["requesting", "queued", "human"].includes(
7659
+ handoffStatus ?? ""
7660
+ ),
7661
+ form: !handoffActive && composerForm && lastMessage ? {
7662
+ ...composerForm,
7663
+ id: `${lastMessage.id}:${composerForm.id}`
7664
+ } : null,
7665
+ allowFormResume: !handoffActive && !state.pendingOffer && !waitingForBooking && !hasPendingConfirmation,
7666
+ placeholder: handoffActive ? "Message support\u2026" : composerPlaceholder,
7667
+ onSubmit: handleSubmit
7668
+ },
7669
+ `${state.messages.find((message) => message.role === "visitor")?.id ?? "empty"}:${latestResultId ?? ""}`
7670
+ ),
7671
+ 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
7672
+ ] })
7673
+ ] }),
7674
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: overlayRef, className: "agent-rail__overlay" }),
7675
+ receiptOpen && receiptSteps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
7676
+ AnswerReceiptDialog,
7677
+ {
7678
+ brandLabel: resolvedBrandLabel,
7679
+ onClose: () => setReceiptOpen(false),
7680
+ steps: receiptSteps
7681
+ }
7682
+ ) : null
7683
+ ] })
7058
7684
  }
7059
7685
  );
7060
7686
  }
7061
7687
 
7062
7688
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
7063
- var import_react16 = require("react");
7689
+ var import_react17 = require("react");
7064
7690
  var import_jsx_runtime19 = require("react/jsx-runtime");
7065
7691
  function ChatSparkIcon() {
7066
7692
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
@@ -7141,8 +7767,8 @@ function TabMarkIcon({
7141
7767
  }) {
7142
7768
  const custom = customIconUrl?.trim();
7143
7769
  const logo = logoUrl?.trim();
7144
- const [customFailed, setCustomFailed] = (0, import_react16.useState)(false);
7145
- const [logoFailed, setLogoFailed] = (0, import_react16.useState)(false);
7770
+ const [customFailed, setCustomFailed] = (0, import_react17.useState)(false);
7771
+ const [logoFailed, setLogoFailed] = (0, import_react17.useState)(false);
7146
7772
  if (custom && !customFailed) {
7147
7773
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7148
7774
  "img",
@@ -7330,8 +7956,8 @@ function findPrecedingVisitorText(messages, agentMessageId) {
7330
7956
  }
7331
7957
  return void 0;
7332
7958
  }
7333
- function normalizeAgentFeedbackAnswerText(text2) {
7334
- const normalized = text2.replace(/\s+/g, " ").trim();
7959
+ function normalizeAgentFeedbackAnswerText(text3) {
7960
+ const normalized = text3.replace(/\s+/g, " ").trim();
7335
7961
  if (!normalized) return void 0;
7336
7962
  return normalized.length > 4e3 ? `${normalized.slice(0, 3997)}...` : normalized;
7337
7963
  }
@@ -7391,9 +8017,9 @@ function AgentWidget({
7391
8017
  }) {
7392
8018
  const isMobile = useIsMobile();
7393
8019
  const placement = normalizeAgentPlacement(placementInput);
7394
- const railSlotRef = (0, import_react17.useRef)(null);
7395
- const [railCollapsed, setRailCollapsed] = (0, import_react17.useState)(defaultCollapsed);
7396
- const [railExpanded, setRailExpanded] = (0, import_react17.useState)(false);
8020
+ const railSlotRef = (0, import_react18.useRef)(null);
8021
+ const [railCollapsed, setRailCollapsed] = (0, import_react18.useState)(defaultCollapsed);
8022
+ const [railExpanded, setRailExpanded] = (0, import_react18.useState)(false);
7397
8023
  const pageShiftActive = shouldApplyPageShift({
7398
8024
  pageShift,
7399
8025
  isMobile,
@@ -7446,7 +8072,7 @@ function AgentWidget({
7446
8072
  } : {},
7447
8073
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
7448
8074
  };
7449
- (0, import_react17.useEffect)(() => {
8075
+ (0, import_react18.useEffect)(() => {
7450
8076
  if (!registerPanelController) return;
7451
8077
  registerAgentPanelController(customerId, {
7452
8078
  open: () => setRailCollapsed(false),
@@ -7481,7 +8107,7 @@ function AgentWidget({
7481
8107
  })
7482
8108
  );
7483
8109
  }
7484
- (0, import_react17.useEffect)(() => {
8110
+ (0, import_react18.useEffect)(() => {
7485
8111
  if (railCollapsed) return;
7486
8112
  const handleKeyDown = (event) => {
7487
8113
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -7591,7 +8217,7 @@ function AgentWidget({
7591
8217
  }
7592
8218
 
7593
8219
  // src/react/components/AgentTranscript/AgentTranscript.tsx
7594
- var import_react18 = require("react");
8220
+ var import_react19 = require("react");
7595
8221
 
7596
8222
  // src/react/components/AgentTranscript/TranscriptActivity.tsx
7597
8223
  var import_jsx_runtime21 = require("react/jsx-runtime");
@@ -7659,9 +8285,9 @@ function groupTranscriptActivities(activities) {
7659
8285
  label: activity.createdAt < previous.startedAt ? activity.label : previous.label
7660
8286
  });
7661
8287
  }
7662
- return [...calls.values()].map(({ latest, id, startedAt, label }) => ({
8288
+ return [...calls.values()].map(({ latest, id: id2, startedAt, label }) => ({
7663
8289
  ...latest,
7664
- id,
8290
+ id: id2,
7665
8291
  label,
7666
8292
  createdAt: startedAt
7667
8293
  }));
@@ -7712,7 +8338,7 @@ function AgentTranscript({
7712
8338
  const previous = entries[index - 1];
7713
8339
  const validTimestamp = Number.isFinite(date.getTime());
7714
8340
  const showTimestamp = validTimestamp && (previous ? entry.createdAt - previous.createdAt >= 5 * 60 * 1e3 || date.toISOString().slice(0, 10) !== new Date(previous.createdAt).toJSON()?.slice(0, 10) : showInitialTimestamp);
7715
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react18.Fragment, { children: [
8341
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react19.Fragment, { children: [
7716
8342
  showTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { className: "agent-transcript__time-marker", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("time", { dateTime: date.toISOString(), children: formatTimestamp(entry.createdAt) }) }) : null,
7717
8343
  entry.kind === "activity" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TranscriptActivity, { activity: entry }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
7718
8344
  "li",