@webless/agent 0.6.3 → 0.6.5

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
@@ -24,18 +24,20 @@ __export(react_exports, {
24
24
  AgentWidget: () => AgentWidget,
25
25
  AssistEdgeTab: () => AssistEdgeTab,
26
26
  DEFAULT_AGENT_PLACEMENT: () => DEFAULT_AGENT_PLACEMENT,
27
+ builtInVisitorToolResultRegistry: () => builtInVisitorToolResultRegistry,
27
28
  createIdleSuggestions: () => createIdleSuggestions,
28
29
  defaultAgentRailTheme: () => defaultAgentRailTheme,
29
30
  defaultDarkAgentRailTheme: () => defaultDarkAgentRailTheme,
30
31
  hasVisitorMessages: () => hasVisitorMessages,
31
32
  isAgentBusy: () => isAgentBusy,
32
33
  normalizeAgentPlacement: () => normalizeAgentPlacement,
34
+ presentVisitorToolResult: () => presentVisitorToolResult,
33
35
  useAgentChat: () => useAgentChat
34
36
  });
35
37
  module.exports = __toCommonJS(react_exports);
36
38
 
37
39
  // src/react/components/AgentWidget/AgentWidget.tsx
38
- var import_react10 = require("react");
40
+ var import_react11 = require("react");
39
41
 
40
42
  // src/react/page-shift.ts
41
43
  var import_react = require("react");
@@ -126,6 +128,26 @@ var import_client2 = require("eve/client");
126
128
  // src/runtime/capability.ts
127
129
  var import_client = require("eve/client");
128
130
  var MAX_REFRESH_SKEW_MS = 3e4;
131
+ var LOCAL_LOOPBACK_ORIGINS = [
132
+ "http://127.0.0.1:3010",
133
+ "http://127.0.0.1:3001"
134
+ ];
135
+ function isLoopbackRuntimeOrigin(origin) {
136
+ try {
137
+ const host = new URL(origin).hostname;
138
+ return host === "127.0.0.1" || host === "localhost";
139
+ } catch {
140
+ return false;
141
+ }
142
+ }
143
+ function localBootstrapOrigins(origin) {
144
+ const normalized = origin.replace(/\/$/, "");
145
+ if (!isLoopbackRuntimeOrigin(normalized)) return [normalized];
146
+ return [
147
+ normalized,
148
+ ...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
149
+ ];
150
+ }
129
151
  function isRecord(value) {
130
152
  return typeof value === "object" && value !== null && !Array.isArray(value);
131
153
  }
@@ -172,20 +194,31 @@ function createAgentRuntimeCapability(options) {
172
194
  );
173
195
  }
174
196
  }
175
- const response = await fetchImplementation(
176
- `${options.runtimeOrigin}/webless/v1/bootstrap`,
177
- {
178
- body: JSON.stringify({
179
- clientSessionId: options.visitorSessionId,
180
- indexId: options.indexId,
181
- ...previewBuildId ? { previewBuildId } : {},
182
- ...previewGrant ? { previewGrant } : {},
183
- version: options.version
184
- }),
185
- headers: { "content-type": "application/json" },
186
- method: "POST"
197
+ const bootstrapBody = JSON.stringify({
198
+ clientSessionId: options.visitorSessionId,
199
+ indexId: options.indexId,
200
+ ...previewBuildId ? { previewBuildId } : {},
201
+ ...previewGrant ? { previewGrant } : {},
202
+ version: options.version
203
+ });
204
+ const postBootstrap = (origin) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
205
+ body: bootstrapBody,
206
+ headers: { "content-type": "application/json" },
207
+ method: "POST"
208
+ });
209
+ let response;
210
+ let lastError;
211
+ for (const origin of localBootstrapOrigins(options.runtimeOrigin)) {
212
+ try {
213
+ response = await postBootstrap(origin);
214
+ break;
215
+ } catch (error) {
216
+ lastError = error;
187
217
  }
188
- );
218
+ }
219
+ if (!response) {
220
+ throw lastError instanceof Error ? lastError : new Error("Agent Runtime is unavailable.");
221
+ }
189
222
  if (!response.ok) {
190
223
  throw new Error(await readBootstrapError(response));
191
224
  }
@@ -347,6 +380,226 @@ function clearPersistedAgentSession(visitorSessionId, options) {
347
380
  sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
348
381
  }
349
382
 
383
+ // src/runtime/tool-ui.ts
384
+ var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
385
+ function isRecord2(value) {
386
+ return value !== null && typeof value === "object" && !Array.isArray(value);
387
+ }
388
+ function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
389
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
390
+ return true;
391
+ }
392
+ if (typeof value === "number") return Number.isFinite(value);
393
+ if (typeof value !== "object") return false;
394
+ if (seen.has(value)) return false;
395
+ seen.add(value);
396
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
397
+ ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
398
+ );
399
+ seen.delete(value);
400
+ return valid;
401
+ }
402
+ function boundedString(value, max) {
403
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
404
+ return void 0;
405
+ }
406
+ return value.trim();
407
+ }
408
+ function numberValue(value) {
409
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
410
+ }
411
+ function integerValue(value) {
412
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
413
+ }
414
+ function isFieldKind(value) {
415
+ return typeof value === "string" && [
416
+ "text",
417
+ "textarea",
418
+ "email",
419
+ "number",
420
+ "select",
421
+ "multi-select",
422
+ "checkbox",
423
+ "confirmation",
424
+ "radio",
425
+ "date",
426
+ "time",
427
+ "date-time",
428
+ "calendar",
429
+ "range",
430
+ "json"
431
+ ].includes(value);
432
+ }
433
+ function parseField(value) {
434
+ if (!isRecord2(value)) return null;
435
+ if (!hasOnlyKeys(value, [
436
+ "description",
437
+ "kind",
438
+ "label",
439
+ "max",
440
+ "maxItems",
441
+ "maxLength",
442
+ "min",
443
+ "minLength",
444
+ "options",
445
+ "path",
446
+ "placeholder",
447
+ "required",
448
+ "step",
449
+ "defaultValue"
450
+ ])) {
451
+ return null;
452
+ }
453
+ if (!isFieldKind(value.kind)) return null;
454
+ const path = boundedString(value.path, 160);
455
+ const label = boundedString(value.label, 160);
456
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
457
+ const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
458
+ const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
459
+ const min = value.min === void 0 ? void 0 : numberValue(value.min);
460
+ const max = value.max === void 0 ? void 0 : numberValue(value.max);
461
+ const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
462
+ const step = value.step === void 0 ? void 0 : numberValue(value.step);
463
+ const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
464
+ const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
465
+ const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
466
+ if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
467
+ return null;
468
+ }
469
+ if (value.description !== void 0 && !description) return null;
470
+ if (value.placeholder !== void 0 && placeholder === void 0) return null;
471
+ if (value.required !== void 0 && required === void 0) return null;
472
+ if (value.min !== void 0 && min === void 0) return null;
473
+ if (value.max !== void 0 && max === void 0) return null;
474
+ if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
475
+ return null;
476
+ if (value.step !== void 0 && step === void 0) return null;
477
+ if (value.minLength !== void 0 && minLength === void 0) return null;
478
+ if (value.maxLength !== void 0 && maxLength === void 0) return null;
479
+ if (value.defaultValue !== void 0 && defaultValue === void 0)
480
+ return null;
481
+ if (value.options !== void 0) {
482
+ if (!Array.isArray(value.options) || value.options.length > 100)
483
+ return null;
484
+ for (const option of value.options) {
485
+ if (!isRecord2(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
486
+ return null;
487
+ }
488
+ }
489
+ }
490
+ return {
491
+ kind: value.kind,
492
+ path,
493
+ label,
494
+ ...description ? { description } : {},
495
+ ...placeholder !== void 0 ? { placeholder } : {},
496
+ ...required !== void 0 ? { required } : {},
497
+ ...defaultValue !== void 0 ? { defaultValue } : {},
498
+ ...value.options !== void 0 ? { options: value.options } : {},
499
+ ...min !== void 0 ? { min } : {},
500
+ ...max !== void 0 ? { max } : {},
501
+ ...maxItems !== void 0 ? { maxItems } : {},
502
+ ...step !== void 0 ? { step } : {},
503
+ ...minLength !== void 0 ? { minLength } : {},
504
+ ...maxLength !== void 0 ? { maxLength } : {}
505
+ };
506
+ }
507
+ function parseStep(value) {
508
+ if (!isRecord2(value)) return null;
509
+ if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
510
+ return null;
511
+ }
512
+ const id = boundedString(value.id, 80);
513
+ const label = boundedString(value.label, 160);
514
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
515
+ 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(
516
+ (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
517
+ )) {
518
+ return null;
519
+ }
520
+ return {
521
+ id,
522
+ label,
523
+ fieldPaths: value.fieldPaths,
524
+ ...description ? { description } : {}
525
+ };
526
+ }
527
+ function parseAction(value) {
528
+ if (!isRecord2(value)) return null;
529
+ if (!hasOnlyKeys(value, ["id", "label"])) return null;
530
+ const label = boundedString(value.label, 80);
531
+ if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
532
+ return null;
533
+ }
534
+ return {
535
+ id: value.id,
536
+ label
537
+ };
538
+ }
539
+ function parseAgentToolUiSurface(value) {
540
+ if (!isRecord2(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
541
+ return null;
542
+ if (!hasOnlyKeys(value, [
543
+ "actions",
544
+ "description",
545
+ "fields",
546
+ "id",
547
+ "operationId",
548
+ "requestId",
549
+ "schemaVersion",
550
+ "steps",
551
+ "submitLabel",
552
+ "title",
553
+ "toolSlug",
554
+ "values"
555
+ ])) {
556
+ return null;
557
+ }
558
+ const id = boundedString(value.id, 200);
559
+ const title = boundedString(value.title, 200);
560
+ const toolSlug = boundedString(value.toolSlug, 200);
561
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
562
+ const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
563
+ const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
564
+ const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
565
+ if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
566
+ return null;
567
+ }
568
+ const fields = value.fields.map(parseField);
569
+ if (fields.some((field) => field === null)) return null;
570
+ const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
571
+ if (steps?.some((step) => step === null)) return null;
572
+ const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
573
+ if (actions?.some((action) => action === null)) return null;
574
+ if (value.description !== void 0 && !description) return null;
575
+ if (value.operationId !== void 0 && !operationId) return null;
576
+ if (value.requestId !== void 0 && !requestId) return null;
577
+ if (value.submitLabel !== void 0 && !submitLabel) return null;
578
+ const values = value.values !== void 0 && isRecord2(value.values) ? value.values : void 0;
579
+ if (value.values !== void 0) {
580
+ if (!values || !Object.values(values).every((item) => isJsonValue(item)))
581
+ return null;
582
+ }
583
+ return {
584
+ schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
585
+ id,
586
+ title,
587
+ toolSlug,
588
+ fields,
589
+ ...actions ? { actions } : {},
590
+ ...description ? { description } : {},
591
+ ...operationId ? { operationId } : {},
592
+ ...requestId ? { requestId } : {},
593
+ ...submitLabel ? { submitLabel } : {},
594
+ ...steps ? { steps } : {},
595
+ ...values ? { values } : {}
596
+ };
597
+ }
598
+ function hasOnlyKeys(value, allowed) {
599
+ const allowedKeys = new Set(allowed);
600
+ return Object.keys(value).every((key) => allowedKeys.has(key));
601
+ }
602
+
350
603
  // src/runtime/client.ts
351
604
  function isTurnBoundary(event) {
352
605
  return event.type === "session.waiting" || event.type === "session.completed" || event.type === "session.failed";
@@ -359,12 +612,8 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
359
612
  if (event.type === "message.completed") {
360
613
  handlers.onComplete?.();
361
614
  }
362
- if (event.type === "action.result") {
363
- const result = event.data.result;
364
- if (result && typeof result === "object" && "output" in result) {
365
- handlers.onActionResult?.(result.output);
366
- }
367
- }
615
+ if (event.type === "action.result") emitActionResult(event, handlers);
616
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
368
617
  if (event.type !== "message.appended") return rendered;
369
618
  const { messageDelta, messageSoFar } = event.data;
370
619
  let delta = messageDelta;
@@ -375,9 +624,45 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
375
624
  } else if (messageDelta) {
376
625
  next += messageDelta;
377
626
  }
378
- if (delta) handlers.onDelta(delta);
627
+ if (delta) handlers.onDelta?.(delta);
379
628
  return next;
380
629
  }
630
+ function emitActionResult(event, handlers) {
631
+ const result = event.data.result;
632
+ if (result.kind === "tool-result") {
633
+ handlers.onToolResult?.({
634
+ callId: result.callId,
635
+ toolName: result.toolName,
636
+ status: event.data.status,
637
+ output: result.output,
638
+ ...event.data.error ? { error: event.data.error } : {}
639
+ });
640
+ }
641
+ if ("output" in result) handlers.onActionResult?.(result.output);
642
+ }
643
+ function emitInputRequests(event, handlers) {
644
+ handlers.onInputRequest?.(
645
+ event.data.requests.map((request) => {
646
+ const ui = parseAgentToolUiSurface(
647
+ request.ui
648
+ );
649
+ return {
650
+ requestId: request.requestId,
651
+ kind: request.kind,
652
+ prompt: request.prompt,
653
+ ...request.display ? { display: request.display } : {},
654
+ ...request.allowFreeform !== void 0 ? { allowFreeform: request.allowFreeform } : {},
655
+ ...request.options ? { options: request.options } : {},
656
+ ...ui ? { ui } : {},
657
+ action: {
658
+ callId: request.action.callId,
659
+ kind: "tool-call",
660
+ toolName: request.action.toolName
661
+ }
662
+ };
663
+ })
664
+ );
665
+ }
381
666
  function isResumeTurnMessage(received, candidate) {
382
667
  if (received === candidate) return true;
383
668
  return Boolean(candidate) && received.endsWith(`
@@ -653,9 +938,11 @@ var AgentSession = class {
653
938
  let streamIndex = session?.state.streamIndex ?? 0;
654
939
  let rendered = "";
655
940
  const workItems = /* @__PURE__ */ new Map();
941
+ let requestedInput = false;
656
942
  try {
657
943
  for await (const event of response) {
658
944
  if (signal.aborted) break;
945
+ if (event.type === "input.requested") requestedInput = true;
659
946
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
660
947
  streamIndex += 1;
661
948
  if (session) {
@@ -673,7 +960,7 @@ var AgentSession = class {
673
960
  this.persistSessionCursor(session);
674
961
  }
675
962
  }
676
- if (!rendered.trim() && !signal.aborted) {
963
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
677
964
  throw new Error("Empty response from runtime");
678
965
  }
679
966
  return rendered.trim();
@@ -693,6 +980,9 @@ var AgentSession = class {
693
980
  () => attached.snapshot({ signal })
694
981
  );
695
982
  const turnEvents = latestTurnEvents(snapshot.events);
983
+ const hasInputRequest = turnEvents.some(
984
+ (event) => event.type === "input.requested"
985
+ );
696
986
  const received = turnEvents[0];
697
987
  const lastSent = persisted.lastMessage;
698
988
  const inFlight = !turnEvents.some((event) => isTurnBoundary(event));
@@ -703,6 +993,8 @@ var AgentSession = class {
703
993
  const workItems = /* @__PURE__ */ new Map();
704
994
  for (const event of turnEvents) {
705
995
  applyWorkEvent(event, handlers, workItems);
996
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
997
+ if (event.type === "action.result") emitActionResult(event, handlers);
706
998
  }
707
999
  if (rendered.startsWith(initialText)) {
708
1000
  const missedText = rendered.slice(initialText.length);
@@ -732,7 +1024,9 @@ var AgentSession = class {
732
1024
  );
733
1025
  }
734
1026
  handlers.onComplete?.();
735
- if (!rendered.trim()) throw new Error("Empty response from runtime");
1027
+ if (!rendered.trim() && !hasInputRequest) {
1028
+ throw new Error("Empty response from runtime");
1029
+ }
736
1030
  return rendered.trim();
737
1031
  }
738
1032
  let streamIndex = snapshot.session.streamIndex;
@@ -751,7 +1045,55 @@ var AgentSession = class {
751
1045
  session = client.sessions.attach(session.state.sessionId, { streamIndex });
752
1046
  this.session = session;
753
1047
  this.persistSessionCursor(session);
754
- if (!rendered.trim() && !signal.aborted) {
1048
+ if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
1049
+ throw new Error("Empty response from runtime");
1050
+ }
1051
+ return rendered.trim();
1052
+ }
1053
+ async respondTurn(responses, signal, handlers) {
1054
+ const client = this.ensureClient();
1055
+ const session = this.session ?? this.attachPersistedSession(client);
1056
+ if (!session) {
1057
+ throw new Error("No active session is waiting for input.");
1058
+ }
1059
+ this.session = session;
1060
+ const inputResponses = responses.map(
1061
+ ({ requestId, optionId, text }) => ({
1062
+ requestId,
1063
+ ...optionId ? { optionId } : {},
1064
+ ...text ? { text } : {}
1065
+ })
1066
+ );
1067
+ const response = await withCapabilityRefresh(
1068
+ this.capability,
1069
+ () => session.respond(inputResponses, { signal })
1070
+ );
1071
+ this.activeResponse = response;
1072
+ let streamIndex = session.state.streamIndex;
1073
+ let rendered = "";
1074
+ let requestedInput = false;
1075
+ const workItems = /* @__PURE__ */ new Map();
1076
+ try {
1077
+ for await (const event of response) {
1078
+ if (signal.aborted) break;
1079
+ if (event.type === "input.requested") requestedInput = true;
1080
+ rendered = applyMessageEvent(event, rendered, handlers, workItems);
1081
+ streamIndex += 1;
1082
+ savePersistedAgentSession(
1083
+ this.visitorSessionId,
1084
+ session.state.sessionId,
1085
+ streamIndex,
1086
+ this.storeOptions
1087
+ );
1088
+ }
1089
+ } finally {
1090
+ this.activeResponse = void 0;
1091
+ this.session = client.sessions.attach(session.state.sessionId, {
1092
+ streamIndex
1093
+ });
1094
+ this.persistSessionCursor(this.session);
1095
+ }
1096
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
755
1097
  throw new Error("Empty response from runtime");
756
1098
  }
757
1099
  return rendered.trim();
@@ -811,6 +1153,11 @@ function createAgentClient(options) {
811
1153
  resumeOptions.handlers,
812
1154
  resumeOptions.initialText
813
1155
  ),
1156
+ respondTurn: (respondOptions) => session.respondTurn(
1157
+ respondOptions.responses,
1158
+ respondOptions.signal ?? new AbortController().signal,
1159
+ respondOptions.handlers
1160
+ ),
814
1161
  reset: () => session.reset(),
815
1162
  cancelActive: () => session.cancelActive(),
816
1163
  getActiveSessionId: () => session.getActiveSessionId()
@@ -860,7 +1207,176 @@ function formatAgentError(error) {
860
1207
  return TRANSIENT_AGENT_ERROR_MESSAGE;
861
1208
  }
862
1209
 
1210
+ // src/react/lib/composer-form.ts
1211
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1212
+ var MIN_FORM_FIELDS = 2;
1213
+ var MAX_FORM_FIELDS = 5;
1214
+ var FIELD_LIBRARY = [
1215
+ {
1216
+ id: "email",
1217
+ kind: "email",
1218
+ label: "Email",
1219
+ placeholder: "email@example.com",
1220
+ required: true,
1221
+ autocomplete: "email",
1222
+ patterns: [/\be-?mails?\b/i]
1223
+ },
1224
+ {
1225
+ id: "name",
1226
+ kind: "text",
1227
+ label: "Name",
1228
+ placeholder: "Your name",
1229
+ required: true,
1230
+ autocomplete: "name",
1231
+ patterns: [/\b((full|first|last)\s+)?names?\b/i],
1232
+ exclude: /\bcompany\s+names?\b/i
1233
+ },
1234
+ {
1235
+ id: "phone",
1236
+ kind: "tel",
1237
+ label: "Phone",
1238
+ placeholder: "+1 555 0100",
1239
+ required: true,
1240
+ autocomplete: "tel",
1241
+ patterns: [/\b(phone|mobile|cell)\b/i]
1242
+ },
1243
+ {
1244
+ id: "company",
1245
+ kind: "text",
1246
+ label: "Company",
1247
+ placeholder: "Company name",
1248
+ required: true,
1249
+ autocomplete: "organization",
1250
+ patterns: [/\bcompan(y|ies)\b/i]
1251
+ },
1252
+ {
1253
+ id: "message",
1254
+ kind: "textarea",
1255
+ label: "Message",
1256
+ placeholder: "Message\u2026",
1257
+ required: true,
1258
+ patterns: [/\b(your message|a message|the message|inquiry|enquiry)\b/i],
1259
+ exclude: /\bin one message\b/i
1260
+ }
1261
+ ];
1262
+ function asRecord(value) {
1263
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1264
+ }
1265
+ function asString(value) {
1266
+ return typeof value === "string" ? value.trim() : "";
1267
+ }
1268
+ function isFieldKind2(value) {
1269
+ return value === "text" || value === "email" || value === "tel" || value === "textarea";
1270
+ }
1271
+ function parseVisitorFormFields(value) {
1272
+ if (!Array.isArray(value)) return [];
1273
+ const fields = [];
1274
+ const seen = /* @__PURE__ */ new Set();
1275
+ for (const item of value) {
1276
+ const record = asRecord(item);
1277
+ const id = asString(record?.id).toLowerCase().replace(/[^a-z0-9_-]/g, "");
1278
+ const kind = asString(record?.kind);
1279
+ if (!record || !id || seen.has(id) || !isFieldKind2(kind)) continue;
1280
+ seen.add(id);
1281
+ const label = asString(record.label) || id;
1282
+ fields.push({
1283
+ id,
1284
+ kind,
1285
+ label,
1286
+ placeholder: asString(record.placeholder) || label,
1287
+ required: record.required !== false,
1288
+ ...asString(record.autocomplete) ? { autocomplete: asString(record.autocomplete) } : {}
1289
+ });
1290
+ if (fields.length >= MAX_FORM_FIELDS) break;
1291
+ }
1292
+ return fields;
1293
+ }
1294
+ function readComposerControlValue(event) {
1295
+ const node = event.target ?? event.currentTarget ?? null;
1296
+ if (node && typeof node === "object" && "value" in node && typeof node.value === "string") {
1297
+ return node.value;
1298
+ }
1299
+ return "";
1300
+ }
1301
+ function isValidComposerFieldValue(field, value) {
1302
+ const trimmed = value.trim();
1303
+ if (!trimmed) return !field.required;
1304
+ if (field.kind === "email") return EMAIL_PATTERN.test(trimmed);
1305
+ if (field.kind === "tel") return trimmed.replace(/\D/g, "").length >= 7;
1306
+ return trimmed.length > 0;
1307
+ }
1308
+ function isComposerFormComplete(form, values) {
1309
+ return form.fields.every(
1310
+ (field) => isValidComposerFieldValue(field, values[field.id] ?? "")
1311
+ );
1312
+ }
1313
+ function formatComposerFormMessage(form, values) {
1314
+ return form.fields.map((field) => {
1315
+ const value = (values[field.id] ?? "").trim();
1316
+ return value ? `${field.label}: ${value}` : "";
1317
+ }).filter(Boolean).join("\n");
1318
+ }
1319
+ function looksLikeFieldCollection(text) {
1320
+ return /\b(please|need|send|share|enter|provide|add|collect|what|which|use)\b/i.test(
1321
+ text
1322
+ ) || /:\s*$/m.test(text) || /^[-*•]\s+/m.test(text);
1323
+ }
1324
+ function looksLikeBookingCopy(text) {
1325
+ return /book(ing)? (card|a demo|this time)|pick a (date|time)|available (times|slots)|calendly/i.test(
1326
+ text
1327
+ );
1328
+ }
1329
+ function inferComposerForm(text) {
1330
+ const cleaned = text.trim();
1331
+ if (!cleaned || looksLikeBookingCopy(cleaned) || !looksLikeFieldCollection(cleaned)) {
1332
+ return null;
1333
+ }
1334
+ const fields = FIELD_LIBRARY.flatMap((field) => {
1335
+ if (field.exclude?.test(cleaned)) {
1336
+ const leftover = cleaned.replace(field.exclude, " ");
1337
+ if (!field.patterns.some((pattern) => pattern.test(leftover))) return [];
1338
+ } else if (!field.patterns.some((pattern) => pattern.test(cleaned))) {
1339
+ return [];
1340
+ }
1341
+ const { patterns: _patterns, exclude: _exclude, ...next } = field;
1342
+ return [next];
1343
+ }).slice(0, MAX_FORM_FIELDS);
1344
+ if (fields.length < MIN_FORM_FIELDS) return null;
1345
+ return {
1346
+ id: `inferred:${fields.map((field) => field.id).join("+")}`,
1347
+ fields
1348
+ };
1349
+ }
1350
+ function resolveComposerForm(input) {
1351
+ if (input.enabled === false || input.hasBookingOffer) return null;
1352
+ const text = input.agentText.trim();
1353
+ if (!text) return null;
1354
+ const card = input.cards?.find((item) => item.type === "visitor_form");
1355
+ if (card?.fields && card.fields.length >= MIN_FORM_FIELDS) {
1356
+ return {
1357
+ id: `card:${card.fields.map((field) => field.id).join("+")}`,
1358
+ fields: card.fields.slice(0, MAX_FORM_FIELDS)
1359
+ };
1360
+ }
1361
+ return inferComposerForm(text);
1362
+ }
1363
+
863
1364
  // src/react/lib/tool-card.ts
1365
+ function preferBookingOffer(current, next) {
1366
+ if (!current) return next;
1367
+ if (next.slots.length !== current.slots.length) {
1368
+ return next.slots.length > current.slots.length ? next : current;
1369
+ }
1370
+ if (next.eventTypes.length !== current.eventTypes.length) {
1371
+ return next.eventTypes.length > current.eventTypes.length ? next : current;
1372
+ }
1373
+ return next;
1374
+ }
1375
+ function looksLikeBookingReady(text) {
1376
+ return /demo option|options are ready|pick a (date|time)|available (times|slots)|book(ing)? (card|the demo)|schedule/i.test(
1377
+ text
1378
+ );
1379
+ }
864
1380
  function bookingOfferIdentityKey(offer) {
865
1381
  const eventTypes = offer.eventTypes.map(
866
1382
  (item) => `${item.uri}:${item.duration ?? ""}:${item.locationKind ?? ""}:${item.location ?? ""}`
@@ -869,10 +1385,10 @@ function bookingOfferIdentityKey(offer) {
869
1385
  return `${eventTypes}::${slots}` || "offer";
870
1386
  }
871
1387
  var FENCE_PATTERN = /```(?:webless-tool-card|json)\s*([\s\S]*?)```/gi;
872
- function asRecord(value) {
1388
+ function asRecord2(value) {
873
1389
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
874
1390
  }
875
- function asString(value) {
1391
+ function asString2(value) {
876
1392
  return typeof value === "string" ? value.trim() : "";
877
1393
  }
878
1394
  function isEventUri(value) {
@@ -882,24 +1398,28 @@ function isEventTypeUri(value) {
882
1398
  return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
883
1399
  }
884
1400
  function parseToolCard(value) {
885
- const record = asRecord(value);
1401
+ const record = asRecord2(value);
886
1402
  if (!record) return null;
887
- if (record.booking_offer && asString(record.type) !== "booking_offer") {
1403
+ if (record.booking_offer && asString2(record.type) !== "booking_offer") {
888
1404
  const nested = parseToolCard(record.booking_offer);
889
1405
  if (nested) return nested;
890
1406
  }
891
- const type = asString(record.type);
1407
+ if (record.visitor_booking && asString2(record.type) !== "booking_confirmed") {
1408
+ const nested = parseToolCard(record.visitor_booking);
1409
+ if (nested) return nested;
1410
+ }
1411
+ const type = asString2(record.type);
892
1412
  if (type === "booking_offer") {
893
1413
  const eventTypes = Array.isArray(record.eventTypes) ? record.eventTypes.flatMap((item) => {
894
- const entry = asRecord(item);
895
- const uri = asString(entry?.uri);
896
- if (!entry || !isEventTypeUri(uri)) return [];
1414
+ const entry = asRecord2(item);
1415
+ const uri = asString2(entry?.uri);
1416
+ if (!entry || !uri) return [];
897
1417
  const duration = entry.duration;
898
- const locationKind = asString(entry.locationKind);
899
- const location = asString(entry.location);
1418
+ const locationKind = asString2(entry.locationKind);
1419
+ const location = asString2(entry.location);
900
1420
  return [
901
1421
  {
902
- name: asString(entry.name) || "Meeting",
1422
+ name: asString2(entry.name) || "Meeting",
903
1423
  uri,
904
1424
  ...typeof duration === "number" ? { duration } : {},
905
1425
  ...locationKind ? { locationKind } : {},
@@ -908,10 +1428,10 @@ function parseToolCard(value) {
908
1428
  ];
909
1429
  }) : [];
910
1430
  const slots = Array.isArray(record.slots) ? record.slots.flatMap((item) => {
911
- const entry = asRecord(item);
912
- const startTime = asString(entry?.startTime);
1431
+ const entry = asRecord2(item);
1432
+ const startTime = asString2(entry?.startTime);
913
1433
  if (!entry || !startTime) return [];
914
- const eventTypeUri = asString(entry.eventTypeUri);
1434
+ const eventTypeUri = asString2(entry.eventTypeUri);
915
1435
  return [
916
1436
  {
917
1437
  startTime,
@@ -919,15 +1439,15 @@ function parseToolCard(value) {
919
1439
  }
920
1440
  ];
921
1441
  }) : [];
922
- if (slots.length === 0) return null;
1442
+ if (eventTypes.length === 0 && slots.length === 0) return null;
923
1443
  return { type: "booking_offer", eventTypes, slots };
924
1444
  }
925
1445
  if (type === "booking_confirmed") {
926
- const eventUri = asString(record.eventUri);
1446
+ const eventUri = asString2(record.eventUri);
927
1447
  if (!isEventUri(eventUri)) return null;
928
- const inviteeUri = asString(record.inviteeUri);
929
- const inviteeEmail = asString(record.inviteeEmail);
930
- const startTime = asString(record.startTime);
1448
+ const inviteeUri = asString2(record.inviteeUri);
1449
+ const inviteeEmail = asString2(record.inviteeEmail);
1450
+ const startTime = asString2(record.startTime);
931
1451
  return {
932
1452
  type: "booking_confirmed",
933
1453
  eventUri,
@@ -937,10 +1457,15 @@ function parseToolCard(value) {
937
1457
  };
938
1458
  }
939
1459
  if (type === "booking_canceled") {
940
- const eventUri = asString(record.eventUri);
1460
+ const eventUri = asString2(record.eventUri);
941
1461
  if (!isEventUri(eventUri)) return null;
942
1462
  return { type: "booking_canceled", eventUri };
943
1463
  }
1464
+ if (type === "visitor_form") {
1465
+ const fields = parseVisitorFormFields(record.fields);
1466
+ if (fields.length < 2) return null;
1467
+ return { type: "visitor_form", fields };
1468
+ }
944
1469
  return null;
945
1470
  }
946
1471
  function formatBookingOfferFence(offer) {
@@ -954,11 +1479,10 @@ function formatBookingOfferFence(offer) {
954
1479
  "```"
955
1480
  ].join("\n");
956
1481
  }
957
- function bookingOfferFromActionOutput(output) {
958
- const record = asRecord(output);
959
- const data = asRecord(record?.data) ?? record;
960
- const card = parseToolCard(data);
961
- return card?.type === "booking_offer" ? card : null;
1482
+ function bookingCardFromActionOutput(output) {
1483
+ const record = asRecord2(output);
1484
+ const data = asRecord2(record?.data) ?? record;
1485
+ return parseToolCard(data);
962
1486
  }
963
1487
  function ensureBookingOfferText(text, offer) {
964
1488
  if (!offer) return text;
@@ -973,6 +1497,21 @@ ${formatBookingOfferFence(offer)}`;
973
1497
  function hideToolCardFences(text) {
974
1498
  return text.replace(/```(?:webless-tool-card|json)\s*[\s\S]*?```/gi, "").replace(/```(?:webless-tool-card|json)[\s\S]*$/i, "").replace(/\n{3,}/g, "\n\n").trim();
975
1499
  }
1500
+ var BOOKING_CARD_FALLBACK = "Pick a date and time that works for you.";
1501
+ function looksLikeBookingAvailabilityDump(text) {
1502
+ const cleaned = text.trim();
1503
+ if (!cleaned) return false;
1504
+ const isoCount = (cleaned.match(/\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/g) ?? []).length;
1505
+ const utcCount = (cleaned.match(/\bUTC\b/g) ?? []).length;
1506
+ return isoCount >= 2 || utcCount >= 2 || /api\.calendly\.com|calendly\.com\//i.test(cleaned) || /webless-tool-card/i.test(cleaned);
1507
+ }
1508
+ function sanitizeBookingOfferCopy(text) {
1509
+ const cleaned = hideToolCardFences(text);
1510
+ if (!cleaned || looksLikeBookingAvailabilityDump(cleaned)) {
1511
+ return BOOKING_CARD_FALLBACK;
1512
+ }
1513
+ return cleaned;
1514
+ }
976
1515
  function visitorTimeZone() {
977
1516
  try {
978
1517
  return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
@@ -1074,6 +1613,7 @@ function formatSlotLabel(startTime) {
1074
1613
  function formatBookingRequest(input) {
1075
1614
  return [
1076
1615
  "Book this meeting now with CALENDLY_POST_INVITEE.",
1616
+ "Execute CALENDLY_POST_INVITEE in this turn with the fields below.",
1077
1617
  "Do not open a Calendly URL and do not list other scheduled events.",
1078
1618
  "Do not invent a location kind. Use only the location fields below.",
1079
1619
  `event_type: ${input.eventTypeUri}`,
@@ -1101,7 +1641,7 @@ function visitorBookingPrefix(booking) {
1101
1641
  }
1102
1642
 
1103
1643
  // src/react/persisted-conversation.ts
1104
- var CONVERSATION_VERSION = 2;
1644
+ var CONVERSATION_VERSION = 3;
1105
1645
  function conversationKey(storageKeyPrefix, visitorSessionId) {
1106
1646
  return `${storageKeyPrefix}:conversation:${visitorSessionId}`;
1107
1647
  }
@@ -1141,30 +1681,171 @@ function parseToolStep(value) {
1141
1681
  ...typeof record.detail === "string" && record.detail ? { detail: record.detail } : {}
1142
1682
  };
1143
1683
  }
1684
+ function parseInputOption(value) {
1685
+ if (typeof value !== "object" || value === null) return null;
1686
+ const record = value;
1687
+ if (typeof record.id !== "string" || typeof record.label !== "string") {
1688
+ return null;
1689
+ }
1690
+ return {
1691
+ id: record.id,
1692
+ label: record.label,
1693
+ ...typeof record.description === "string" ? { description: record.description } : {},
1694
+ ...record.style === "default" || record.style === "primary" || record.style === "danger" ? { style: record.style } : {}
1695
+ };
1696
+ }
1697
+ function parseInputRequest(value) {
1698
+ if (typeof value !== "object" || value === null) return null;
1699
+ const record = value;
1700
+ if (typeof record.requestId !== "string" || record.kind !== "question" && record.kind !== "session-limit" && record.kind !== "tool-approval" || typeof record.prompt !== "string") {
1701
+ return null;
1702
+ }
1703
+ const action = record.action;
1704
+ if (typeof action !== "object" || action === null) return null;
1705
+ const actionRecord = action;
1706
+ if (typeof actionRecord.callId !== "string" || actionRecord.kind !== "tool-call" || typeof actionRecord.toolName !== "string") {
1707
+ return null;
1708
+ }
1709
+ const options = Array.isArray(record.options) ? record.options.map(parseInputOption) : void 0;
1710
+ if (Array.isArray(record.options) && options?.some((option) => option === null)) {
1711
+ return null;
1712
+ }
1713
+ const ui = record.ui ? parseAgentToolUiSurface(record.ui) : void 0;
1714
+ if (record.ui && !ui) return null;
1715
+ return {
1716
+ requestId: record.requestId,
1717
+ kind: record.kind,
1718
+ prompt: record.prompt,
1719
+ action: {
1720
+ callId: actionRecord.callId,
1721
+ kind: "tool-call",
1722
+ toolName: actionRecord.toolName
1723
+ },
1724
+ ...record.display === "confirmation" || record.display === "select" || record.display === "text" ? { display: record.display } : {},
1725
+ ...record.allowFreeform === true ? { allowFreeform: true } : {},
1726
+ ...options && options.length > 0 ? {
1727
+ options: options.filter(
1728
+ (option) => option !== null
1729
+ )
1730
+ } : {},
1731
+ ...ui ? { ui } : {}
1732
+ };
1733
+ }
1734
+ function parseToolResult(value) {
1735
+ if (typeof value !== "object" || value === null) return null;
1736
+ const record = value;
1737
+ if (typeof record.id !== "string" || typeof record.toolName !== "string" || record.status !== "completed" && record.status !== "failed" && record.status !== "rejected") {
1738
+ return null;
1739
+ }
1740
+ if (record.kind === "input") {
1741
+ const surface = parseAgentToolUiSurface(
1742
+ record.surface
1743
+ );
1744
+ return surface ? {
1745
+ id: record.id,
1746
+ toolName: record.toolName,
1747
+ status: record.status,
1748
+ kind: "input",
1749
+ surface
1750
+ } : null;
1751
+ }
1752
+ if (record.kind === "entity" && typeof record.title === "string") {
1753
+ return {
1754
+ id: record.id,
1755
+ toolName: record.toolName,
1756
+ status: record.status,
1757
+ kind: "entity",
1758
+ title: record.title,
1759
+ ...typeof record.description === "string" ? { description: record.description } : {}
1760
+ };
1761
+ }
1762
+ if (record.kind === "collection" && typeof record.title === "string" && Array.isArray(record.items)) {
1763
+ return {
1764
+ id: record.id,
1765
+ toolName: record.toolName,
1766
+ status: record.status,
1767
+ kind: "collection",
1768
+ title: record.title,
1769
+ items: record.items.flatMap((item) => {
1770
+ if (typeof item !== "object" || item === null) return [];
1771
+ const entry = item;
1772
+ if (typeof entry.title !== "string") return [];
1773
+ return [
1774
+ {
1775
+ title: entry.title,
1776
+ ...typeof entry.description === "string" ? { description: entry.description } : {},
1777
+ ...typeof entry.href === "string" ? { href: entry.href } : {}
1778
+ }
1779
+ ];
1780
+ })
1781
+ };
1782
+ }
1783
+ if (record.kind === "signature" && typeof record.title === "string") {
1784
+ return {
1785
+ id: record.id,
1786
+ toolName: record.toolName,
1787
+ status: record.status,
1788
+ kind: "signature",
1789
+ title: record.title,
1790
+ ...typeof record.description === "string" ? { description: record.description } : {},
1791
+ ...typeof record.statusLabel === "string" ? { statusLabel: record.statusLabel } : {}
1792
+ };
1793
+ }
1794
+ if (record.kind !== "summary" || typeof record.title !== "string")
1795
+ return null;
1796
+ return {
1797
+ id: record.id,
1798
+ toolName: record.toolName,
1799
+ status: record.status,
1800
+ kind: "summary",
1801
+ title: record.title,
1802
+ ...typeof record.description === "string" ? { description: record.description } : {}
1803
+ };
1804
+ }
1144
1805
  function visitorTurnText(message) {
1145
1806
  return message.role === "visitor" && message.runtimeText ? message.runtimeText : message.text;
1146
1807
  }
1147
1808
  function loadPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
1148
1809
  if (typeof sessionStorage === "undefined") return null;
1149
- const raw = sessionStorage.getItem(conversationKey(storageKeyPrefix, visitorSessionId));
1810
+ const raw = sessionStorage.getItem(
1811
+ conversationKey(storageKeyPrefix, visitorSessionId)
1812
+ );
1150
1813
  if (!raw) return null;
1151
1814
  try {
1152
1815
  const value = JSON.parse(raw);
1153
1816
  if (typeof value !== "object" || value === null) return null;
1154
1817
  const record = value;
1155
- if (record.version !== 1 && record.version !== CONVERSATION_VERSION || !Array.isArray(record.messages) || typeof record.pending !== "boolean" || typeof record.streamingText !== "string" || record.version === CONVERSATION_VERSION && !Array.isArray(record.toolSteps)) {
1818
+ if (record.version !== 1 && record.version !== 2 && record.version !== CONVERSATION_VERSION || !Array.isArray(record.messages) || typeof record.pending !== "boolean" || typeof record.streamingText !== "string" || record.version === CONVERSATION_VERSION && !Array.isArray(record.toolSteps)) {
1156
1819
  return null;
1157
1820
  }
1158
1821
  const messages = record.messages.map(parseMessage);
1159
1822
  if (messages.some((message) => message === null)) return null;
1160
- const storedToolSteps = record.version === CONVERSATION_VERSION && Array.isArray(record.toolSteps) ? record.toolSteps : [];
1823
+ const storedToolSteps = (record.version === 2 || record.version === CONVERSATION_VERSION) && Array.isArray(record.toolSteps) ? record.toolSteps : [];
1161
1824
  const toolSteps = storedToolSteps.map(parseToolStep);
1162
1825
  if (toolSteps.some((step) => step === null)) return null;
1826
+ const storedToolResults = record.version === CONVERSATION_VERSION && Array.isArray(record.toolResults) ? record.toolResults : [];
1827
+ const toolResults = storedToolResults.map(parseToolResult);
1828
+ if (toolResults.some((result) => result === null)) return null;
1829
+ const storedPendingInputs = Array.isArray(record.pendingInputs) ? record.pendingInputs : [];
1830
+ const pendingInputs = storedPendingInputs.map(parseInputRequest);
1831
+ if (pendingInputs.some((request) => request === null)) return null;
1163
1832
  return {
1164
- messages: messages.filter((message) => message !== null),
1833
+ messages: messages.filter(
1834
+ (message) => message !== null
1835
+ ),
1165
1836
  pending: record.pending,
1166
1837
  streamingText: record.streamingText,
1167
- toolSteps: toolSteps.filter((step) => step !== null)
1838
+ toolSteps: toolSteps.filter((step) => step !== null),
1839
+ ...Array.isArray(record.toolResults) ? {
1840
+ toolResults: toolResults.filter(
1841
+ (result) => result !== null
1842
+ )
1843
+ } : {},
1844
+ ...pendingInputs.length > 0 ? {
1845
+ pendingInputs: pendingInputs.filter(
1846
+ (request) => request !== null
1847
+ )
1848
+ } : {}
1168
1849
  };
1169
1850
  } catch {
1170
1851
  return null;
@@ -1179,7 +1860,9 @@ function savePersistedAgentConversation(storageKeyPrefix, visitorSessionId, conv
1179
1860
  }
1180
1861
  function clearPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
1181
1862
  if (typeof sessionStorage === "undefined") return;
1182
- sessionStorage.removeItem(conversationKey(storageKeyPrefix, visitorSessionId));
1863
+ sessionStorage.removeItem(
1864
+ conversationKey(storageKeyPrefix, visitorSessionId)
1865
+ );
1183
1866
  clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId);
1184
1867
  }
1185
1868
  function pendingBookingKey(storageKeyPrefix, visitorSessionId) {
@@ -1215,52 +1898,482 @@ function savePendingWidgetBooking(storageKeyPrefix, visitorSessionId, booking) {
1215
1898
  }
1216
1899
  function clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
1217
1900
  if (typeof sessionStorage === "undefined") return;
1218
- sessionStorage.removeItem(pendingBookingKey(storageKeyPrefix, visitorSessionId));
1901
+ sessionStorage.removeItem(
1902
+ pendingBookingKey(storageKeyPrefix, visitorSessionId)
1903
+ );
1219
1904
  }
1220
1905
 
1221
- // src/react/hooks/useAgentChat.ts
1222
- var DEFAULT_GREETING = "Hi! I can search this site or bring in a specialist when it helps. What would you like to know?";
1223
- function createInitialState(greeting = DEFAULT_GREETING) {
1224
- return {
1225
- phase: "idle",
1226
- messages: [
1227
- {
1228
- id: "greeting",
1229
- role: "agent",
1230
- text: greeting,
1231
- createdAt: 0
1232
- }
1233
- ],
1234
- toolSteps: [],
1235
- journey: null,
1236
- followUps: [],
1237
- streamingText: "",
1238
- pendingOffer: null,
1239
- error: null
1240
- };
1906
+ // src/runtime/tool-result-envelope.ts
1907
+ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
1908
+ "schemaVersion",
1909
+ "output",
1910
+ "presentationKinds",
1911
+ "ui"
1912
+ ]);
1913
+ function isRecord3(value) {
1914
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1241
1915
  }
1242
- function stateFromConversation(conversation, initialState) {
1243
- if (!conversation || conversation.messages.length === 0) return initialState;
1916
+ function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
1917
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
1918
+ return true;
1919
+ }
1920
+ if (typeof value === "number") return Number.isFinite(value);
1921
+ if (typeof value !== "object") return false;
1922
+ if (seen.has(value)) return false;
1923
+ seen.add(value);
1924
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
1925
+ ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
1926
+ );
1927
+ seen.delete(value);
1928
+ return valid;
1929
+ }
1930
+ function decodeEnvelope(value) {
1931
+ if (typeof value !== "string") return value;
1932
+ try {
1933
+ return JSON.parse(value);
1934
+ } catch {
1935
+ return null;
1936
+ }
1937
+ }
1938
+ function parseAgentToolResultEnvelope(value) {
1939
+ const decoded = decodeEnvelope(value);
1940
+ if (!isRecord3(decoded)) return null;
1941
+ const keys = Object.keys(decoded);
1942
+ if (keys.some((key) => !ENVELOPE_KEYS.has(key)) || decoded.schemaVersion !== "webless.tool-result.v1" || !isJsonValue2(decoded.output) || !Array.isArray(decoded.presentationKinds) || decoded.presentationKinds.length < 1 || decoded.presentationKinds.length > 16) {
1943
+ return null;
1944
+ }
1945
+ const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
1946
+ if (typeof kind !== "string") return [];
1947
+ const normalized = kind.trim();
1948
+ return normalized && normalized.length <= 128 ? [normalized] : [];
1949
+ });
1950
+ if (presentationKinds.length !== decoded.presentationKinds.length) {
1951
+ return null;
1952
+ }
1953
+ const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
1954
+ if (decoded.ui !== void 0 && !ui) return null;
1244
1955
  return {
1245
- ...initialState,
1246
- messages: conversation.messages,
1247
- phase: conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
1248
- streamingText: conversation.streamingText,
1249
- toolSteps: conversation.toolSteps
1956
+ schemaVersion: "webless.tool-result.v1",
1957
+ output: decoded.output,
1958
+ presentationKinds,
1959
+ ...ui ? { ui } : {}
1250
1960
  };
1251
1961
  }
1252
- function upsertToolStep(steps, item) {
1253
- const next = {
1254
- id: item.id,
1255
- kind: item.kind,
1256
- label: item.label,
1257
- state: item.state,
1962
+
1963
+ // src/react/lib/tool-result.ts
1964
+ var MAX_TEXT_LENGTH = 240;
1965
+ var MAX_DETAILS = 6;
1966
+ var MAX_LINKS = 4;
1967
+ var MAX_COLLECTION_ITEMS = 8;
1968
+ var INTERNAL_FACT_LABEL = /^(hs\b|createdate|created at|updatedate|updated at|firstname|first name|lastname|last name|vid|objectid|object id|all contact)/iu;
1969
+ function safeText(value) {
1970
+ if (typeof value !== "string") return "";
1971
+ return value.trim().slice(0, MAX_TEXT_LENGTH);
1972
+ }
1973
+ function safeHref(value) {
1974
+ const text = safeText(value);
1975
+ if (!text) return "";
1976
+ try {
1977
+ const url = new URL(text);
1978
+ return url.protocol === "https:" ? url.toString() : "";
1979
+ } catch {
1980
+ return "";
1981
+ }
1982
+ }
1983
+ function fallbackTitle(result) {
1984
+ if (result.status === "failed") return "Couldn\u2019t complete this action";
1985
+ if (result.status === "rejected") return "Action not approved";
1986
+ return "Action completed";
1987
+ }
1988
+ function bookingPresenter(kind) {
1989
+ return {
1990
+ kind,
1991
+ present: ({ envelope }) => {
1992
+ const card = bookingCardFromActionOutput(envelope.output);
1993
+ return card && card.type === kind ? { kind: "booking", card } : null;
1994
+ }
1995
+ };
1996
+ }
1997
+ function asRecord3(value) {
1998
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1999
+ }
2000
+ function parseFacts(value) {
2001
+ if (!Array.isArray(value)) return [];
2002
+ return value.flatMap((item) => {
2003
+ const fact = asRecord3(item);
2004
+ const label = safeText(fact?.label);
2005
+ const factValue = safeText(fact?.value);
2006
+ if (!label || !factValue) return [];
2007
+ if (INTERNAL_FACT_LABEL.test(label)) return [];
2008
+ return [{ label, value: factValue }];
2009
+ });
2010
+ }
2011
+ function parseLinks(output, defaultLabel) {
2012
+ const href = safeHref(output.href);
2013
+ const linkLabel = safeText(output.linkLabel) || defaultLabel;
2014
+ return href ? [{ label: linkLabel, href }] : [];
2015
+ }
2016
+ function entityResultPresenter() {
2017
+ return {
2018
+ kind: "entity_result",
2019
+ present: ({ envelope, result }) => {
2020
+ const output = asRecord3(envelope.output);
2021
+ if (!output) return null;
2022
+ const title = safeText(output.title) || fallbackTitle(result);
2023
+ const description = safeText(output.description);
2024
+ const facts = parseFacts(output.facts).filter((fact) => fact.value !== description && fact.value !== title).slice(0, MAX_DETAILS);
2025
+ return {
2026
+ kind: "entity",
2027
+ title,
2028
+ ...description && description !== title ? { description } : {},
2029
+ ...facts.length ? { details: facts } : {},
2030
+ ...parseLinks(output, "Open record").length ? { links: parseLinks(output, "Open record") } : {}
2031
+ };
2032
+ }
2033
+ };
2034
+ }
2035
+ function collectionResultPresenter() {
2036
+ return {
2037
+ kind: "collection",
2038
+ present: ({ envelope, result }) => {
2039
+ const output = asRecord3(envelope.output);
2040
+ if (!output) return null;
2041
+ const title = safeText(output.title) || fallbackTitleFromKind("collection");
2042
+ const items = Array.isArray(output.items) ? output.items.slice(0, MAX_COLLECTION_ITEMS).flatMap((item) => {
2043
+ const entry = asRecord3(item);
2044
+ if (!entry) return [];
2045
+ const itemTitle = safeText(entry.title);
2046
+ if (!itemTitle) return [];
2047
+ const description = safeText(entry.description);
2048
+ const details = parseFacts(entry.facts).slice(0, MAX_DETAILS);
2049
+ const href = safeHref(entry.href);
2050
+ return [
2051
+ {
2052
+ title: itemTitle,
2053
+ ...description ? { description } : {},
2054
+ ...details.length ? { details } : {},
2055
+ ...href ? { href } : {}
2056
+ }
2057
+ ];
2058
+ }) : [];
2059
+ return {
2060
+ kind: "collection",
2061
+ title,
2062
+ items
2063
+ };
2064
+ }
2065
+ };
2066
+ }
2067
+ function signatureResultPresenter() {
2068
+ return {
2069
+ kind: "signature",
2070
+ present: ({ envelope, result }) => {
2071
+ const output = asRecord3(envelope.output);
2072
+ if (!output) return null;
2073
+ const title = safeText(output.title) || fallbackTitleFromKind("signature");
2074
+ const description = safeText(output.description);
2075
+ const statusLabel = safeText(output.status);
2076
+ return {
2077
+ kind: "signature",
2078
+ title,
2079
+ ...description ? { description } : {},
2080
+ ...statusLabel ? { statusLabel } : {},
2081
+ ...parseLinks(output, "Sign now").length ? { links: parseLinks(output, "Sign now") } : {}
2082
+ };
2083
+ }
2084
+ };
2085
+ }
2086
+ function resultCardPresenter(kind) {
2087
+ return {
2088
+ kind,
2089
+ present: ({ envelope, result }) => {
2090
+ const output = asRecord3(envelope.output);
2091
+ if (!output) return null;
2092
+ const title = safeText(output.title) || fallbackTitleFromKind(kind);
2093
+ const description = safeText(output.description);
2094
+ const facts = parseFacts(output.facts).slice(0, MAX_DETAILS);
2095
+ return {
2096
+ kind: "summary",
2097
+ title,
2098
+ ...description && description !== title ? { description } : {},
2099
+ ...facts.length ? { details: facts } : {},
2100
+ ...parseLinks(output, "Open").length ? { links: parseLinks(output, "Open") } : {}
2101
+ };
2102
+ }
2103
+ };
2104
+ }
2105
+ function fallbackTitleFromKind(kind) {
2106
+ if (kind === "document") return "Document ready";
2107
+ if (kind === "signature") return "Ready to sign";
2108
+ if (kind === "payment") return "Payment link";
2109
+ if (kind === "collection") return "Results";
2110
+ if (kind === "confirmation_result") return "Confirmed";
2111
+ return "Saved";
2112
+ }
2113
+ var builtInVisitorToolResultRegistry = [
2114
+ bookingPresenter("booking_offer"),
2115
+ bookingPresenter("booking_confirmed"),
2116
+ bookingPresenter("booking_canceled"),
2117
+ entityResultPresenter(),
2118
+ collectionResultPresenter(),
2119
+ signatureResultPresenter(),
2120
+ resultCardPresenter("document"),
2121
+ resultCardPresenter("payment"),
2122
+ resultCardPresenter("confirmation_result")
2123
+ ];
2124
+ function resolvePresenterOutput(result, envelope, registry) {
2125
+ const presenters = [...registry, ...builtInVisitorToolResultRegistry];
2126
+ for (const presentationKind of envelope.presentationKinds) {
2127
+ const presenter = presenters.find(
2128
+ (candidate) => candidate.kind === presentationKind
2129
+ );
2130
+ if (!presenter) continue;
2131
+ try {
2132
+ const presented = presenter.present({
2133
+ envelope,
2134
+ presentationKind,
2135
+ result
2136
+ });
2137
+ if (presented) return presented;
2138
+ } catch {
2139
+ return null;
2140
+ }
2141
+ }
2142
+ return null;
2143
+ }
2144
+ function finalizeSummaryPresentation(result, proposed) {
2145
+ const title = safeText(proposed.title) || fallbackTitle(result);
2146
+ const description = safeText(proposed.description);
2147
+ const details = proposed.details?.slice(0, MAX_DETAILS).flatMap((detail) => {
2148
+ const label = safeText(detail.label);
2149
+ const value = safeText(detail.value);
2150
+ if (!label || !value) return [];
2151
+ if (INTERNAL_FACT_LABEL.test(label)) return [];
2152
+ if (value === title || value === description) return [];
2153
+ return [{ label, value }];
2154
+ });
2155
+ const links = proposed.links?.slice(0, MAX_LINKS).flatMap((link) => {
2156
+ const label = safeText(link.label);
2157
+ const href = safeHref(link.href);
2158
+ return label && href ? [{ label, href }] : [];
2159
+ });
2160
+ return {
2161
+ id: result.callId,
2162
+ toolName: result.toolName,
2163
+ status: result.status,
2164
+ kind: "summary",
2165
+ title,
2166
+ ...description && description !== title ? { description } : {},
2167
+ ...details?.length ? { details } : {},
2168
+ ...links?.length ? { links } : {}
2169
+ };
2170
+ }
2171
+ function presentVisitorToolResult(result, registry = []) {
2172
+ const envelope = parseAgentToolResultEnvelope(result.output) ?? legacyBookingEnvelope(result.output);
2173
+ const proposed = envelope ? resolvePresenterOutput(result, envelope, registry) : null;
2174
+ if (proposed?.kind === "booking") {
2175
+ return {
2176
+ id: result.callId,
2177
+ toolName: result.toolName,
2178
+ status: result.status,
2179
+ kind: "booking",
2180
+ card: proposed.card
2181
+ };
2182
+ }
2183
+ if (envelope?.ui && envelope.presentationKinds.includes("tool_input")) {
2184
+ return {
2185
+ id: result.callId,
2186
+ toolName: result.toolName,
2187
+ status: result.status,
2188
+ kind: "hidden"
2189
+ };
2190
+ }
2191
+ if (!proposed) {
2192
+ if (result.status === "failed" || result.status === "rejected") {
2193
+ return {
2194
+ id: result.callId,
2195
+ toolName: result.toolName,
2196
+ status: result.status,
2197
+ kind: "summary",
2198
+ title: fallbackTitle(result)
2199
+ };
2200
+ }
2201
+ return {
2202
+ id: result.callId,
2203
+ toolName: result.toolName,
2204
+ status: result.status,
2205
+ kind: "hidden"
2206
+ };
2207
+ }
2208
+ if (proposed.kind === "entity") {
2209
+ return {
2210
+ id: result.callId,
2211
+ toolName: result.toolName,
2212
+ status: result.status,
2213
+ ...proposed
2214
+ };
2215
+ }
2216
+ if (proposed.kind === "collection") {
2217
+ return {
2218
+ id: result.callId,
2219
+ toolName: result.toolName,
2220
+ status: result.status,
2221
+ ...proposed
2222
+ };
2223
+ }
2224
+ if (proposed.kind === "signature") {
2225
+ return {
2226
+ id: result.callId,
2227
+ toolName: result.toolName,
2228
+ status: result.status,
2229
+ ...proposed
2230
+ };
2231
+ }
2232
+ return finalizeSummaryPresentation(result, proposed);
2233
+ }
2234
+ function legacyBookingEnvelope(output) {
2235
+ const card = bookingCardFromActionOutput(output);
2236
+ return card ? {
2237
+ schemaVersion: "webless.tool-result.v1",
2238
+ output: card,
2239
+ presentationKinds: [card.type]
2240
+ } : null;
2241
+ }
2242
+
2243
+ // src/react/lib/visitor-input.ts
2244
+ function shouldRenderVisitorInputCard(request) {
2245
+ if (request.kind === "tool-approval" || request.kind === "session-limit") {
2246
+ return true;
2247
+ }
2248
+ if (request.kind === "question") {
2249
+ return (request.options?.length ?? 0) > 0;
2250
+ }
2251
+ return false;
2252
+ }
2253
+ function isChatCollectibleInputRequest(request) {
2254
+ return request.kind === "question" && (request.options?.length ?? 0) === 0;
2255
+ }
2256
+ function appendChatCollectiblePrompts(messages, requests) {
2257
+ const next = [...messages];
2258
+ for (const request of requests.filter(isChatCollectibleInputRequest)) {
2259
+ const prompt = request.prompt.trim();
2260
+ if (!prompt) continue;
2261
+ const last = next.at(-1);
2262
+ if (last?.role === "agent" && last.text.trim() === prompt) continue;
2263
+ next.push({
2264
+ id: `agent-input-${request.requestId}`,
2265
+ role: "agent",
2266
+ text: prompt,
2267
+ createdAt: Date.now()
2268
+ });
2269
+ }
2270
+ return next;
2271
+ }
2272
+ function chatInputResponseForText(requests, text) {
2273
+ const trimmed = text.trim();
2274
+ if (!trimmed) return null;
2275
+ const pending = requests.find(isChatCollectibleInputRequest);
2276
+ if (!pending) return null;
2277
+ return { requestId: pending.requestId, text: trimmed };
2278
+ }
2279
+ function normalizeAssistantDedupeKey(text) {
2280
+ return text.trim().replace(/\s+/g, " ").toLowerCase();
2281
+ }
2282
+ function isNearDuplicateAssistantText(left, right) {
2283
+ const a = normalizeAssistantDedupeKey(left);
2284
+ const b = normalizeAssistantDedupeKey(right);
2285
+ if (!a || !b) return false;
2286
+ if (a === b) return true;
2287
+ const shorter = a.length <= b.length ? a : b;
2288
+ const longer = a.length <= b.length ? b : a;
2289
+ if (shorter.length < 40) return false;
2290
+ return longer.startsWith(
2291
+ shorter.slice(0, Math.floor(shorter.length * 0.85))
2292
+ );
2293
+ }
2294
+ function appendAgentTurnMessage(messages, displayText) {
2295
+ const trimmed = displayText.trim();
2296
+ if (!trimmed) return [...messages];
2297
+ const agentMessage = {
2298
+ id: `agent-${Date.now()}`,
2299
+ role: "agent",
2300
+ text: trimmed,
2301
+ createdAt: Date.now()
2302
+ };
2303
+ const last = messages.at(-1);
2304
+ if (last?.role === "agent" && (last.id.startsWith("agent-input-") || isNearDuplicateAssistantText(last.text, trimmed))) {
2305
+ return [...messages.slice(0, -1), agentMessage];
2306
+ }
2307
+ return [...messages, agentMessage];
2308
+ }
2309
+
2310
+ // src/react/hooks/useAgentChat.ts
2311
+ var DEFAULT_GREETING = "Hi! I can search this site or bring in a specialist when it helps. What would you like to know?";
2312
+ function createInitialState(greeting = DEFAULT_GREETING) {
2313
+ return {
2314
+ phase: "idle",
2315
+ messages: [
2316
+ {
2317
+ id: "greeting",
2318
+ role: "agent",
2319
+ text: greeting,
2320
+ createdAt: 0
2321
+ }
2322
+ ],
2323
+ toolSteps: [],
2324
+ journey: null,
2325
+ followUps: [],
2326
+ streamingText: "",
2327
+ pendingOffer: null,
2328
+ pendingInputs: [],
2329
+ toolResults: [],
2330
+ error: null
2331
+ };
2332
+ }
2333
+ function stateFromConversation(conversation, initialState) {
2334
+ if (!conversation || conversation.messages.length === 0) return initialState;
2335
+ const pendingInputs = conversation.pendingInputs ?? [];
2336
+ const hasWaitingInput = pendingInputs.length > 0;
2337
+ return {
2338
+ ...initialState,
2339
+ messages: conversation.messages,
2340
+ phase: hasWaitingInput ? "waiting-input" : conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
2341
+ streamingText: conversation.streamingText,
2342
+ toolSteps: conversation.toolSteps,
2343
+ toolResults: conversation.toolResults,
2344
+ ...hasWaitingInput ? { pendingInputs } : {}
2345
+ };
2346
+ }
2347
+ function upsertToolStep(steps, item) {
2348
+ const next = {
2349
+ id: item.id,
2350
+ kind: item.kind,
2351
+ label: item.label,
2352
+ state: item.state,
1258
2353
  ...item.detail ? { detail: item.detail } : {}
1259
2354
  };
1260
2355
  const index = steps.findIndex((step) => step.id === item.id);
1261
2356
  if (index < 0) return [...steps, next];
1262
2357
  return steps.map((step, stepIndex) => stepIndex === index ? next : step);
1263
2358
  }
2359
+ function applyBookingOffer(prev, card) {
2360
+ const pendingOffer = preferBookingOffer(prev.pendingOffer, card);
2361
+ const last = prev.messages.at(-1);
2362
+ if (last?.role === "agent" && prev.phase === "complete") {
2363
+ return {
2364
+ ...prev,
2365
+ pendingOffer,
2366
+ messages: [
2367
+ ...prev.messages.slice(0, -1),
2368
+ {
2369
+ ...last,
2370
+ text: ensureBookingOfferText(last.text, pendingOffer)
2371
+ }
2372
+ ]
2373
+ };
2374
+ }
2375
+ return { ...prev, pendingOffer };
2376
+ }
1264
2377
  function completeActivePlanning(steps) {
1265
2378
  return steps.map(
1266
2379
  (step) => step.kind === "planning" && step.state === "active" ? {
@@ -1270,6 +2383,9 @@ function completeActivePlanning(steps) {
1270
2383
  } : step
1271
2384
  );
1272
2385
  }
2386
+ function isJsonRecord(value) {
2387
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2388
+ }
1273
2389
  function useAgentChat({
1274
2390
  customerId,
1275
2391
  getUnpublishedPreviewGrant,
@@ -1279,7 +2395,8 @@ function useAgentChat({
1279
2395
  runtimeOrigin,
1280
2396
  visitorSessionId,
1281
2397
  storageKeyPrefix,
1282
- greeting
2398
+ greeting,
2399
+ toolResultRegistry
1283
2400
  }) {
1284
2401
  const initialState = (0, import_react2.useMemo)(
1285
2402
  () => createInitialState(greeting?.trim() || DEFAULT_GREETING),
@@ -1287,6 +2404,8 @@ function useAgentChat({
1287
2404
  );
1288
2405
  const previewGrantProviderRef = (0, import_react2.useRef)(getUnpublishedPreviewGrant);
1289
2406
  previewGrantProviderRef.current = getUnpublishedPreviewGrant;
2407
+ const toolResultRegistryRef = (0, import_react2.useRef)(toolResultRegistry);
2408
+ toolResultRegistryRef.current = toolResultRegistry;
1290
2409
  const resolveUnpublishedPreviewGrant = () => {
1291
2410
  const provider = previewGrantProviderRef.current;
1292
2411
  if (!provider) {
@@ -1381,14 +2500,18 @@ function useAgentChat({
1381
2500
  messages: state.messages,
1382
2501
  pending: isAgentBusy(state.phase),
1383
2502
  streamingText: state.streamingText,
1384
- toolSteps: state.toolSteps
2503
+ toolSteps: state.toolSteps,
2504
+ toolResults: state.toolResults ?? [],
2505
+ ...state.pendingInputs && state.pendingInputs.length > 0 ? { pendingInputs: state.pendingInputs } : {}
1385
2506
  });
1386
2507
  }, [
1387
2508
  resolvedStorageKeyPrefix,
1388
2509
  state.messages,
2510
+ state.pendingInputs,
1389
2511
  state.phase,
1390
2512
  state.streamingText,
1391
2513
  state.toolSteps,
2514
+ state.toolResults,
1392
2515
  visitorId
1393
2516
  ]);
1394
2517
  const reset = (0, import_react2.useCallback)(() => {
@@ -1401,13 +2524,20 @@ function useAgentChat({
1401
2524
  }, [initialState, resolvedStorageKeyPrefix, visitorId]);
1402
2525
  const runTurn = (0, import_react2.useCallback)(
1403
2526
  async (input) => {
1404
- const { controller, initialText = "", resume, visitorText } = input;
2527
+ const {
2528
+ controller,
2529
+ initialText = "",
2530
+ responses,
2531
+ resume,
2532
+ visitorText
2533
+ } = input;
1405
2534
  const { signal } = controller;
1406
2535
  const isActiveRun = () => runRef.current === controller && !signal.aborted;
1407
2536
  try {
1408
2537
  let streamStarted = Boolean(initialText);
1409
2538
  let streamed = initialText;
1410
2539
  const capturedOffers = [];
2540
+ let capturedInputCount = 0;
1411
2541
  const handlers = {
1412
2542
  onWork: (item) => {
1413
2543
  if (!isActiveRun()) return;
@@ -1417,11 +2547,62 @@ function useAgentChat({
1417
2547
  toolSteps: upsertToolStep(prev.toolSteps, item)
1418
2548
  }));
1419
2549
  },
1420
- onActionResult: (output) => {
1421
- const offer = bookingOfferFromActionOutput(output);
1422
- if (!offer) return;
1423
- capturedOffers.push(offer);
1424
- setState((prev) => ({ ...prev, pendingOffer: offer }));
2550
+ onToolResult: (result) => {
2551
+ const presentation = presentVisitorToolResult(
2552
+ result,
2553
+ toolResultRegistryRef.current
2554
+ );
2555
+ if (presentation.kind === "booking") {
2556
+ const card = presentation.card;
2557
+ if (card.type === "booking_offer") {
2558
+ capturedOffers.push(card);
2559
+ setState((prev) => applyBookingOffer(prev, card));
2560
+ return;
2561
+ }
2562
+ if (!isActiveRun()) return;
2563
+ if (card.type === "booking_confirmed") {
2564
+ pendingBookingRef.current = card;
2565
+ savePendingWidgetBooking(
2566
+ resolvedStorageKeyPrefix,
2567
+ visitorId,
2568
+ card
2569
+ );
2570
+ } else if (card.type === "booking_canceled" && pendingBookingRef.current?.eventUri === card.eventUri) {
2571
+ pendingBookingRef.current = null;
2572
+ clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
2573
+ }
2574
+ return;
2575
+ }
2576
+ if (!isActiveRun()) return;
2577
+ if (presentation.kind === "hidden") return;
2578
+ if (presentation.kind === "input") return;
2579
+ setState((prev) => ({
2580
+ ...prev,
2581
+ toolResults: [
2582
+ ...(prev.toolResults ?? []).filter(
2583
+ (item) => item.id !== presentation.id
2584
+ ),
2585
+ presentation
2586
+ ]
2587
+ }));
2588
+ },
2589
+ onInputRequest: (requests) => {
2590
+ if (!isActiveRun()) return;
2591
+ capturedInputCount += requests.length;
2592
+ const cardRequests = requests.filter(shouldRenderVisitorInputCard);
2593
+ const chatRequests = requests.filter(isChatCollectibleInputRequest);
2594
+ setState((prev) => ({
2595
+ ...prev,
2596
+ phase: cardRequests.length > 0 ? "waiting-input" : "complete",
2597
+ pendingInputs: [...requests],
2598
+ ...chatRequests.length > 0 ? {
2599
+ messages: appendChatCollectiblePrompts(
2600
+ prev.messages,
2601
+ chatRequests
2602
+ ),
2603
+ toolSteps: completeActivePlanning(prev.toolSteps)
2604
+ } : {}
2605
+ }));
1425
2606
  },
1426
2607
  onDelta: (delta) => {
1427
2608
  if (!isActiveRun()) return;
@@ -1447,7 +2628,11 @@ function useAgentChat({
1447
2628
  streamStarted = true;
1448
2629
  }
1449
2630
  };
1450
- let finalText = resume ? await clientRef.current.resumeTurn({
2631
+ let finalText = responses ? await clientRef.current.respondTurn({
2632
+ handlers,
2633
+ responses,
2634
+ signal
2635
+ }) : resume ? await clientRef.current.resumeTurn({
1451
2636
  handlers,
1452
2637
  initialText,
1453
2638
  message: visitorText,
@@ -1459,17 +2644,15 @@ function useAgentChat({
1459
2644
  signal
1460
2645
  });
1461
2646
  }
1462
- if (!isActiveRun() || finalText === null) return;
2647
+ if (!isActiveRun() || finalText === null) return null;
2648
+ if (!finalText.trim() && capturedInputCount > 0) {
2649
+ runRef.current = null;
2650
+ return null;
2651
+ }
1463
2652
  const displayText = ensureBookingOfferText(
1464
2653
  finalText,
1465
2654
  capturedOffers.at(-1) ?? null
1466
2655
  );
1467
- const agentMessage = {
1468
- id: `agent-${Date.now()}`,
1469
- role: "agent",
1470
- text: displayText,
1471
- createdAt: Date.now()
1472
- };
1473
2656
  const parsedCards = extractToolCards(displayText);
1474
2657
  for (const card of parsedCards) {
1475
2658
  if (card.type === "booking_confirmed") {
@@ -1483,21 +2666,25 @@ function useAgentChat({
1483
2666
  }
1484
2667
  setState((prev) => ({
1485
2668
  ...prev,
1486
- phase: "complete",
1487
- messages: [...prev.messages, agentMessage],
2669
+ phase: (prev.pendingInputs ?? []).some(shouldRenderVisitorInputCard) ? "waiting-input" : "complete",
2670
+ messages: appendAgentTurnMessage(prev.messages, displayText),
1488
2671
  toolSteps: completeActivePlanning(prev.toolSteps),
1489
2672
  streamingText: "",
1490
- pendingOffer: null,
2673
+ pendingOffer: capturedOffers.at(-1) ?? prev.pendingOffer,
2674
+ pendingInputs: (prev.pendingInputs ?? []).filter(
2675
+ isChatCollectibleInputRequest
2676
+ ),
1491
2677
  followUps: [],
1492
2678
  journey: null
1493
2679
  }));
1494
2680
  runRef.current = null;
2681
+ return displayText;
1495
2682
  } catch (error) {
1496
2683
  if (error instanceof DOMException && error.name === "AbortError")
1497
- return;
1498
- if (!isActiveRun()) return;
2684
+ return null;
2685
+ if (!isActiveRun()) return null;
1499
2686
  const message = formatAgentError(error);
1500
- if (!message) return;
2687
+ if (!message) return null;
1501
2688
  setState((prev) => ({
1502
2689
  ...prev,
1503
2690
  phase: "error",
@@ -1513,6 +2700,7 @@ function useAgentChat({
1513
2700
  error: message
1514
2701
  }));
1515
2702
  runRef.current = null;
2703
+ return null;
1516
2704
  }
1517
2705
  },
1518
2706
  [resolvedStorageKeyPrefix, visitorId]
@@ -1540,6 +2728,49 @@ function useAgentChat({
1540
2728
  );
1541
2729
  const submit = (0, import_react2.useCallback)(
1542
2730
  async (visitorText, options) => {
2731
+ const trimmed = visitorText.trim();
2732
+ if (!trimmed) return null;
2733
+ const chatResponse = chatInputResponseForText(
2734
+ state.pendingInputs ?? [],
2735
+ trimmed
2736
+ );
2737
+ if (chatResponse) {
2738
+ const visitorMessage2 = {
2739
+ id: `visitor-${Date.now()}`,
2740
+ role: "visitor",
2741
+ text: trimmed,
2742
+ createdAt: Date.now()
2743
+ };
2744
+ if (runRef.current) {
2745
+ runRef.current.abort();
2746
+ clientRef.current.cancelActive();
2747
+ }
2748
+ const controller2 = new AbortController();
2749
+ runRef.current = controller2;
2750
+ setState((prev) => ({
2751
+ ...prev,
2752
+ phase: "running-tools",
2753
+ messages: [...prev.messages, visitorMessage2],
2754
+ pendingInputs: (prev.pendingInputs ?? []).filter(
2755
+ (request) => request.requestId !== chatResponse.requestId
2756
+ ),
2757
+ toolSteps: [
2758
+ {
2759
+ id: "planning",
2760
+ kind: "planning",
2761
+ label: "Understanding your question",
2762
+ state: "active"
2763
+ }
2764
+ ],
2765
+ error: null
2766
+ }));
2767
+ return await runTurn({
2768
+ controller: controller2,
2769
+ responses: [chatResponse],
2770
+ resume: false,
2771
+ visitorText: ""
2772
+ });
2773
+ }
1543
2774
  if (runRef.current) {
1544
2775
  runRef.current.abort();
1545
2776
  clientRef.current.cancelActive();
@@ -1574,11 +2805,17 @@ ${outgoing}` : outgoing;
1574
2805
  followUps: [],
1575
2806
  streamingText: "",
1576
2807
  pendingOffer: null,
2808
+ pendingInputs: [],
2809
+ toolResults: [],
1577
2810
  error: null
1578
2811
  }));
1579
- await runTurn({ controller, resume: false, visitorText: runtimeText });
2812
+ return await runTurn({
2813
+ controller,
2814
+ resume: false,
2815
+ visitorText: runtimeText
2816
+ });
1580
2817
  },
1581
- [runTurn]
2818
+ [runTurn, state.pendingInputs]
1582
2819
  );
1583
2820
  const retry = (0, import_react2.useCallback)(async () => {
1584
2821
  const visitorMessage = [...state.messages].reverse().find((message) => message.role === "visitor");
@@ -1604,6 +2841,8 @@ ${outgoing}` : outgoing;
1604
2841
  followUps: [],
1605
2842
  streamingText: "",
1606
2843
  pendingOffer: null,
2844
+ pendingInputs: [],
2845
+ toolResults: [],
1607
2846
  error: null
1608
2847
  }));
1609
2848
  await runTurn({
@@ -1612,6 +2851,50 @@ ${outgoing}` : outgoing;
1612
2851
  visitorText: visitorTurnText(visitorMessage)
1613
2852
  });
1614
2853
  }, [runTurn, state.messages]);
2854
+ const respondToToolInput = (0, import_react2.useCallback)(
2855
+ async (surface, values) => {
2856
+ const details = JSON.stringify(values);
2857
+ await submit(`${surface.title} details provided`, {
2858
+ runtimeText: [
2859
+ `Structured input provided for ${surface.toolSlug}.`,
2860
+ surface.operationId ? `Operation: ${surface.operationId}.` : "",
2861
+ `Use these exact values and retry the action: ${details}`,
2862
+ "Do not invent or alter any values."
2863
+ ].filter(Boolean).join("\n")
2864
+ });
2865
+ },
2866
+ [submit]
2867
+ );
2868
+ const respondToInput = (0, import_react2.useCallback)(
2869
+ async (response) => {
2870
+ if (runRef.current) return;
2871
+ const pending = state.pendingInputs?.find(
2872
+ (request) => request.requestId === response.requestId
2873
+ );
2874
+ if (!pending) return;
2875
+ if (pending.ui && isJsonRecord(response.value)) {
2876
+ await respondToToolInput(pending.ui, response.value);
2877
+ return;
2878
+ }
2879
+ const controller = new AbortController();
2880
+ runRef.current = controller;
2881
+ setState((prev) => ({
2882
+ ...prev,
2883
+ phase: "running-tools",
2884
+ pendingInputs: (prev.pendingInputs ?? []).filter(
2885
+ (request) => request.requestId !== response.requestId
2886
+ ),
2887
+ error: null
2888
+ }));
2889
+ await runTurn({
2890
+ controller,
2891
+ responses: [response],
2892
+ resume: false,
2893
+ visitorText: ""
2894
+ });
2895
+ },
2896
+ [respondToToolInput, runTurn, state.pendingInputs]
2897
+ );
1615
2898
  (0, import_react2.useEffect)(() => {
1616
2899
  const conversation = loadPersistedAgentConversation(
1617
2900
  resolvedStorageKeyPrefix,
@@ -1645,6 +2928,8 @@ ${outgoing}` : outgoing;
1645
2928
  state,
1646
2929
  reset,
1647
2930
  retry,
2931
+ respondToInput,
2932
+ respondToToolInput,
1648
2933
  submit,
1649
2934
  rememberBooking,
1650
2935
  forgetBooking,
@@ -1709,7 +2994,7 @@ function unregisterAgentPanelController(customerId) {
1709
2994
  }
1710
2995
 
1711
2996
  // src/react/components/AgentRail/AgentRail.tsx
1712
- var import_react9 = require("react");
2997
+ var import_react10 = require("react");
1713
2998
 
1714
2999
  // src/react/types/conversation.ts
1715
3000
  var defaultAgentRailTheme = {
@@ -1723,8 +3008,8 @@ var defaultAgentRailTheme = {
1723
3008
  textMuted: "#5a6378",
1724
3009
  textSubtle: "#8a94a8",
1725
3010
  border: "rgb(42 51 70 / 0.1)",
1726
- visitorBubble: "#6f16ff",
1727
- visitorText: "#ffffff",
3011
+ visitorBubble: "#f3edff",
3012
+ visitorText: "#171b2a",
1728
3013
  success: "#18794e",
1729
3014
  danger: "#c94b63",
1730
3015
  fontBody: '"Mulish", "Avenir Next", "Segoe UI", sans-serif',
@@ -1741,7 +3026,8 @@ var defaultDarkAgentRailTheme = {
1741
3026
  textMuted: "#b6bfce",
1742
3027
  textSubtle: "#919cad",
1743
3028
  border: "rgb(226 232 240 / 0.16)",
1744
- visitorBubble: "#7c3aed",
3029
+ visitorBubble: "#2b2140",
3030
+ visitorText: "#f5f7fb",
1745
3031
  success: "#55cf91",
1746
3032
  danger: "#ff8da1"
1747
3033
  };
@@ -1779,10 +3065,20 @@ function resolveAgentColorScheme(colorScheme = "auto", prefersDarkMode) {
1779
3065
  // src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
1780
3066
  var import_react5 = require("react");
1781
3067
  var import_jsx_runtime = require("react/jsx-runtime");
3068
+ function joinLabels(labels) {
3069
+ if (labels.length <= 1) return labels[0] ?? "";
3070
+ if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
3071
+ return `${labels.slice(0, -1).join(", ")} and ${labels.at(-1) ?? ""}`;
3072
+ }
1782
3073
  function workSummary(steps, failed, brandLabel) {
3074
+ const activeSpecialists = steps.filter(
3075
+ (step) => step.kind === "specialist" && step.state === "active"
3076
+ );
3077
+ if (activeSpecialists.length > 0)
3078
+ return `Working with ${joinLabels(
3079
+ activeSpecialists.map((step) => step.label)
3080
+ )}`;
1783
3081
  const active = [...steps].reverse().find((step) => step.state === "active");
1784
- if (active?.kind === "specialist")
1785
- return `${active.label} is reviewing your question`;
1786
3082
  if (active?.kind === "search") return "Searching this site";
1787
3083
  if (active)
1788
3084
  return brandLabel ? `${brandLabel} is choosing the best way to help` : "Choosing the best way to help";
@@ -1800,7 +3096,7 @@ function workSummary(steps, failed, brandLabel) {
1800
3096
  if (specialists.length === 1)
1801
3097
  return `Brought in ${specialists[0]?.label}`;
1802
3098
  if (searched) return "Searched this site";
1803
- return "Answer ready";
3099
+ return brandLabel ? `Answered with ${brandLabel}` : "Answer ready";
1804
3100
  }
1805
3101
  function stepLabel(step, brandLabel) {
1806
3102
  return step.kind === "planning" ? brandLabel : step.label;
@@ -1818,36 +3114,11 @@ function stepDetail(step, steps) {
1818
3114
  return "Searched this site";
1819
3115
  return step.detail;
1820
3116
  }
1821
- function SearchIcon() {
1822
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
1823
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "7", cy: "7", r: "3.75", stroke: "currentColor", strokeWidth: "1.4" }),
1824
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1825
- "path",
1826
- {
1827
- d: "m10 10 3 3",
1828
- stroke: "currentColor",
1829
- strokeWidth: "1.4",
1830
- strokeLinecap: "round"
1831
- }
1832
- )
1833
- ] });
1834
- }
1835
- function PlanningIcon() {
1836
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1837
- "path",
1838
- {
1839
- d: "M4 4.5h8M4 8h5.5M4 11.5h7",
1840
- stroke: "currentColor",
1841
- strokeWidth: "1.4",
1842
- strokeLinecap: "round"
1843
- }
1844
- ) });
1845
- }
1846
3117
  function AgentActivityBubble({
1847
3118
  brandLabel = "",
1848
- brandLogoUrl,
1849
3119
  failed = false,
1850
- steps
3120
+ steps,
3121
+ onRetryStep
1851
3122
  }) {
1852
3123
  const active = steps.some((step) => step.state === "active");
1853
3124
  const receiptId = steps.map((step) => step.id).join(":");
@@ -1855,6 +3126,10 @@ function AgentActivityBubble({
1855
3126
  null
1856
3127
  );
1857
3128
  const detailsOpen = active || expandedReceiptId === receiptId;
3129
+ const delegationCount = steps.filter(
3130
+ (step) => step.kind === "specialist"
3131
+ ).length;
3132
+ const delegated = delegationCount > 0 && steps.some((step) => step.kind === "planning");
1858
3133
  const visibleSteps = steps.filter(
1859
3134
  (step) => step.kind !== "planning" || Boolean(brandLabel)
1860
3135
  );
@@ -1887,10 +3162,11 @@ function AgentActivityBubble({
1887
3162
  ),
1888
3163
  detailsOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "agent-activity-bubble__steps", children: visibleSteps.map((step) => {
1889
3164
  const detail = stepDetail(step, steps);
3165
+ const child = delegated && step.kind === "specialist";
1890
3166
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1891
3167
  "li",
1892
3168
  {
1893
- className: "agent-activity-bubble__step",
3169
+ className: `agent-activity-bubble__step${child ? " agent-activity-bubble__step--child" : ""}`,
1894
3170
  "data-kind": step.kind,
1895
3171
  "data-state": step.state,
1896
3172
  children: [
@@ -1898,25 +3174,27 @@ function AgentActivityBubble({
1898
3174
  "span",
1899
3175
  {
1900
3176
  className: "agent-activity-bubble__step-icon",
1901
- "aria-hidden": "true",
1902
- children: step.kind === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1903
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlanningIcon, {}),
1904
- brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1905
- "img",
1906
- {
1907
- src: brandLogoUrl,
1908
- alt: "",
1909
- onError: (event) => {
1910
- event.currentTarget.hidden = true;
1911
- }
1912
- }
1913
- ) : null
1914
- ] }) : step.kind === "search" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
3177
+ "aria-hidden": "true"
1915
3178
  }
1916
3179
  ),
1917
3180
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-copy", children: [
1918
3181
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-heading", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: stepLabel(step, brandLabel) }) }),
1919
- detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
3182
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null,
3183
+ delegated && step.kind === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__delegation", children: [
3184
+ "Delegated ",
3185
+ delegationCount,
3186
+ " ",
3187
+ delegationCount === 1 ? "task" : "tasks"
3188
+ ] }) : null,
3189
+ step.state === "error" && onRetryStep ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3190
+ "button",
3191
+ {
3192
+ type: "button",
3193
+ className: "agent-activity-bubble__step-retry",
3194
+ onClick: () => onRetryStep(step),
3195
+ children: "Retry"
3196
+ }
3197
+ ) : null
1920
3198
  ] })
1921
3199
  ]
1922
3200
  },
@@ -1931,59 +3209,190 @@ function AgentActivityBubble({
1931
3209
  var import_react6 = require("react");
1932
3210
  var import_jsx_runtime2 = require("react/jsx-runtime");
1933
3211
  function SendIcon() {
1934
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 12V4M8 4l-3 3M8 4l3 3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
3212
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3213
+ "path",
3214
+ {
3215
+ d: "M8 12V4M8 4l-3 3M8 4l3 3",
3216
+ stroke: "currentColor",
3217
+ strokeWidth: "1.5",
3218
+ strokeLinecap: "round",
3219
+ strokeLinejoin: "round"
3220
+ }
3221
+ ) });
3222
+ }
3223
+ function emptyValues(form) {
3224
+ const values = {};
3225
+ for (const field of form?.fields ?? []) values[field.id] = "";
3226
+ return values;
1935
3227
  }
1936
3228
  function Composer({
1937
3229
  disabled = false,
1938
3230
  placeholder = "Ask anything\u2026",
1939
3231
  variant = "default",
3232
+ form = null,
1940
3233
  onSubmit
1941
3234
  }) {
1942
3235
  const [value, setValue] = (0, import_react6.useState)("");
3236
+ const [values, setValues] = (0, import_react6.useState)(
3237
+ () => emptyValues(form)
3238
+ );
3239
+ const [blurred, setBlurred] = (0, import_react6.useState)({});
1943
3240
  const inputRef = (0, import_react6.useRef)(null);
1944
- function submitCurrent() {
3241
+ const firstFieldRef = (0, import_react6.useRef)(null);
3242
+ const formId = (0, import_react6.useId)();
3243
+ const activeForm = form;
3244
+ const canSendForm = activeForm ? isComposerFormComplete(activeForm, values) : Boolean(value.trim());
3245
+ (0, import_react6.useEffect)(() => {
3246
+ setValues(emptyValues(form));
3247
+ setBlurred({});
3248
+ }, [form?.id]);
3249
+ (0, import_react6.useEffect)(() => {
3250
+ if (activeForm) firstFieldRef.current?.focus();
3251
+ }, [activeForm?.id]);
3252
+ function submitChat() {
1945
3253
  const trimmed = value.trim();
1946
3254
  if (!trimmed || disabled) return;
1947
3255
  onSubmit?.(trimmed);
1948
3256
  setValue("");
1949
3257
  inputRef.current?.focus();
1950
3258
  }
3259
+ function submitForm() {
3260
+ if (!activeForm || disabled || !canSendForm) return;
3261
+ onSubmit?.(formatComposerFormMessage(activeForm, values));
3262
+ setValues(emptyValues(activeForm));
3263
+ setBlurred({});
3264
+ }
1951
3265
  function handleSubmit(event) {
1952
3266
  event.preventDefault();
1953
- submitCurrent();
3267
+ if (activeForm) submitForm();
3268
+ else submitChat();
1954
3269
  }
1955
- function handleKeyDown(event) {
3270
+ function handleChatKeyDown(event) {
1956
3271
  if (event.key === "Enter" && !event.shiftKey) {
1957
3272
  event.preventDefault();
1958
- submitCurrent();
3273
+ submitChat();
1959
3274
  }
1960
3275
  }
1961
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("form", { className: `composer${variant === "dock" ? " composer--dock" : ""}`, onSubmit: handleSubmit, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "composer__field", children: [
1962
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1963
- "textarea",
1964
- {
1965
- ref: inputRef,
1966
- className: "composer__input",
1967
- rows: 1,
1968
- value,
1969
- placeholder,
1970
- disabled,
1971
- "aria-label": "Message",
1972
- onChange: (event) => setValue(event.target.value),
1973
- onKeyDown: handleKeyDown
1974
- }
1975
- ),
1976
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1977
- "button",
1978
- {
1979
- type: "submit",
1980
- className: "composer__send",
1981
- disabled: disabled || !value.trim(),
1982
- "aria-label": "Send message",
1983
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
1984
- }
1985
- )
1986
- ] }) });
3276
+ function handleFormKeyDown(event) {
3277
+ const target = event.target;
3278
+ const isTextarea = Boolean(target && "tagName" in target && target.tagName === "TEXTAREA");
3279
+ if (event.key === "Enter" && !event.shiftKey && !isTextarea) {
3280
+ event.preventDefault();
3281
+ submitForm();
3282
+ }
3283
+ }
3284
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3285
+ "form",
3286
+ {
3287
+ className: [
3288
+ "composer",
3289
+ variant === "dock" ? "composer--dock" : "",
3290
+ activeForm ? "composer--form" : ""
3291
+ ].filter(Boolean).join(" "),
3292
+ onSubmit: handleSubmit,
3293
+ children: activeForm ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "composer__sheet", role: "group", "aria-label": "Required details", children: [
3294
+ activeForm.fields.map((field, index) => {
3295
+ const fieldId = `${formId}-${field.id}`;
3296
+ const invalid = Boolean(blurred[field.id]) && !isValidComposerFieldValue(field, values[field.id] ?? "");
3297
+ const controlProps = {
3298
+ id: fieldId,
3299
+ name: field.id,
3300
+ disabled,
3301
+ required: field.required,
3302
+ autoComplete: field.autocomplete,
3303
+ placeholder: field.placeholder,
3304
+ spellCheck: false,
3305
+ value: values[field.id] ?? "",
3306
+ "aria-invalid": invalid || void 0,
3307
+ "aria-describedby": invalid ? `${fieldId}-error` : void 0,
3308
+ onBlur: () => setBlurred((current) => ({ ...current, [field.id]: true })),
3309
+ onChange: (event) => {
3310
+ const next = readComposerControlValue(event);
3311
+ setValues((current) => ({
3312
+ ...current,
3313
+ [field.id]: next
3314
+ }));
3315
+ },
3316
+ onKeyDown: handleFormKeyDown
3317
+ };
3318
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3319
+ "div",
3320
+ {
3321
+ className: [
3322
+ "composer__row",
3323
+ field.kind === "textarea" ? "composer__row--grow" : ""
3324
+ ].filter(Boolean).join(" "),
3325
+ children: [
3326
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "composer__label", htmlFor: fieldId, children: [
3327
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "composer__sr-only", children: field.label }),
3328
+ field.kind === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3329
+ "textarea",
3330
+ {
3331
+ ...controlProps,
3332
+ ref: index === 0 ? (node) => {
3333
+ firstFieldRef.current = node;
3334
+ } : void 0,
3335
+ className: "composer__control composer__control--area",
3336
+ rows: 3
3337
+ }
3338
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3339
+ "input",
3340
+ {
3341
+ ...controlProps,
3342
+ ref: index === 0 ? (node) => {
3343
+ firstFieldRef.current = node;
3344
+ } : void 0,
3345
+ className: "composer__control",
3346
+ type: field.kind,
3347
+ inputMode: field.kind === "tel" ? "tel" : void 0
3348
+ }
3349
+ )
3350
+ ] }),
3351
+ invalid ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "composer__error", id: `${fieldId}-error`, children: field.kind === "email" ? "Enter a valid email to continue." : `Add your ${field.label.toLowerCase()} to continue.` }) : null
3352
+ ]
3353
+ },
3354
+ field.id
3355
+ );
3356
+ }),
3357
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "composer__toolbar", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3358
+ "button",
3359
+ {
3360
+ type: "submit",
3361
+ className: "composer__send",
3362
+ disabled: disabled || !canSendForm,
3363
+ "aria-label": "Send details",
3364
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
3365
+ }
3366
+ ) })
3367
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "composer__field", children: [
3368
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3369
+ "textarea",
3370
+ {
3371
+ ref: inputRef,
3372
+ className: "composer__input",
3373
+ rows: 1,
3374
+ value,
3375
+ placeholder,
3376
+ disabled,
3377
+ spellCheck: false,
3378
+ "aria-label": "Message",
3379
+ onChange: (event) => setValue(event.target.value),
3380
+ onKeyDown: handleChatKeyDown
3381
+ }
3382
+ ),
3383
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3384
+ "button",
3385
+ {
3386
+ type: "submit",
3387
+ className: "composer__send",
3388
+ disabled: disabled || !value.trim(),
3389
+ "aria-label": "Send message",
3390
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
3391
+ }
3392
+ )
3393
+ ] })
3394
+ }
3395
+ );
1987
3396
  }
1988
3397
 
1989
3398
  // src/react/components/FollowUpChips/FollowUpChips.tsx
@@ -2031,9 +3440,15 @@ var import_react8 = require("react");
2031
3440
  // src/react/components/BookingCard/BookingCard.tsx
2032
3441
  var import_react7 = require("react");
2033
3442
  var import_jsx_runtime4 = require("react/jsx-runtime");
3443
+ var BOOKING_STEPS = [
3444
+ { id: "date", label: "Date" },
3445
+ { id: "time", label: "Time" },
3446
+ { id: "details", label: "Details" }
3447
+ ];
2034
3448
  function monthFromKey(key) {
2035
3449
  const [year, month] = key.split("-").map(Number);
2036
- if (!year || !month) return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
3450
+ if (!year || !month)
3451
+ return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
2037
3452
  return { year, month: month - 1 };
2038
3453
  }
2039
3454
  function dateKeyFromParts(year, month, day) {
@@ -2066,6 +3481,7 @@ function BookingCard({
2066
3481
  const [startTime, setStartTime] = (0, import_react7.useState)("");
2067
3482
  const [name, setName] = (0, import_react7.useState)("");
2068
3483
  const [email, setEmail] = (0, import_react7.useState)("");
3484
+ const stepIndex = BOOKING_STEPS.findIndex((item) => item.id === step);
2069
3485
  const slots = (0, import_react7.useMemo)(
2070
3486
  () => bookingSlotsForEventType(offer.slots, eventTypeUri),
2071
3487
  [eventTypeUri, offer.slots]
@@ -2086,14 +3502,18 @@ function BookingCard({
2086
3502
  setSelectedDate("");
2087
3503
  setStartTime("");
2088
3504
  setVisibleMonth(
2089
- firstAvailableBookingMonth(bookingSlotsForEventType(offer.slots, nextType))
3505
+ firstAvailableBookingMonth(
3506
+ bookingSlotsForEventType(offer.slots, nextType)
3507
+ )
2090
3508
  );
2091
3509
  }
2092
3510
  const daySlots = (0, import_react7.useMemo)(
2093
3511
  () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
2094
3512
  [selectedDate, slots]
2095
3513
  );
2096
- const selectedType = offer.eventTypes.find((item) => item.uri === eventTypeUri);
3514
+ const selectedType = offer.eventTypes.find(
3515
+ (item) => item.uri === eventTypeUri
3516
+ );
2097
3517
  const selectedSample = availableByDate.get(selectedDate) ?? startTime;
2098
3518
  const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
2099
3519
  const weekdays = (0, import_react7.useMemo)(() => weekdayLabels(), []);
@@ -2139,24 +3559,47 @@ function BookingCard({
2139
3559
  });
2140
3560
  }
2141
3561
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { className: "booking-card", "aria-label": "Book a meeting", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { className: "booking-card__form", onSubmit: handleSubmit, children: [
3562
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ol", { className: "booking-card__steps", "aria-label": "Booking steps", children: BOOKING_STEPS.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3563
+ "li",
3564
+ {
3565
+ className: [
3566
+ "booking-card__step-indicator",
3567
+ index === stepIndex ? "booking-card__step-indicator--active" : "",
3568
+ index < stepIndex ? "booking-card__step-indicator--complete" : ""
3569
+ ].filter(Boolean).join(" "),
3570
+ "aria-current": index === stepIndex ? "step" : void 0,
3571
+ children: [
3572
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", children: index + 1 }),
3573
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: item.label })
3574
+ ]
3575
+ },
3576
+ item.id
3577
+ )) }),
2142
3578
  step === "date" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2143
3579
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__title", children: selectedType?.name || "Pick a date" }),
2144
3580
  timeZone ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "booking-card__tz", children: [
2145
3581
  "Times in ",
2146
3582
  timeZone
2147
3583
  ] }) : null,
2148
- offer.eventTypes.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-type`, children: [
2149
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Meeting" }),
2150
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2151
- "select",
2152
- {
2153
- id: `${fieldId}-type`,
2154
- value: eventTypeUri,
2155
- onChange: (event) => selectEventType(event.target.value),
2156
- children: offer.eventTypes.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: item.uri, children: item.name }, item.uri))
2157
- }
2158
- )
2159
- ] }) : null,
3584
+ offer.eventTypes.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3585
+ "label",
3586
+ {
3587
+ className: "booking-card__field",
3588
+ htmlFor: `${fieldId}-type`,
3589
+ children: [
3590
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Meeting" }),
3591
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3592
+ "select",
3593
+ {
3594
+ id: `${fieldId}-type`,
3595
+ value: eventTypeUri,
3596
+ onChange: (event) => selectEventType(event.target.value),
3597
+ children: offer.eventTypes.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: item.uri, children: item.name }, item.uri))
3598
+ }
3599
+ )
3600
+ ]
3601
+ }
3602
+ ) : null,
2160
3603
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__month", children: [
2161
3604
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2162
3605
  "button",
@@ -2183,29 +3626,44 @@ function BookingCard({
2183
3626
  )
2184
3627
  ] }),
2185
3628
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "booking-card__weekdays", children: weekdays.map((label) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: label }, label)) }),
2186
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "booking-card__calendar", role: "grid", "aria-label": "Available dates", children: cells.map((cell, index) => {
2187
- if (!cell) {
2188
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "booking-card__day" }, `empty-${index}`);
3629
+ offer.slots.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__loading", role: "status", children: "Finding available times\u2026" }) : null,
3630
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3631
+ "div",
3632
+ {
3633
+ className: "booking-card__calendar",
3634
+ role: "grid",
3635
+ "aria-label": "Available dates",
3636
+ children: cells.map((cell, index) => {
3637
+ if (!cell) {
3638
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3639
+ "span",
3640
+ {
3641
+ className: "booking-card__day"
3642
+ },
3643
+ `empty-${index}`
3644
+ );
3645
+ }
3646
+ const available = availableByDate.has(cell.key);
3647
+ const selected = cell.key === selectedDate;
3648
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3649
+ "button",
3650
+ {
3651
+ type: "button",
3652
+ className: [
3653
+ "booking-card__day",
3654
+ available ? "booking-card__day--available" : "",
3655
+ selected ? "booking-card__day--selected" : ""
3656
+ ].filter(Boolean).join(" "),
3657
+ disabled: !available,
3658
+ "aria-pressed": selected,
3659
+ onClick: () => selectDate(cell.key),
3660
+ children: cell.day
3661
+ },
3662
+ cell.key
3663
+ );
3664
+ })
2189
3665
  }
2190
- const available = availableByDate.has(cell.key);
2191
- const selected = cell.key === selectedDate;
2192
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2193
- "button",
2194
- {
2195
- type: "button",
2196
- className: [
2197
- "booking-card__day",
2198
- available ? "booking-card__day--available" : "",
2199
- selected ? "booking-card__day--selected" : ""
2200
- ].filter(Boolean).join(" "),
2201
- disabled: !available,
2202
- "aria-pressed": selected,
2203
- onClick: () => selectDate(cell.key),
2204
- children: cell.day
2205
- },
2206
- cell.key
2207
- );
2208
- }) })
3666
+ )
2209
3667
  ] }, "date") : null,
2210
3668
  step === "time" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2211
3669
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step-bar", children: [
@@ -2257,35 +3715,57 @@ function BookingCard({
2257
3715
  ] })
2258
3716
  ] }),
2259
3717
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__identity", children: [
2260
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-name`, children: [
2261
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Name" }),
2262
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2263
- "input",
2264
- {
2265
- id: `${fieldId}-name`,
2266
- autoComplete: "name",
2267
- value: name,
2268
- onChange: (event) => setName(event.target.value),
2269
- required: true
2270
- }
2271
- )
2272
- ] }),
2273
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-email`, children: [
2274
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Email" }),
2275
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2276
- "input",
2277
- {
2278
- id: `${fieldId}-email`,
2279
- type: "email",
2280
- autoComplete: "email",
2281
- value: email,
2282
- onChange: (event) => setEmail(event.target.value),
2283
- required: true
2284
- }
2285
- )
2286
- ] })
3718
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3719
+ "label",
3720
+ {
3721
+ className: "booking-card__field",
3722
+ htmlFor: `${fieldId}-name`,
3723
+ children: [
3724
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Name" }),
3725
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3726
+ "input",
3727
+ {
3728
+ id: `${fieldId}-name`,
3729
+ autoComplete: "name",
3730
+ value: name,
3731
+ onChange: (event) => setName(event.target.value),
3732
+ required: true
3733
+ }
3734
+ )
3735
+ ]
3736
+ }
3737
+ ),
3738
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3739
+ "label",
3740
+ {
3741
+ className: "booking-card__field",
3742
+ htmlFor: `${fieldId}-email`,
3743
+ children: [
3744
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Email" }),
3745
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3746
+ "input",
3747
+ {
3748
+ id: `${fieldId}-email`,
3749
+ type: "email",
3750
+ autoComplete: "email",
3751
+ value: email,
3752
+ onChange: (event) => setEmail(event.target.value),
3753
+ required: true
3754
+ }
3755
+ )
3756
+ ]
3757
+ }
3758
+ )
2287
3759
  ] }),
2288
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", className: "booking-card__submit", children: "Book this time" })
3760
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3761
+ "button",
3762
+ {
3763
+ type: "submit",
3764
+ className: "booking-card__submit",
3765
+ disabled: !name.trim() || !email.trim(),
3766
+ children: "Book this time"
3767
+ }
3768
+ )
2289
3769
  ] }, "details") : null
2290
3770
  ] }) });
2291
3771
  }
@@ -2294,6 +3774,43 @@ function BookingCard({
2294
3774
  var import_streamdown = require("streamdown");
2295
3775
  var import_styles = require("streamdown/styles.css");
2296
3776
  var import_jsx_runtime5 = require("react/jsx-runtime");
3777
+ function normalizeDedupeText(text) {
3778
+ return text.trim().replace(/\s+/g, " ").toLowerCase();
3779
+ }
3780
+ function paragraphsAreNearDuplicates(first, second) {
3781
+ const left = normalizeDedupeText(first);
3782
+ const right = normalizeDedupeText(second);
3783
+ if (left.length < 40 || right.length < 40) return false;
3784
+ if (left === right) return true;
3785
+ const shorter = left.length <= right.length ? left : right;
3786
+ const longer = left.length <= right.length ? right : left;
3787
+ return longer.startsWith(
3788
+ shorter.slice(0, Math.floor(shorter.length * 0.85))
3789
+ );
3790
+ }
3791
+ function paragraphsShareOpening(first, second) {
3792
+ const opening = first.split("\n")[0]?.trim();
3793
+ if (!opening || opening.length < 20) return false;
3794
+ return second.trim().startsWith(opening);
3795
+ }
3796
+ function collapseRepeatedText(text) {
3797
+ const trimmed = text.trim();
3798
+ if (trimmed.length < 40) return trimmed;
3799
+ const paragraphs = trimmed.split(/\n{2,}/u).map((part) => part.trim()).filter(Boolean);
3800
+ if (paragraphs.length === 2 && (paragraphsAreNearDuplicates(paragraphs[0], paragraphs[1]) || paragraphsShareOpening(paragraphs[0], paragraphs[1]))) {
3801
+ return paragraphs[0];
3802
+ }
3803
+ if (paragraphs.length >= 2 && paragraphs.length % 2 === 0) {
3804
+ const mid2 = paragraphs.length / 2;
3805
+ const first = paragraphs.slice(0, mid2).join("\n\n");
3806
+ const second = paragraphs.slice(mid2).join("\n\n");
3807
+ if (first === second) return first;
3808
+ }
3809
+ const mid = Math.floor(trimmed.length / 2);
3810
+ const left = trimmed.slice(0, mid).trim();
3811
+ const right = trimmed.slice(mid).trim();
3812
+ return left.length >= 20 && left === right ? left : trimmed;
3813
+ }
2297
3814
  function MessageBubble({
2298
3815
  message,
2299
3816
  brandLogoUrl,
@@ -2310,24 +3827,41 @@ function MessageBubble({
2310
3827
  const offers = offer ? [offer] : extractedOffers;
2311
3828
  const visibleText = hideToolCardFences(message.text);
2312
3829
  const isStreaming = message.role === "agent" && Boolean(message.streaming);
2313
- const displayText = offers.length > 0 ? visibleText || "Pick a date and time that works for you." : visibleText || (isStreaming ? "" : message.text);
3830
+ const displayText = collapseRepeatedText(
3831
+ offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
3832
+ );
2314
3833
  if (message.role === "visitor") {
2315
3834
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "message-bubble__text", children: message.text }) });
2316
3835
  }
2317
- const agentText = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "message-bubble__text", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2318
- import_streamdown.Streamdown,
2319
- {
2320
- animated: true,
2321
- caret: "circle",
2322
- className: "message-bubble__markdown",
2323
- controls: false,
2324
- isAnimating: isStreaming,
2325
- linkSafety: { enabled: false },
2326
- mode: isStreaming ? "streaming" : "static",
2327
- skipHtml: true,
2328
- children: displayText
2329
- }
2330
- ) });
3836
+ const citations = message.citations ?? [];
3837
+ const agentText = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "message-bubble__text", children: [
3838
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3839
+ import_streamdown.Streamdown,
3840
+ {
3841
+ animated: isStreaming,
3842
+ caret: "circle",
3843
+ className: "message-bubble__markdown",
3844
+ controls: false,
3845
+ isAnimating: isStreaming,
3846
+ linkSafety: { enabled: false },
3847
+ mode: isStreaming ? "streaming" : "static",
3848
+ skipHtml: true,
3849
+ children: displayText
3850
+ }
3851
+ ),
3852
+ citations.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "message-bubble__sources", "aria-label": "Sources", children: citations.map((citation) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3853
+ "a",
3854
+ {
3855
+ href: citation.url,
3856
+ target: "_blank",
3857
+ rel: "noreferrer",
3858
+ children: [
3859
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "message-bubble__source-icon", "aria-hidden": "true", children: "\u25A6" }),
3860
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: citation.label })
3861
+ ]
3862
+ }
3863
+ ) }, citation.id)) }) : null
3864
+ ] });
2331
3865
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
2332
3866
  displayText ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "message-bubble__agent-row", children: [
2333
3867
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -2353,10 +3887,262 @@ function MessageBubble({
2353
3887
  ] });
2354
3888
  }
2355
3889
 
2356
- // src/react/components/AgentRail/AgentRail.tsx
3890
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3891
+ var import_react9 = require("react");
3892
+
3893
+ // src/react/components/ConfirmationCard/ConfirmationCard.tsx
2357
3894
  var import_jsx_runtime6 = require("react/jsx-runtime");
3895
+ function ConfirmationCard({
3896
+ disabled = false,
3897
+ request,
3898
+ onRespond
3899
+ }) {
3900
+ const options = request.options ?? [];
3901
+ const heading = request.kind === "tool-approval" ? "Confirm this action" : request.prompt;
3902
+ const prompt = request.kind === "tool-approval" ? request.prompt : void 0;
3903
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3904
+ "section",
3905
+ {
3906
+ className: "confirmation-card",
3907
+ "aria-labelledby": `confirmation-${request.requestId}`,
3908
+ children: [
3909
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "confirmation-card__heading", children: [
3910
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { id: `confirmation-${request.requestId}`, children: heading }),
3911
+ prompt ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: prompt }) : null
3912
+ ] }),
3913
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "confirmation-card__actions", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3914
+ "button",
3915
+ {
3916
+ type: "button",
3917
+ className: `confirmation-card__action confirmation-card__action--${option.style ?? "default"}`,
3918
+ disabled,
3919
+ onClick: () => onRespond?.({
3920
+ requestId: request.requestId,
3921
+ optionId: option.id
3922
+ }),
3923
+ children: option.label
3924
+ },
3925
+ option.id
3926
+ )) })
3927
+ ]
3928
+ }
3929
+ );
3930
+ }
3931
+
3932
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3933
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3934
+ function HumanInputCard({
3935
+ disabled = false,
3936
+ request,
3937
+ onRespond
3938
+ }) {
3939
+ const [text, setText] = (0, import_react9.useState)("");
3940
+ const options = request.options ?? [];
3941
+ const showText = request.display === "text" || request.allowFreeform && options.length === 0;
3942
+ if (options.length > 0) {
3943
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3944
+ ConfirmationCard,
3945
+ {
3946
+ disabled,
3947
+ request,
3948
+ onRespond
3949
+ }
3950
+ );
3951
+ }
3952
+ function submitText(event) {
3953
+ event.preventDefault();
3954
+ const value = text.trim();
3955
+ if (!value || disabled) return;
3956
+ onRespond?.({ requestId: request.requestId, text: value });
3957
+ }
3958
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3959
+ "section",
3960
+ {
3961
+ className: "human-input-card",
3962
+ "aria-labelledby": `human-input-${request.requestId}`,
3963
+ children: [
3964
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "human-input-card__heading", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { id: `human-input-${request.requestId}`, children: request.prompt }) }),
3965
+ showText ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("form", { onSubmit: submitText, children: [
3966
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { htmlFor: `human-input-text-${request.requestId}`, children: "Response" }),
3967
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
3968
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3969
+ "input",
3970
+ {
3971
+ id: `human-input-text-${request.requestId}`,
3972
+ value: text,
3973
+ disabled,
3974
+ onChange: (event) => setText(event.target.value)
3975
+ }
3976
+ ),
3977
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "submit", disabled: disabled || !text.trim(), children: "Send" })
3978
+ ] })
3979
+ ] }) : null,
3980
+ !showText && options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "human-input-card__unavailable", role: "status", children: "This request can\u2019t be answered here." }) : null
3981
+ ]
3982
+ }
3983
+ );
3984
+ }
3985
+
3986
+ // src/react/components/CollectionResultCard/CollectionResultCard.tsx
3987
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3988
+ function CollectionResultCard({
3989
+ result
3990
+ }) {
3991
+ const empty = result.items.length === 0;
3992
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3993
+ "section",
3994
+ {
3995
+ className: `collection-result-card tool-result-card tool-result-card--${result.status}`,
3996
+ "aria-label": result.title,
3997
+ children: [
3998
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "tool-result-card__heading", children: [
3999
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4000
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: result.title })
4001
+ ] }),
4002
+ empty ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "collection-result-card__empty", children: "No matching record for the email you shared." }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "collection-result-card__list", children: result.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { children: [
4003
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "collection-result-card__item-title", children: item.title }),
4004
+ item.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: item.description }) : null,
4005
+ item.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dl", { children: item.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
4006
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dt", { children: detail.label }),
4007
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dd", { children: detail.value })
4008
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
4009
+ item.href ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { href: item.href, target: "_blank", rel: "noreferrer", children: "Open record" }) : null
4010
+ ] }, item.title)) })
4011
+ ]
4012
+ }
4013
+ );
4014
+ }
4015
+
4016
+ // src/react/components/EntityResultCard/EntityResultCard.tsx
4017
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4018
+ function EntityResultCard({
4019
+ result
4020
+ }) {
4021
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4022
+ "section",
4023
+ {
4024
+ className: `entity-result-card tool-result-card tool-result-card--${result.status}`,
4025
+ "aria-label": result.title,
4026
+ children: [
4027
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "tool-result-card__heading", children: [
4028
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4029
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: result.title })
4030
+ ] }),
4031
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: result.description }) : null,
4032
+ result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
4033
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dt", { children: detail.label }),
4034
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dd", { children: detail.value })
4035
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
4036
+ result.links?.length ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4037
+ "a",
4038
+ {
4039
+ href: link.href,
4040
+ target: "_blank",
4041
+ rel: "noreferrer",
4042
+ children: link.label
4043
+ },
4044
+ link.href
4045
+ )) }) : null
4046
+ ]
4047
+ }
4048
+ );
4049
+ }
4050
+
4051
+ // src/react/components/SignatureResultCard/SignatureResultCard.tsx
4052
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4053
+ function SignatureResultCard({
4054
+ result
4055
+ }) {
4056
+ const primaryLink = result.links?.[0];
4057
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4058
+ "section",
4059
+ {
4060
+ className: `signature-result-card tool-result-card tool-result-card--${result.status}`,
4061
+ "aria-label": result.title,
4062
+ children: [
4063
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tool-result-card__heading", children: [
4064
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4065
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: result.title })
4066
+ ] }),
4067
+ result.statusLabel ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "signature-result-card__badge", children: result.statusLabel }) : null,
4068
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: result.description }) : null,
4069
+ primaryLink ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4070
+ "a",
4071
+ {
4072
+ className: "signature-result-card__cta",
4073
+ href: primaryLink.href,
4074
+ target: "_blank",
4075
+ rel: "noreferrer",
4076
+ children: primaryLink.label
4077
+ }
4078
+ ) : null
4079
+ ]
4080
+ }
4081
+ );
4082
+ }
4083
+
4084
+ // src/react/components/ToolResultCard/ToolResultCard.tsx
4085
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4086
+ function ToolResultCard({
4087
+ result
4088
+ }) {
4089
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4090
+ "section",
4091
+ {
4092
+ className: `tool-result-card tool-result-card--${result.status}`,
4093
+ "aria-label": result.title,
4094
+ children: [
4095
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "tool-result-card__heading", children: [
4096
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4097
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: result.title })
4098
+ ] }),
4099
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { children: result.description }) : null,
4100
+ result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
4101
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dt", { children: detail.label }),
4102
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dd", { children: detail.value })
4103
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
4104
+ result.links?.length ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4105
+ "a",
4106
+ {
4107
+ href: link.href,
4108
+ target: "_blank",
4109
+ rel: "noreferrer",
4110
+ children: link.label
4111
+ },
4112
+ link.href
4113
+ )) }) : null
4114
+ ]
4115
+ }
4116
+ );
4117
+ }
4118
+
4119
+ // src/react/components/VisitorToolResultView/VisitorToolResultView.tsx
4120
+ var import_jsx_runtime12 = require("react/jsx-runtime");
4121
+ function VisitorToolResultView({
4122
+ result
4123
+ }) {
4124
+ if (result.kind === "entity") {
4125
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EntityResultCard, { result });
4126
+ }
4127
+ if (result.kind === "collection") {
4128
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CollectionResultCard, { result });
4129
+ }
4130
+ if (result.kind === "signature") {
4131
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SignatureResultCard, { result });
4132
+ }
4133
+ if (result.kind === "summary") {
4134
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToolResultCard, { result });
4135
+ }
4136
+ return null;
4137
+ }
4138
+ function isRenderableVisitorToolResult(result) {
4139
+ return result.kind === "summary" || result.kind === "entity" || result.kind === "collection" || result.kind === "signature";
4140
+ }
4141
+
4142
+ // src/react/components/AgentRail/AgentRail.tsx
4143
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2358
4144
  function MinimizeIcon() {
2359
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4145
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2360
4146
  "path",
2361
4147
  {
2362
4148
  d: "M3.5 8h9",
@@ -2367,7 +4153,7 @@ function MinimizeIcon() {
2367
4153
  ) });
2368
4154
  }
2369
4155
  function CloseIcon() {
2370
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4156
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2371
4157
  "path",
2372
4158
  {
2373
4159
  d: "M4 4l8 8M12 4l-8 8",
@@ -2378,7 +4164,7 @@ function CloseIcon() {
2378
4164
  ) });
2379
4165
  }
2380
4166
  function NewChatIcon() {
2381
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4167
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2382
4168
  "path",
2383
4169
  {
2384
4170
  d: "M9.5 3.5h3v3M12.25 3.75 8 8M7 4H4.5A1.5 1.5 0 0 0 3 5.5v6A1.5 1.5 0 0 0 4.5 13h6a1.5 1.5 0 0 0 1.5-1.5V9",
@@ -2390,7 +4176,7 @@ function NewChatIcon() {
2390
4176
  ) });
2391
4177
  }
2392
4178
  function ExpandIcon() {
2393
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4179
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2394
4180
  "path",
2395
4181
  {
2396
4182
  d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
@@ -2402,7 +4188,7 @@ function ExpandIcon() {
2402
4188
  ) });
2403
4189
  }
2404
4190
  function RestoreIcon() {
2405
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4191
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2406
4192
  "path",
2407
4193
  {
2408
4194
  d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
@@ -2430,28 +4216,29 @@ function AgentRail({
2430
4216
  onRetry,
2431
4217
  onSubmit,
2432
4218
  onFollowUpSelect,
2433
- onBook
4219
+ onBook,
4220
+ onInputResponse
2434
4221
  }) {
2435
- const transcriptRef = (0, import_react9.useRef)(null);
4222
+ const transcriptRef = (0, import_react10.useRef)(null);
2436
4223
  const resolvedBrandLabel = brandLabel.trim();
2437
4224
  const resolvedBrandLogoUrl = brandLogoUrl?.trim();
2438
- const [failedLogoUrl, setFailedLogoUrl] = (0, import_react9.useState)(null);
4225
+ const [failedLogoUrl, setFailedLogoUrl] = (0, import_react10.useState)(null);
2439
4226
  const showBrandLogo = Boolean(resolvedBrandLogoUrl) && failedLogoUrl !== resolvedBrandLogoUrl;
2440
4227
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2441
4228
  const brandedTheme = { ...defaultAgentRailTheme, ...theme };
2442
4229
  const resolvedTheme = resolvedColorScheme === "dark" ? {
2443
4230
  ...brandedTheme,
2444
4231
  brand: theme?.brand ?? defaultDarkAgentRailTheme.brand,
2445
- brandDeep: defaultDarkAgentRailTheme.brandDeep,
2446
- brandSoft: `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
2447
- border: defaultDarkAgentRailTheme.border,
2448
- danger: defaultDarkAgentRailTheme.danger,
2449
- success: defaultDarkAgentRailTheme.success,
2450
- surface: defaultDarkAgentRailTheme.surface,
2451
- surfaceMuted: defaultDarkAgentRailTheme.surfaceMuted,
2452
- text: defaultDarkAgentRailTheme.text,
2453
- textMuted: defaultDarkAgentRailTheme.textMuted,
2454
- textSubtle: defaultDarkAgentRailTheme.textSubtle,
4232
+ brandDeep: theme?.brandDeep ?? defaultDarkAgentRailTheme.brandDeep,
4233
+ brandSoft: theme?.brandSoft ?? `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
4234
+ border: theme?.border ?? defaultDarkAgentRailTheme.border,
4235
+ danger: theme?.danger ?? defaultDarkAgentRailTheme.danger,
4236
+ success: theme?.success ?? defaultDarkAgentRailTheme.success,
4237
+ surface: theme?.surface ?? defaultDarkAgentRailTheme.surface,
4238
+ surfaceMuted: theme?.surfaceMuted ?? defaultDarkAgentRailTheme.surfaceMuted,
4239
+ text: theme?.text ?? defaultDarkAgentRailTheme.text,
4240
+ textMuted: theme?.textMuted ?? defaultDarkAgentRailTheme.textMuted,
4241
+ textSubtle: theme?.textSubtle ?? defaultDarkAgentRailTheme.textSubtle,
2455
4242
  visitorBubble: theme?.visitorBubble ?? theme?.brand ?? defaultDarkAgentRailTheme.visitorBubble
2456
4243
  } : brandedTheme;
2457
4244
  const railStyle = {
@@ -2474,8 +4261,15 @@ function AgentRail({
2474
4261
  "--as-font-display": resolvedTheme.fontDisplay,
2475
4262
  colorScheme: resolvedColorScheme
2476
4263
  };
2477
- const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming";
4264
+ const pendingInputRequests = (state.pendingInputs ?? []).filter(
4265
+ (request) => shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
4266
+ );
4267
+ const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming" || state.phase === "waiting-input" && pendingInputRequests.length > 0;
4268
+ const semanticSurfaceDisabled = isBusy && state.phase !== "waiting-input";
2478
4269
  const showActivity = state.toolSteps.length > 0;
4270
+ const visitorToolResults = (state.toolResults ?? []).filter(
4271
+ isRenderableVisitorToolResult
4272
+ );
2479
4273
  const hasVisitorMessages2 = state.messages.some(
2480
4274
  (message) => message.role === "visitor"
2481
4275
  );
@@ -2485,22 +4279,44 @@ function AgentRail({
2485
4279
  );
2486
4280
  const visibleMessages = hasVisitorMessages2 ? state.messages : [];
2487
4281
  const lastMessage = visibleMessages.at(-1);
2488
- const completedAnswer = showActivity && state.phase === "complete" && lastMessage?.role === "agent" ? lastMessage : null;
2489
- const transcriptMessages = completedAnswer ? visibleMessages.slice(0, -1) : visibleMessages;
2490
- const streamingMessage = state.phase === "streaming" && state.streamingText ? {
4282
+ let lastAgentIndex = -1;
4283
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4284
+ if (visibleMessages[index]?.role === "agent") {
4285
+ lastAgentIndex = index;
4286
+ break;
4287
+ }
4288
+ }
4289
+ let lastVisitorIndex = -1;
4290
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4291
+ if (visibleMessages[index]?.role === "visitor") {
4292
+ lastVisitorIndex = index;
4293
+ break;
4294
+ }
4295
+ }
4296
+ const lastIsAgent = lastMessage?.role === "agent";
4297
+ const streamingMessage = state.phase === "streaming" && state.streamingText && !lastIsAgent ? {
2491
4298
  createdAt: 0,
2492
4299
  id: "streaming-response",
2493
4300
  role: "agent",
2494
4301
  streaming: true,
2495
4302
  text: state.streamingText
2496
- } : state.pendingOffer ? {
4303
+ } : state.pendingOffer && !lastIsAgent ? {
2497
4304
  createdAt: 0,
2498
4305
  id: "pending-booking",
2499
4306
  role: "agent",
2500
4307
  streaming: false,
2501
4308
  text: "Pick a date and time that works for you."
2502
4309
  } : null;
2503
- (0, import_react9.useEffect)(() => {
4310
+ const bookingReadyText = state.streamingText || (lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "");
4311
+ const waitingForBooking = !state.pendingOffer && looksLikeBookingReady(bookingReadyText) && (state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming");
4312
+ const lastAgentText = lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "";
4313
+ const composerForm = resolveComposerForm({
4314
+ agentText: lastAgentText,
4315
+ cards: extractToolCards(lastAgentText),
4316
+ hasBookingOffer: Boolean(state.pendingOffer) || waitingForBooking,
4317
+ enabled: lastIsAgent && !isBusy
4318
+ });
4319
+ (0, import_react10.useEffect)(() => {
2504
4320
  const node = transcriptRef.current;
2505
4321
  if (!node) return;
2506
4322
  node.scrollTop = node.scrollHeight;
@@ -2511,11 +4327,13 @@ function AgentRail({
2511
4327
  state.followUps,
2512
4328
  state.journey
2513
4329
  ]);
2514
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4330
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2515
4331
  "aside",
2516
4332
  {
2517
- className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4333
+ className: `agent-rail not-typeset${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4334
+ "data-not-typeset": "",
2518
4335
  "data-color-scheme": resolvedColorScheme,
4336
+ spellCheck: false,
2519
4337
  style: railStyle,
2520
4338
  "aria-label": "Agent conversation",
2521
4339
  "aria-modal": mobileFullscreen || expanded ? true : void 0,
@@ -2523,28 +4341,28 @@ function AgentRail({
2523
4341
  role: mobileFullscreen || expanded ? "dialog" : void 0,
2524
4342
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
2525
4343
  children: [
2526
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("header", { className: "agent-rail__header", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__brand-row", children: [
2527
- onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4344
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("header", { className: "agent-rail__header", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "agent-rail__brand-row", children: [
4345
+ onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2528
4346
  "button",
2529
4347
  {
2530
4348
  type: "button",
2531
4349
  className: "agent-rail__collapse",
2532
4350
  "aria-label": "Collapse assist",
2533
4351
  onClick: onCollapse,
2534
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MinimizeIcon, {})
4352
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MinimizeIcon, {})
2535
4353
  }
2536
- ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4354
+ ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2537
4355
  "button",
2538
4356
  {
2539
4357
  type: "button",
2540
4358
  className: "agent-rail__close",
2541
4359
  "aria-label": "Close agent",
2542
4360
  onClick: onClose,
2543
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, {})
4361
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CloseIcon, {})
2544
4362
  }
2545
- ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
2546
- resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__identity", children: [
2547
- showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4363
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
4364
+ resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "agent-rail__identity", children: [
4365
+ showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2548
4366
  "img",
2549
4367
  {
2550
4368
  className: "agent-rail__brand-logo",
@@ -2555,10 +4373,10 @@ function AgentRail({
2555
4373
  }
2556
4374
  }
2557
4375
  ) }) : null,
2558
- resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
4376
+ resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
2559
4377
  ] }) : null,
2560
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__actions", children: [
2561
- onReset ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4378
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "agent-rail__actions", children: [
4379
+ onReset ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2562
4380
  "button",
2563
4381
  {
2564
4382
  type: "button",
@@ -2566,24 +4384,24 @@ function AgentRail({
2566
4384
  "aria-label": "Start a new conversation",
2567
4385
  disabled: !hasVisitorMessages2,
2568
4386
  onClick: onReset,
2569
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NewChatIcon, {})
4387
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(NewChatIcon, {})
2570
4388
  }
2571
4389
  ) : null,
2572
- onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4390
+ onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2573
4391
  "button",
2574
4392
  {
2575
4393
  type: "button",
2576
4394
  className: "agent-rail__expand",
2577
4395
  "aria-label": expanded ? "Exit full screen" : "Open full screen",
2578
4396
  onClick: onExpandToggle,
2579
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExpandIcon, {})
4397
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ExpandIcon, {})
2580
4398
  }
2581
4399
  ) : null
2582
4400
  ] })
2583
4401
  ] }) }),
2584
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__thread", children: [
2585
- !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
2586
- greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4402
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "agent-rail__thread", children: [
4403
+ !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
4404
+ greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2587
4405
  MessageBubble,
2588
4406
  {
2589
4407
  message: greeting,
@@ -2591,7 +4409,7 @@ function AgentRail({
2591
4409
  onBook
2592
4410
  }
2593
4411
  ) : null,
2594
- showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4412
+ showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2595
4413
  FollowUpChips,
2596
4414
  {
2597
4415
  suggestions: state.followUps,
@@ -2599,64 +4417,94 @@ function AgentRail({
2599
4417
  label: "Start here",
2600
4418
  onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
2601
4419
  }
2602
- ) }) : null
4420
+ ) }) : null,
4421
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4422
+ AgentActivityBubble,
4423
+ {
4424
+ brandLabel: resolvedBrandLabel,
4425
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4426
+ failed: state.phase === "error",
4427
+ steps: state.toolSteps
4428
+ }
4429
+ ) : null,
4430
+ visitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VisitorToolResultView, { result }, result.id)),
4431
+ pendingInputRequests.map((request) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4432
+ HumanInputCard,
4433
+ {
4434
+ request,
4435
+ onRespond: onInputResponse
4436
+ },
4437
+ request.requestId
4438
+ ))
2603
4439
  ] }) : null,
2604
- transcriptMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2605
- MessageBubble,
2606
- {
2607
- message,
2608
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2609
- onBook
2610
- },
2611
- message.id
2612
- )),
2613
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2614
- AgentActivityBubble,
2615
- {
2616
- brandLabel: resolvedBrandLabel,
2617
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2618
- failed: state.phase === "error",
2619
- steps: state.toolSteps
2620
- }
2621
- ) : null,
2622
- completedAnswer ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4440
+ visibleMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "agent-rail__turn-block", children: [
4441
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4442
+ MessageBubble,
4443
+ {
4444
+ message,
4445
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4446
+ offer: index === lastAgentIndex ? state.pendingOffer : void 0,
4447
+ onBook
4448
+ }
4449
+ ),
4450
+ index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
4451
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4452
+ AgentActivityBubble,
4453
+ {
4454
+ brandLabel: resolvedBrandLabel,
4455
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4456
+ failed: state.phase === "error",
4457
+ steps: state.toolSteps
4458
+ }
4459
+ ) : null,
4460
+ visitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VisitorToolResultView, { result }, result.id)),
4461
+ pendingInputRequests.map((request) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4462
+ HumanInputCard,
4463
+ {
4464
+ request,
4465
+ onRespond: onInputResponse
4466
+ },
4467
+ request.requestId
4468
+ ))
4469
+ ] }) : null
4470
+ ] }, message.id)),
4471
+ streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2623
4472
  MessageBubble,
2624
4473
  {
2625
- message: completedAnswer,
4474
+ message: streamingMessage,
2626
4475
  brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4476
+ offer: state.pendingOffer,
2627
4477
  onBook
2628
4478
  }
2629
4479
  ) : null,
2630
- streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2631
- MessageBubble,
4480
+ waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4481
+ BookingCard,
2632
4482
  {
2633
- message: streamingMessage,
2634
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2635
- offer: state.pendingOffer,
2636
- onBook
4483
+ offer: { type: "booking_offer", eventTypes: [], slots: [] }
2637
4484
  }
2638
4485
  ) : null,
2639
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
2640
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2641
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Something went wrong" }),
2642
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: state.error })
4486
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
4487
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
4488
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: "Something went wrong" }),
4489
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: state.error })
2643
4490
  ] }),
2644
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
4491
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
2645
4492
  ] }) : null
2646
4493
  ] }) }),
2647
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
2648
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4494
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
4495
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2649
4496
  Composer,
2650
4497
  {
2651
4498
  variant: expanded || mobileFullscreen ? "dock" : "default",
2652
4499
  disabled: isBusy,
4500
+ form: composerForm,
2653
4501
  placeholder: composerPlaceholder,
2654
4502
  onSubmit
2655
4503
  }
2656
4504
  ),
2657
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
2658
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "AI can make mistakes. Check important info." }),
2659
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: poweredByLabel })
4505
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { children: [
4506
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "AI can make mistakes. Check important info." }),
4507
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: poweredByLabel })
2660
4508
  ] }) })
2661
4509
  ] })
2662
4510
  ]
@@ -2665,9 +4513,9 @@ function AgentRail({
2665
4513
  }
2666
4514
 
2667
4515
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
2668
- var import_jsx_runtime7 = require("react/jsx-runtime");
4516
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2669
4517
  function SparklesIcon() {
2670
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4518
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2671
4519
  "svg",
2672
4520
  {
2673
4521
  className: "assist-edge-tab__sparkles",
@@ -2675,21 +4523,21 @@ function SparklesIcon() {
2675
4523
  fill: "none",
2676
4524
  "aria-hidden": "true",
2677
4525
  children: [
2678
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4526
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2679
4527
  "path",
2680
4528
  {
2681
4529
  d: "M8 1.2l.95 2.7 2.85.05-2.25 1.75.8 2.75L8 6.7 5.65 8.45l.8-2.75L4.2 3.95l2.85-.05L8 1.2z",
2682
4530
  fill: "currentColor"
2683
4531
  }
2684
4532
  ),
2685
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4533
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2686
4534
  "path",
2687
4535
  {
2688
4536
  d: "M14.2 6.4l.55 1.55 1.65.03-1.3 1 .46 1.58-1.36-1-1.36 1 .46-1.58-1.3-1 1.65-.03.55-1.55z",
2689
4537
  fill: "currentColor"
2690
4538
  }
2691
4539
  ),
2692
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4540
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2693
4541
  "path",
2694
4542
  {
2695
4543
  d: "M3.1 9.1l.4 1.15 1.22.02-.96.74.34 1.17-1-.74-1 .74.34-1.17-.96-.74 1.22-.02.4-1.15z",
@@ -2703,7 +4551,7 @@ function SparklesIcon() {
2703
4551
  function TabMarkIcon({ customIconUrl }) {
2704
4552
  const url = customIconUrl?.trim();
2705
4553
  if (url) {
2706
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4554
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2707
4555
  "img",
2708
4556
  {
2709
4557
  alt: "",
@@ -2713,10 +4561,10 @@ function TabMarkIcon({ customIconUrl }) {
2713
4561
  }
2714
4562
  );
2715
4563
  }
2716
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparklesIcon, {});
4564
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SparklesIcon, {});
2717
4565
  }
2718
4566
  function ChevronLeftIcon() {
2719
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4567
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2720
4568
  "path",
2721
4569
  {
2722
4570
  d: "M10 4L6 8l4 4",
@@ -2728,7 +4576,7 @@ function ChevronLeftIcon() {
2728
4576
  ) });
2729
4577
  }
2730
4578
  function ChevronDownIcon() {
2731
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4579
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2732
4580
  "path",
2733
4581
  {
2734
4582
  d: "M4 6l4 4 4-4",
@@ -2740,7 +4588,7 @@ function ChevronDownIcon() {
2740
4588
  ) });
2741
4589
  }
2742
4590
  function DragDots() {
2743
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", {}, index)) });
4591
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("i", {}, index)) });
2744
4592
  }
2745
4593
  var VARIANT_COPY = {
2746
4594
  outline: { label: "Ask anything", aria: "Ask anything" },
@@ -2769,6 +4617,7 @@ function AssistEdgeTab({
2769
4617
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2770
4618
  const copy = VARIANT_COPY[variant];
2771
4619
  const visibleLabel = label?.trim() || copy.label;
4620
+ const alignment = along < 50 ? "start" : along > 50 ? "end" : "center";
2772
4621
  const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
2773
4622
  const resolvedBrandColor = brandColor ?? (resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
2774
4623
  const resolvedBorderColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.border : borderColor;
@@ -2785,11 +4634,11 @@ function AssistEdgeTab({
2785
4634
  ...resolvedTextColor ? { "--as-text": resolvedTextColor } : {},
2786
4635
  colorScheme: resolvedColorScheme
2787
4636
  };
2788
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4637
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2789
4638
  "button",
2790
4639
  {
2791
4640
  type: "button",
2792
- className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
4641
+ className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side} assist-edge-tab--align-${alignment}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
2793
4642
  "data-color-scheme": resolvedColorScheme,
2794
4643
  style,
2795
4644
  "aria-label": `Open ${visibleLabel}`,
@@ -2797,15 +4646,15 @@ function AssistEdgeTab({
2797
4646
  tabIndex: visible ? 0 : -1,
2798
4647
  onClick: onOpen,
2799
4648
  children: [
2800
- mobile ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2801
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4649
+ mobile ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4650
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2802
4651
  "span",
2803
4652
  {
2804
4653
  className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
2805
4654
  "aria-hidden": "true",
2806
4655
  children: [
2807
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2808
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4656
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TabMarkIcon, { customIconUrl }),
4657
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2809
4658
  "img",
2810
4659
  {
2811
4660
  className: "assist-edge-tab__logo",
@@ -2819,11 +4668,11 @@ function AssistEdgeTab({
2819
4668
  ]
2820
4669
  }
2821
4670
  ),
2822
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel })
2823
- ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2824
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2825
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2826
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4671
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel })
4672
+ ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4673
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4674
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TabMarkIcon, { customIconUrl }),
4675
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2827
4676
  "img",
2828
4677
  {
2829
4678
  className: "assist-edge-tab__logo",
@@ -2835,18 +4684,18 @@ function AssistEdgeTab({
2835
4684
  }
2836
4685
  ) : null
2837
4686
  ] }),
2838
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2839
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {})
4687
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4688
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronDownIcon, {})
2840
4689
  ] }) : null,
2841
- variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2842
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {}),
2843
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2844
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DragDots, {})
4690
+ variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4691
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronLeftIcon, {}),
4692
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4693
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DragDots, {})
2845
4694
  ] }) : null,
2846
- variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2847
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2848
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2849
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4695
+ variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4696
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4697
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TabMarkIcon, { customIconUrl }),
4698
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2850
4699
  "img",
2851
4700
  {
2852
4701
  className: "assist-edge-tab__logo",
@@ -2858,8 +4707,8 @@ function AssistEdgeTab({
2858
4707
  }
2859
4708
  ) : null
2860
4709
  ] }),
2861
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2862
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {})
4710
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4711
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronLeftIcon, {})
2863
4712
  ] }) : null
2864
4713
  ]
2865
4714
  }
@@ -2867,7 +4716,7 @@ function AssistEdgeTab({
2867
4716
  }
2868
4717
 
2869
4718
  // src/react/components/AgentWidget/AgentWidget.tsx
2870
- var import_jsx_runtime8 = require("react/jsx-runtime");
4719
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2871
4720
  function AgentWidget({
2872
4721
  indexId,
2873
4722
  customerId,
@@ -2880,13 +4729,14 @@ function AgentWidget({
2880
4729
  pageShift = true,
2881
4730
  registerPanelController = false,
2882
4731
  colorScheme = "auto",
2883
- branding
4732
+ branding,
4733
+ toolResultRegistry
2884
4734
  }) {
2885
4735
  const isMobile = useIsMobile();
2886
4736
  const placement = normalizeAgentPlacement(placementInput);
2887
- const railSlotRef = (0, import_react10.useRef)(null);
2888
- const [railCollapsed, setRailCollapsed] = (0, import_react10.useState)(defaultCollapsed);
2889
- const [railExpanded, setRailExpanded] = (0, import_react10.useState)(false);
4737
+ const railSlotRef = (0, import_react11.useRef)(null);
4738
+ const [railCollapsed, setRailCollapsed] = (0, import_react11.useState)(defaultCollapsed);
4739
+ const [railExpanded, setRailExpanded] = (0, import_react11.useState)(false);
2890
4740
  const pageShiftActive = shouldApplyPageShift({
2891
4741
  pageShift,
2892
4742
  isMobile,
@@ -2897,14 +4747,15 @@ function AgentWidget({
2897
4747
  active: pageShiftActive,
2898
4748
  railSlotRef
2899
4749
  });
2900
- const { state, reset, retry, submit } = useAgentChat({
4750
+ const { state, reset, retry, respondToInput, respondToToolInput, submit } = useAgentChat({
2901
4751
  customerId,
2902
4752
  getUnpublishedPreviewGrant,
2903
4753
  indexId,
2904
4754
  previewBuildId,
2905
4755
  version,
2906
4756
  runtimeOrigin,
2907
- greeting: branding?.greeting
4757
+ greeting: branding?.greeting,
4758
+ toolResultRegistry
2908
4759
  });
2909
4760
  const agentName = branding?.agentName ?? "";
2910
4761
  const tabLabel = branding?.tabLabel ?? agentName;
@@ -2925,22 +4776,24 @@ function AgentWidget({
2925
4776
  } : {},
2926
4777
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
2927
4778
  };
2928
- (0, import_react10.useEffect)(() => {
4779
+ (0, import_react11.useEffect)(() => {
2929
4780
  if (!registerPanelController) return;
2930
4781
  registerAgentPanelController(customerId, {
2931
4782
  open: () => setRailCollapsed(false),
2932
4783
  close: () => {
2933
4784
  setRailCollapsed(true);
2934
4785
  setRailExpanded(false);
2935
- }
4786
+ },
4787
+ reset,
4788
+ submit
2936
4789
  });
2937
4790
  return () => unregisterAgentPanelController(customerId);
2938
- }, [customerId, registerPanelController]);
4791
+ }, [customerId, registerPanelController, reset, submit]);
2939
4792
  async function handleSubmit(message) {
2940
4793
  if (isMobile) setRailCollapsed(false);
2941
4794
  await submit(message);
2942
4795
  }
2943
- (0, import_react10.useEffect)(() => {
4796
+ (0, import_react11.useEffect)(() => {
2944
4797
  if (railCollapsed) return;
2945
4798
  const handleKeyDown = (event) => {
2946
4799
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -2974,19 +4827,19 @@ function AgentWidget({
2974
4827
  window.addEventListener("keydown", handleKeyDown);
2975
4828
  return () => window.removeEventListener("keydown", handleKeyDown);
2976
4829
  }, [isMobile, railCollapsed, railExpanded]);
2977
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "webless-agent-root", children: [
2978
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4830
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "webless-agent-root", children: [
4831
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2979
4832
  "div",
2980
4833
  {
2981
4834
  className: `webless-agent-root__shell${railCollapsed ? " webless-agent-root__shell--collapsed" : ""}${railExpanded ? " webless-agent-root__shell--expanded" : ""}`,
2982
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4835
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2983
4836
  "div",
2984
4837
  {
2985
4838
  ref: railSlotRef,
2986
4839
  className: "webless-agent-root__rail-slot",
2987
4840
  inert: railCollapsed || void 0,
2988
4841
  "aria-hidden": railCollapsed,
2989
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4842
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2990
4843
  AgentRail,
2991
4844
  {
2992
4845
  theme,
@@ -3004,6 +4857,8 @@ function AgentWidget({
3004
4857
  onSubmit: handleSubmit,
3005
4858
  onReset: reset,
3006
4859
  onRetry: () => void retry(),
4860
+ onInputResponse: (response) => void respondToInput(response),
4861
+ onToolInput: (surface, values) => void respondToToolInput(surface, values),
3007
4862
  onFollowUpSelect: (label) => void handleSubmit(label),
3008
4863
  onBook: (input) => void submit(input.displayText, { runtimeText: input.runtimeText })
3009
4864
  }
@@ -3012,7 +4867,7 @@ function AgentWidget({
3012
4867
  )
3013
4868
  }
3014
4869
  ),
3015
- railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4870
+ railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3016
4871
  AssistEdgeTab,
3017
4872
  {
3018
4873
  variant: placement.variant,
@@ -3042,12 +4897,14 @@ function AgentWidget({
3042
4897
  AgentWidget,
3043
4898
  AssistEdgeTab,
3044
4899
  DEFAULT_AGENT_PLACEMENT,
4900
+ builtInVisitorToolResultRegistry,
3045
4901
  createIdleSuggestions,
3046
4902
  defaultAgentRailTheme,
3047
4903
  defaultDarkAgentRailTheme,
3048
4904
  hasVisitorMessages,
3049
4905
  isAgentBusy,
3050
4906
  normalizeAgentPlacement,
4907
+ presentVisitorToolResult,
3051
4908
  useAgentChat
3052
4909
  });
3053
4910
  //# sourceMappingURL=react.cjs.map