@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/embed.cjs CHANGED
@@ -27,6 +27,8 @@ __export(embed_exports, {
27
27
  mountAgent: () => mountAgent,
28
28
  normalizeAgentPlacement: () => normalizeAgentPlacement,
29
29
  normalizeAgentTagManifest: () => normalizeAgentTagManifest,
30
+ resetAgentPanel: () => resetAgentPanel,
31
+ submitAgentPanel: () => submitAgentPanel,
30
32
  unmountAgent: () => unmountAgent
31
33
  });
32
34
  module.exports = __toCommonJS(embed_exports);
@@ -48,9 +50,21 @@ function openAgentPanel(customerId) {
48
50
  function closeAgentPanel(customerId) {
49
51
  controllers.get(customerId)?.close();
50
52
  }
53
+ function resetAgentPanel(customerId) {
54
+ controllers.get(customerId)?.reset();
55
+ }
56
+ function submitAgentPanel(customerId, message, options) {
57
+ const controller = controllers.get(customerId);
58
+ if (!controller) {
59
+ return Promise.reject(
60
+ new Error(`Agent panel "${customerId}" is not mounted.`)
61
+ );
62
+ }
63
+ return controller.submit(message, options);
64
+ }
51
65
 
52
66
  // src/react/components/AgentWidget/AgentWidget.tsx
53
- var import_react10 = require("react");
67
+ var import_react11 = require("react");
54
68
 
55
69
  // src/react/page-shift.ts
56
70
  var import_react = require("react");
@@ -141,6 +155,26 @@ var import_client2 = require("eve/client");
141
155
  // src/runtime/capability.ts
142
156
  var import_client = require("eve/client");
143
157
  var MAX_REFRESH_SKEW_MS = 3e4;
158
+ var LOCAL_LOOPBACK_ORIGINS = [
159
+ "http://127.0.0.1:3010",
160
+ "http://127.0.0.1:3001"
161
+ ];
162
+ function isLoopbackRuntimeOrigin(origin) {
163
+ try {
164
+ const host = new URL(origin).hostname;
165
+ return host === "127.0.0.1" || host === "localhost";
166
+ } catch {
167
+ return false;
168
+ }
169
+ }
170
+ function localBootstrapOrigins(origin) {
171
+ const normalized = origin.replace(/\/$/, "");
172
+ if (!isLoopbackRuntimeOrigin(normalized)) return [normalized];
173
+ return [
174
+ normalized,
175
+ ...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
176
+ ];
177
+ }
144
178
  function isRecord(value) {
145
179
  return typeof value === "object" && value !== null && !Array.isArray(value);
146
180
  }
@@ -187,20 +221,31 @@ function createAgentRuntimeCapability(options) {
187
221
  );
188
222
  }
189
223
  }
190
- const response = await fetchImplementation(
191
- `${options.runtimeOrigin}/webless/v1/bootstrap`,
192
- {
193
- body: JSON.stringify({
194
- clientSessionId: options.visitorSessionId,
195
- indexId: options.indexId,
196
- ...previewBuildId ? { previewBuildId } : {},
197
- ...previewGrant ? { previewGrant } : {},
198
- version: options.version
199
- }),
200
- headers: { "content-type": "application/json" },
201
- method: "POST"
224
+ const bootstrapBody = JSON.stringify({
225
+ clientSessionId: options.visitorSessionId,
226
+ indexId: options.indexId,
227
+ ...previewBuildId ? { previewBuildId } : {},
228
+ ...previewGrant ? { previewGrant } : {},
229
+ version: options.version
230
+ });
231
+ const postBootstrap = (origin) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
232
+ body: bootstrapBody,
233
+ headers: { "content-type": "application/json" },
234
+ method: "POST"
235
+ });
236
+ let response;
237
+ let lastError;
238
+ for (const origin of localBootstrapOrigins(options.runtimeOrigin)) {
239
+ try {
240
+ response = await postBootstrap(origin);
241
+ break;
242
+ } catch (error) {
243
+ lastError = error;
202
244
  }
203
- );
245
+ }
246
+ if (!response) {
247
+ throw lastError instanceof Error ? lastError : new Error("Agent Runtime is unavailable.");
248
+ }
204
249
  if (!response.ok) {
205
250
  throw new Error(await readBootstrapError(response));
206
251
  }
@@ -362,6 +407,226 @@ function clearPersistedAgentSession(visitorSessionId, options) {
362
407
  sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
363
408
  }
364
409
 
410
+ // src/runtime/tool-ui.ts
411
+ var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
412
+ function isRecord2(value) {
413
+ return value !== null && typeof value === "object" && !Array.isArray(value);
414
+ }
415
+ function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
416
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
417
+ return true;
418
+ }
419
+ if (typeof value === "number") return Number.isFinite(value);
420
+ if (typeof value !== "object") return false;
421
+ if (seen.has(value)) return false;
422
+ seen.add(value);
423
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
424
+ ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
425
+ );
426
+ seen.delete(value);
427
+ return valid;
428
+ }
429
+ function boundedString(value, max) {
430
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
431
+ return void 0;
432
+ }
433
+ return value.trim();
434
+ }
435
+ function numberValue(value) {
436
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
437
+ }
438
+ function integerValue(value) {
439
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
440
+ }
441
+ function isFieldKind(value) {
442
+ return typeof value === "string" && [
443
+ "text",
444
+ "textarea",
445
+ "email",
446
+ "number",
447
+ "select",
448
+ "multi-select",
449
+ "checkbox",
450
+ "confirmation",
451
+ "radio",
452
+ "date",
453
+ "time",
454
+ "date-time",
455
+ "calendar",
456
+ "range",
457
+ "json"
458
+ ].includes(value);
459
+ }
460
+ function parseField(value) {
461
+ if (!isRecord2(value)) return null;
462
+ if (!hasOnlyKeys(value, [
463
+ "description",
464
+ "kind",
465
+ "label",
466
+ "max",
467
+ "maxItems",
468
+ "maxLength",
469
+ "min",
470
+ "minLength",
471
+ "options",
472
+ "path",
473
+ "placeholder",
474
+ "required",
475
+ "step",
476
+ "defaultValue"
477
+ ])) {
478
+ return null;
479
+ }
480
+ if (!isFieldKind(value.kind)) return null;
481
+ const path = boundedString(value.path, 160);
482
+ const label = boundedString(value.label, 160);
483
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
484
+ const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
485
+ const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
486
+ const min = value.min === void 0 ? void 0 : numberValue(value.min);
487
+ const max = value.max === void 0 ? void 0 : numberValue(value.max);
488
+ const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
489
+ const step = value.step === void 0 ? void 0 : numberValue(value.step);
490
+ const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
491
+ const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
492
+ const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
493
+ if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
494
+ return null;
495
+ }
496
+ if (value.description !== void 0 && !description) return null;
497
+ if (value.placeholder !== void 0 && placeholder === void 0) return null;
498
+ if (value.required !== void 0 && required === void 0) return null;
499
+ if (value.min !== void 0 && min === void 0) return null;
500
+ if (value.max !== void 0 && max === void 0) return null;
501
+ if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
502
+ return null;
503
+ if (value.step !== void 0 && step === void 0) return null;
504
+ if (value.minLength !== void 0 && minLength === void 0) return null;
505
+ if (value.maxLength !== void 0 && maxLength === void 0) return null;
506
+ if (value.defaultValue !== void 0 && defaultValue === void 0)
507
+ return null;
508
+ if (value.options !== void 0) {
509
+ if (!Array.isArray(value.options) || value.options.length > 100)
510
+ return null;
511
+ for (const option of value.options) {
512
+ if (!isRecord2(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
513
+ return null;
514
+ }
515
+ }
516
+ }
517
+ return {
518
+ kind: value.kind,
519
+ path,
520
+ label,
521
+ ...description ? { description } : {},
522
+ ...placeholder !== void 0 ? { placeholder } : {},
523
+ ...required !== void 0 ? { required } : {},
524
+ ...defaultValue !== void 0 ? { defaultValue } : {},
525
+ ...value.options !== void 0 ? { options: value.options } : {},
526
+ ...min !== void 0 ? { min } : {},
527
+ ...max !== void 0 ? { max } : {},
528
+ ...maxItems !== void 0 ? { maxItems } : {},
529
+ ...step !== void 0 ? { step } : {},
530
+ ...minLength !== void 0 ? { minLength } : {},
531
+ ...maxLength !== void 0 ? { maxLength } : {}
532
+ };
533
+ }
534
+ function parseStep(value) {
535
+ if (!isRecord2(value)) return null;
536
+ if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
537
+ return null;
538
+ }
539
+ const id = boundedString(value.id, 80);
540
+ const label = boundedString(value.label, 160);
541
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
542
+ 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(
543
+ (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
544
+ )) {
545
+ return null;
546
+ }
547
+ return {
548
+ id,
549
+ label,
550
+ fieldPaths: value.fieldPaths,
551
+ ...description ? { description } : {}
552
+ };
553
+ }
554
+ function parseAction(value) {
555
+ if (!isRecord2(value)) return null;
556
+ if (!hasOnlyKeys(value, ["id", "label"])) return null;
557
+ const label = boundedString(value.label, 80);
558
+ if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
559
+ return null;
560
+ }
561
+ return {
562
+ id: value.id,
563
+ label
564
+ };
565
+ }
566
+ function parseAgentToolUiSurface(value) {
567
+ if (!isRecord2(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
568
+ return null;
569
+ if (!hasOnlyKeys(value, [
570
+ "actions",
571
+ "description",
572
+ "fields",
573
+ "id",
574
+ "operationId",
575
+ "requestId",
576
+ "schemaVersion",
577
+ "steps",
578
+ "submitLabel",
579
+ "title",
580
+ "toolSlug",
581
+ "values"
582
+ ])) {
583
+ return null;
584
+ }
585
+ const id = boundedString(value.id, 200);
586
+ const title = boundedString(value.title, 200);
587
+ const toolSlug = boundedString(value.toolSlug, 200);
588
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
589
+ const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
590
+ const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
591
+ const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
592
+ if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
593
+ return null;
594
+ }
595
+ const fields = value.fields.map(parseField);
596
+ if (fields.some((field) => field === null)) return null;
597
+ const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
598
+ if (steps?.some((step) => step === null)) return null;
599
+ const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
600
+ if (actions?.some((action) => action === null)) return null;
601
+ if (value.description !== void 0 && !description) return null;
602
+ if (value.operationId !== void 0 && !operationId) return null;
603
+ if (value.requestId !== void 0 && !requestId) return null;
604
+ if (value.submitLabel !== void 0 && !submitLabel) return null;
605
+ const values = value.values !== void 0 && isRecord2(value.values) ? value.values : void 0;
606
+ if (value.values !== void 0) {
607
+ if (!values || !Object.values(values).every((item) => isJsonValue(item)))
608
+ return null;
609
+ }
610
+ return {
611
+ schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
612
+ id,
613
+ title,
614
+ toolSlug,
615
+ fields,
616
+ ...actions ? { actions } : {},
617
+ ...description ? { description } : {},
618
+ ...operationId ? { operationId } : {},
619
+ ...requestId ? { requestId } : {},
620
+ ...submitLabel ? { submitLabel } : {},
621
+ ...steps ? { steps } : {},
622
+ ...values ? { values } : {}
623
+ };
624
+ }
625
+ function hasOnlyKeys(value, allowed) {
626
+ const allowedKeys = new Set(allowed);
627
+ return Object.keys(value).every((key) => allowedKeys.has(key));
628
+ }
629
+
365
630
  // src/runtime/client.ts
366
631
  function isTurnBoundary(event) {
367
632
  return event.type === "session.waiting" || event.type === "session.completed" || event.type === "session.failed";
@@ -374,12 +639,8 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
374
639
  if (event.type === "message.completed") {
375
640
  handlers.onComplete?.();
376
641
  }
377
- if (event.type === "action.result") {
378
- const result = event.data.result;
379
- if (result && typeof result === "object" && "output" in result) {
380
- handlers.onActionResult?.(result.output);
381
- }
382
- }
642
+ if (event.type === "action.result") emitActionResult(event, handlers);
643
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
383
644
  if (event.type !== "message.appended") return rendered;
384
645
  const { messageDelta, messageSoFar } = event.data;
385
646
  let delta = messageDelta;
@@ -390,9 +651,45 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
390
651
  } else if (messageDelta) {
391
652
  next += messageDelta;
392
653
  }
393
- if (delta) handlers.onDelta(delta);
654
+ if (delta) handlers.onDelta?.(delta);
394
655
  return next;
395
656
  }
657
+ function emitActionResult(event, handlers) {
658
+ const result = event.data.result;
659
+ if (result.kind === "tool-result") {
660
+ handlers.onToolResult?.({
661
+ callId: result.callId,
662
+ toolName: result.toolName,
663
+ status: event.data.status,
664
+ output: result.output,
665
+ ...event.data.error ? { error: event.data.error } : {}
666
+ });
667
+ }
668
+ if ("output" in result) handlers.onActionResult?.(result.output);
669
+ }
670
+ function emitInputRequests(event, handlers) {
671
+ handlers.onInputRequest?.(
672
+ event.data.requests.map((request) => {
673
+ const ui = parseAgentToolUiSurface(
674
+ request.ui
675
+ );
676
+ return {
677
+ requestId: request.requestId,
678
+ kind: request.kind,
679
+ prompt: request.prompt,
680
+ ...request.display ? { display: request.display } : {},
681
+ ...request.allowFreeform !== void 0 ? { allowFreeform: request.allowFreeform } : {},
682
+ ...request.options ? { options: request.options } : {},
683
+ ...ui ? { ui } : {},
684
+ action: {
685
+ callId: request.action.callId,
686
+ kind: "tool-call",
687
+ toolName: request.action.toolName
688
+ }
689
+ };
690
+ })
691
+ );
692
+ }
396
693
  function isResumeTurnMessage(received, candidate) {
397
694
  if (received === candidate) return true;
398
695
  return Boolean(candidate) && received.endsWith(`
@@ -668,9 +965,11 @@ var AgentSession = class {
668
965
  let streamIndex = session?.state.streamIndex ?? 0;
669
966
  let rendered = "";
670
967
  const workItems = /* @__PURE__ */ new Map();
968
+ let requestedInput = false;
671
969
  try {
672
970
  for await (const event of response) {
673
971
  if (signal.aborted) break;
972
+ if (event.type === "input.requested") requestedInput = true;
674
973
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
675
974
  streamIndex += 1;
676
975
  if (session) {
@@ -688,7 +987,7 @@ var AgentSession = class {
688
987
  this.persistSessionCursor(session);
689
988
  }
690
989
  }
691
- if (!rendered.trim() && !signal.aborted) {
990
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
692
991
  throw new Error("Empty response from runtime");
693
992
  }
694
993
  return rendered.trim();
@@ -708,6 +1007,9 @@ var AgentSession = class {
708
1007
  () => attached.snapshot({ signal })
709
1008
  );
710
1009
  const turnEvents = latestTurnEvents(snapshot.events);
1010
+ const hasInputRequest = turnEvents.some(
1011
+ (event) => event.type === "input.requested"
1012
+ );
711
1013
  const received = turnEvents[0];
712
1014
  const lastSent = persisted.lastMessage;
713
1015
  const inFlight = !turnEvents.some((event) => isTurnBoundary(event));
@@ -718,6 +1020,8 @@ var AgentSession = class {
718
1020
  const workItems = /* @__PURE__ */ new Map();
719
1021
  for (const event of turnEvents) {
720
1022
  applyWorkEvent(event, handlers, workItems);
1023
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
1024
+ if (event.type === "action.result") emitActionResult(event, handlers);
721
1025
  }
722
1026
  if (rendered.startsWith(initialText)) {
723
1027
  const missedText = rendered.slice(initialText.length);
@@ -747,7 +1051,9 @@ var AgentSession = class {
747
1051
  );
748
1052
  }
749
1053
  handlers.onComplete?.();
750
- if (!rendered.trim()) throw new Error("Empty response from runtime");
1054
+ if (!rendered.trim() && !hasInputRequest) {
1055
+ throw new Error("Empty response from runtime");
1056
+ }
751
1057
  return rendered.trim();
752
1058
  }
753
1059
  let streamIndex = snapshot.session.streamIndex;
@@ -766,7 +1072,55 @@ var AgentSession = class {
766
1072
  session = client.sessions.attach(session.state.sessionId, { streamIndex });
767
1073
  this.session = session;
768
1074
  this.persistSessionCursor(session);
769
- if (!rendered.trim() && !signal.aborted) {
1075
+ if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
1076
+ throw new Error("Empty response from runtime");
1077
+ }
1078
+ return rendered.trim();
1079
+ }
1080
+ async respondTurn(responses, signal, handlers) {
1081
+ const client = this.ensureClient();
1082
+ const session = this.session ?? this.attachPersistedSession(client);
1083
+ if (!session) {
1084
+ throw new Error("No active session is waiting for input.");
1085
+ }
1086
+ this.session = session;
1087
+ const inputResponses = responses.map(
1088
+ ({ requestId, optionId, text }) => ({
1089
+ requestId,
1090
+ ...optionId ? { optionId } : {},
1091
+ ...text ? { text } : {}
1092
+ })
1093
+ );
1094
+ const response = await withCapabilityRefresh(
1095
+ this.capability,
1096
+ () => session.respond(inputResponses, { signal })
1097
+ );
1098
+ this.activeResponse = response;
1099
+ let streamIndex = session.state.streamIndex;
1100
+ let rendered = "";
1101
+ let requestedInput = false;
1102
+ const workItems = /* @__PURE__ */ new Map();
1103
+ try {
1104
+ for await (const event of response) {
1105
+ if (signal.aborted) break;
1106
+ if (event.type === "input.requested") requestedInput = true;
1107
+ rendered = applyMessageEvent(event, rendered, handlers, workItems);
1108
+ streamIndex += 1;
1109
+ savePersistedAgentSession(
1110
+ this.visitorSessionId,
1111
+ session.state.sessionId,
1112
+ streamIndex,
1113
+ this.storeOptions
1114
+ );
1115
+ }
1116
+ } finally {
1117
+ this.activeResponse = void 0;
1118
+ this.session = client.sessions.attach(session.state.sessionId, {
1119
+ streamIndex
1120
+ });
1121
+ this.persistSessionCursor(this.session);
1122
+ }
1123
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
770
1124
  throw new Error("Empty response from runtime");
771
1125
  }
772
1126
  return rendered.trim();
@@ -826,6 +1180,11 @@ function createAgentClient(options) {
826
1180
  resumeOptions.handlers,
827
1181
  resumeOptions.initialText
828
1182
  ),
1183
+ respondTurn: (respondOptions) => session.respondTurn(
1184
+ respondOptions.responses,
1185
+ respondOptions.signal ?? new AbortController().signal,
1186
+ respondOptions.handlers
1187
+ ),
829
1188
  reset: () => session.reset(),
830
1189
  cancelActive: () => session.cancelActive(),
831
1190
  getActiveSessionId: () => session.getActiveSessionId()
@@ -875,7 +1234,176 @@ function formatAgentError(error) {
875
1234
  return TRANSIENT_AGENT_ERROR_MESSAGE;
876
1235
  }
877
1236
 
1237
+ // src/react/lib/composer-form.ts
1238
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1239
+ var MIN_FORM_FIELDS = 2;
1240
+ var MAX_FORM_FIELDS = 5;
1241
+ var FIELD_LIBRARY = [
1242
+ {
1243
+ id: "email",
1244
+ kind: "email",
1245
+ label: "Email",
1246
+ placeholder: "email@example.com",
1247
+ required: true,
1248
+ autocomplete: "email",
1249
+ patterns: [/\be-?mails?\b/i]
1250
+ },
1251
+ {
1252
+ id: "name",
1253
+ kind: "text",
1254
+ label: "Name",
1255
+ placeholder: "Your name",
1256
+ required: true,
1257
+ autocomplete: "name",
1258
+ patterns: [/\b((full|first|last)\s+)?names?\b/i],
1259
+ exclude: /\bcompany\s+names?\b/i
1260
+ },
1261
+ {
1262
+ id: "phone",
1263
+ kind: "tel",
1264
+ label: "Phone",
1265
+ placeholder: "+1 555 0100",
1266
+ required: true,
1267
+ autocomplete: "tel",
1268
+ patterns: [/\b(phone|mobile|cell)\b/i]
1269
+ },
1270
+ {
1271
+ id: "company",
1272
+ kind: "text",
1273
+ label: "Company",
1274
+ placeholder: "Company name",
1275
+ required: true,
1276
+ autocomplete: "organization",
1277
+ patterns: [/\bcompan(y|ies)\b/i]
1278
+ },
1279
+ {
1280
+ id: "message",
1281
+ kind: "textarea",
1282
+ label: "Message",
1283
+ placeholder: "Message\u2026",
1284
+ required: true,
1285
+ patterns: [/\b(your message|a message|the message|inquiry|enquiry)\b/i],
1286
+ exclude: /\bin one message\b/i
1287
+ }
1288
+ ];
1289
+ function asRecord(value) {
1290
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1291
+ }
1292
+ function asString(value) {
1293
+ return typeof value === "string" ? value.trim() : "";
1294
+ }
1295
+ function isFieldKind2(value) {
1296
+ return value === "text" || value === "email" || value === "tel" || value === "textarea";
1297
+ }
1298
+ function parseVisitorFormFields(value) {
1299
+ if (!Array.isArray(value)) return [];
1300
+ const fields = [];
1301
+ const seen = /* @__PURE__ */ new Set();
1302
+ for (const item of value) {
1303
+ const record = asRecord(item);
1304
+ const id = asString(record?.id).toLowerCase().replace(/[^a-z0-9_-]/g, "");
1305
+ const kind = asString(record?.kind);
1306
+ if (!record || !id || seen.has(id) || !isFieldKind2(kind)) continue;
1307
+ seen.add(id);
1308
+ const label = asString(record.label) || id;
1309
+ fields.push({
1310
+ id,
1311
+ kind,
1312
+ label,
1313
+ placeholder: asString(record.placeholder) || label,
1314
+ required: record.required !== false,
1315
+ ...asString(record.autocomplete) ? { autocomplete: asString(record.autocomplete) } : {}
1316
+ });
1317
+ if (fields.length >= MAX_FORM_FIELDS) break;
1318
+ }
1319
+ return fields;
1320
+ }
1321
+ function readComposerControlValue(event) {
1322
+ const node = event.target ?? event.currentTarget ?? null;
1323
+ if (node && typeof node === "object" && "value" in node && typeof node.value === "string") {
1324
+ return node.value;
1325
+ }
1326
+ return "";
1327
+ }
1328
+ function isValidComposerFieldValue(field, value) {
1329
+ const trimmed = value.trim();
1330
+ if (!trimmed) return !field.required;
1331
+ if (field.kind === "email") return EMAIL_PATTERN.test(trimmed);
1332
+ if (field.kind === "tel") return trimmed.replace(/\D/g, "").length >= 7;
1333
+ return trimmed.length > 0;
1334
+ }
1335
+ function isComposerFormComplete(form, values) {
1336
+ return form.fields.every(
1337
+ (field) => isValidComposerFieldValue(field, values[field.id] ?? "")
1338
+ );
1339
+ }
1340
+ function formatComposerFormMessage(form, values) {
1341
+ return form.fields.map((field) => {
1342
+ const value = (values[field.id] ?? "").trim();
1343
+ return value ? `${field.label}: ${value}` : "";
1344
+ }).filter(Boolean).join("\n");
1345
+ }
1346
+ function looksLikeFieldCollection(text) {
1347
+ return /\b(please|need|send|share|enter|provide|add|collect|what|which|use)\b/i.test(
1348
+ text
1349
+ ) || /:\s*$/m.test(text) || /^[-*•]\s+/m.test(text);
1350
+ }
1351
+ function looksLikeBookingCopy(text) {
1352
+ return /book(ing)? (card|a demo|this time)|pick a (date|time)|available (times|slots)|calendly/i.test(
1353
+ text
1354
+ );
1355
+ }
1356
+ function inferComposerForm(text) {
1357
+ const cleaned = text.trim();
1358
+ if (!cleaned || looksLikeBookingCopy(cleaned) || !looksLikeFieldCollection(cleaned)) {
1359
+ return null;
1360
+ }
1361
+ const fields = FIELD_LIBRARY.flatMap((field) => {
1362
+ if (field.exclude?.test(cleaned)) {
1363
+ const leftover = cleaned.replace(field.exclude, " ");
1364
+ if (!field.patterns.some((pattern) => pattern.test(leftover))) return [];
1365
+ } else if (!field.patterns.some((pattern) => pattern.test(cleaned))) {
1366
+ return [];
1367
+ }
1368
+ const { patterns: _patterns, exclude: _exclude, ...next } = field;
1369
+ return [next];
1370
+ }).slice(0, MAX_FORM_FIELDS);
1371
+ if (fields.length < MIN_FORM_FIELDS) return null;
1372
+ return {
1373
+ id: `inferred:${fields.map((field) => field.id).join("+")}`,
1374
+ fields
1375
+ };
1376
+ }
1377
+ function resolveComposerForm(input) {
1378
+ if (input.enabled === false || input.hasBookingOffer) return null;
1379
+ const text = input.agentText.trim();
1380
+ if (!text) return null;
1381
+ const card = input.cards?.find((item) => item.type === "visitor_form");
1382
+ if (card?.fields && card.fields.length >= MIN_FORM_FIELDS) {
1383
+ return {
1384
+ id: `card:${card.fields.map((field) => field.id).join("+")}`,
1385
+ fields: card.fields.slice(0, MAX_FORM_FIELDS)
1386
+ };
1387
+ }
1388
+ return inferComposerForm(text);
1389
+ }
1390
+
878
1391
  // src/react/lib/tool-card.ts
1392
+ function preferBookingOffer(current, next) {
1393
+ if (!current) return next;
1394
+ if (next.slots.length !== current.slots.length) {
1395
+ return next.slots.length > current.slots.length ? next : current;
1396
+ }
1397
+ if (next.eventTypes.length !== current.eventTypes.length) {
1398
+ return next.eventTypes.length > current.eventTypes.length ? next : current;
1399
+ }
1400
+ return next;
1401
+ }
1402
+ function looksLikeBookingReady(text) {
1403
+ return /demo option|options are ready|pick a (date|time)|available (times|slots)|book(ing)? (card|the demo)|schedule/i.test(
1404
+ text
1405
+ );
1406
+ }
879
1407
  function bookingOfferIdentityKey(offer) {
880
1408
  const eventTypes = offer.eventTypes.map(
881
1409
  (item) => `${item.uri}:${item.duration ?? ""}:${item.locationKind ?? ""}:${item.location ?? ""}`
@@ -884,10 +1412,10 @@ function bookingOfferIdentityKey(offer) {
884
1412
  return `${eventTypes}::${slots}` || "offer";
885
1413
  }
886
1414
  var FENCE_PATTERN = /```(?:webless-tool-card|json)\s*([\s\S]*?)```/gi;
887
- function asRecord(value) {
1415
+ function asRecord2(value) {
888
1416
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
889
1417
  }
890
- function asString(value) {
1418
+ function asString2(value) {
891
1419
  return typeof value === "string" ? value.trim() : "";
892
1420
  }
893
1421
  function isEventUri(value) {
@@ -897,24 +1425,28 @@ function isEventTypeUri(value) {
897
1425
  return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
898
1426
  }
899
1427
  function parseToolCard(value) {
900
- const record = asRecord(value);
1428
+ const record = asRecord2(value);
901
1429
  if (!record) return null;
902
- if (record.booking_offer && asString(record.type) !== "booking_offer") {
1430
+ if (record.booking_offer && asString2(record.type) !== "booking_offer") {
903
1431
  const nested = parseToolCard(record.booking_offer);
904
1432
  if (nested) return nested;
905
1433
  }
906
- const type = asString(record.type);
1434
+ if (record.visitor_booking && asString2(record.type) !== "booking_confirmed") {
1435
+ const nested = parseToolCard(record.visitor_booking);
1436
+ if (nested) return nested;
1437
+ }
1438
+ const type = asString2(record.type);
907
1439
  if (type === "booking_offer") {
908
1440
  const eventTypes = Array.isArray(record.eventTypes) ? record.eventTypes.flatMap((item) => {
909
- const entry = asRecord(item);
910
- const uri = asString(entry?.uri);
911
- if (!entry || !isEventTypeUri(uri)) return [];
1441
+ const entry = asRecord2(item);
1442
+ const uri = asString2(entry?.uri);
1443
+ if (!entry || !uri) return [];
912
1444
  const duration = entry.duration;
913
- const locationKind = asString(entry.locationKind);
914
- const location = asString(entry.location);
1445
+ const locationKind = asString2(entry.locationKind);
1446
+ const location = asString2(entry.location);
915
1447
  return [
916
1448
  {
917
- name: asString(entry.name) || "Meeting",
1449
+ name: asString2(entry.name) || "Meeting",
918
1450
  uri,
919
1451
  ...typeof duration === "number" ? { duration } : {},
920
1452
  ...locationKind ? { locationKind } : {},
@@ -923,10 +1455,10 @@ function parseToolCard(value) {
923
1455
  ];
924
1456
  }) : [];
925
1457
  const slots = Array.isArray(record.slots) ? record.slots.flatMap((item) => {
926
- const entry = asRecord(item);
927
- const startTime = asString(entry?.startTime);
1458
+ const entry = asRecord2(item);
1459
+ const startTime = asString2(entry?.startTime);
928
1460
  if (!entry || !startTime) return [];
929
- const eventTypeUri = asString(entry.eventTypeUri);
1461
+ const eventTypeUri = asString2(entry.eventTypeUri);
930
1462
  return [
931
1463
  {
932
1464
  startTime,
@@ -934,15 +1466,15 @@ function parseToolCard(value) {
934
1466
  }
935
1467
  ];
936
1468
  }) : [];
937
- if (slots.length === 0) return null;
1469
+ if (eventTypes.length === 0 && slots.length === 0) return null;
938
1470
  return { type: "booking_offer", eventTypes, slots };
939
1471
  }
940
1472
  if (type === "booking_confirmed") {
941
- const eventUri = asString(record.eventUri);
1473
+ const eventUri = asString2(record.eventUri);
942
1474
  if (!isEventUri(eventUri)) return null;
943
- const inviteeUri = asString(record.inviteeUri);
944
- const inviteeEmail = asString(record.inviteeEmail);
945
- const startTime = asString(record.startTime);
1475
+ const inviteeUri = asString2(record.inviteeUri);
1476
+ const inviteeEmail = asString2(record.inviteeEmail);
1477
+ const startTime = asString2(record.startTime);
946
1478
  return {
947
1479
  type: "booking_confirmed",
948
1480
  eventUri,
@@ -952,10 +1484,15 @@ function parseToolCard(value) {
952
1484
  };
953
1485
  }
954
1486
  if (type === "booking_canceled") {
955
- const eventUri = asString(record.eventUri);
1487
+ const eventUri = asString2(record.eventUri);
956
1488
  if (!isEventUri(eventUri)) return null;
957
1489
  return { type: "booking_canceled", eventUri };
958
1490
  }
1491
+ if (type === "visitor_form") {
1492
+ const fields = parseVisitorFormFields(record.fields);
1493
+ if (fields.length < 2) return null;
1494
+ return { type: "visitor_form", fields };
1495
+ }
959
1496
  return null;
960
1497
  }
961
1498
  function formatBookingOfferFence(offer) {
@@ -969,11 +1506,10 @@ function formatBookingOfferFence(offer) {
969
1506
  "```"
970
1507
  ].join("\n");
971
1508
  }
972
- function bookingOfferFromActionOutput(output) {
973
- const record = asRecord(output);
974
- const data = asRecord(record?.data) ?? record;
975
- const card = parseToolCard(data);
976
- return card?.type === "booking_offer" ? card : null;
1509
+ function bookingCardFromActionOutput(output) {
1510
+ const record = asRecord2(output);
1511
+ const data = asRecord2(record?.data) ?? record;
1512
+ return parseToolCard(data);
977
1513
  }
978
1514
  function ensureBookingOfferText(text, offer) {
979
1515
  if (!offer) return text;
@@ -988,6 +1524,21 @@ ${formatBookingOfferFence(offer)}`;
988
1524
  function hideToolCardFences(text) {
989
1525
  return text.replace(/```(?:webless-tool-card|json)\s*[\s\S]*?```/gi, "").replace(/```(?:webless-tool-card|json)[\s\S]*$/i, "").replace(/\n{3,}/g, "\n\n").trim();
990
1526
  }
1527
+ var BOOKING_CARD_FALLBACK = "Pick a date and time that works for you.";
1528
+ function looksLikeBookingAvailabilityDump(text) {
1529
+ const cleaned = text.trim();
1530
+ if (!cleaned) return false;
1531
+ const isoCount = (cleaned.match(/\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/g) ?? []).length;
1532
+ const utcCount = (cleaned.match(/\bUTC\b/g) ?? []).length;
1533
+ return isoCount >= 2 || utcCount >= 2 || /api\.calendly\.com|calendly\.com\//i.test(cleaned) || /webless-tool-card/i.test(cleaned);
1534
+ }
1535
+ function sanitizeBookingOfferCopy(text) {
1536
+ const cleaned = hideToolCardFences(text);
1537
+ if (!cleaned || looksLikeBookingAvailabilityDump(cleaned)) {
1538
+ return BOOKING_CARD_FALLBACK;
1539
+ }
1540
+ return cleaned;
1541
+ }
991
1542
  function visitorTimeZone() {
992
1543
  try {
993
1544
  return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
@@ -1089,6 +1640,7 @@ function formatSlotLabel(startTime) {
1089
1640
  function formatBookingRequest(input) {
1090
1641
  return [
1091
1642
  "Book this meeting now with CALENDLY_POST_INVITEE.",
1643
+ "Execute CALENDLY_POST_INVITEE in this turn with the fields below.",
1092
1644
  "Do not open a Calendly URL and do not list other scheduled events.",
1093
1645
  "Do not invent a location kind. Use only the location fields below.",
1094
1646
  `event_type: ${input.eventTypeUri}`,
@@ -1116,7 +1668,7 @@ function visitorBookingPrefix(booking) {
1116
1668
  }
1117
1669
 
1118
1670
  // src/react/persisted-conversation.ts
1119
- var CONVERSATION_VERSION = 2;
1671
+ var CONVERSATION_VERSION = 3;
1120
1672
  function conversationKey(storageKeyPrefix, visitorSessionId) {
1121
1673
  return `${storageKeyPrefix}:conversation:${visitorSessionId}`;
1122
1674
  }
@@ -1156,30 +1708,171 @@ function parseToolStep(value) {
1156
1708
  ...typeof record.detail === "string" && record.detail ? { detail: record.detail } : {}
1157
1709
  };
1158
1710
  }
1711
+ function parseInputOption(value) {
1712
+ if (typeof value !== "object" || value === null) return null;
1713
+ const record = value;
1714
+ if (typeof record.id !== "string" || typeof record.label !== "string") {
1715
+ return null;
1716
+ }
1717
+ return {
1718
+ id: record.id,
1719
+ label: record.label,
1720
+ ...typeof record.description === "string" ? { description: record.description } : {},
1721
+ ...record.style === "default" || record.style === "primary" || record.style === "danger" ? { style: record.style } : {}
1722
+ };
1723
+ }
1724
+ function parseInputRequest(value) {
1725
+ if (typeof value !== "object" || value === null) return null;
1726
+ const record = value;
1727
+ if (typeof record.requestId !== "string" || record.kind !== "question" && record.kind !== "session-limit" && record.kind !== "tool-approval" || typeof record.prompt !== "string") {
1728
+ return null;
1729
+ }
1730
+ const action = record.action;
1731
+ if (typeof action !== "object" || action === null) return null;
1732
+ const actionRecord = action;
1733
+ if (typeof actionRecord.callId !== "string" || actionRecord.kind !== "tool-call" || typeof actionRecord.toolName !== "string") {
1734
+ return null;
1735
+ }
1736
+ const options = Array.isArray(record.options) ? record.options.map(parseInputOption) : void 0;
1737
+ if (Array.isArray(record.options) && options?.some((option) => option === null)) {
1738
+ return null;
1739
+ }
1740
+ const ui = record.ui ? parseAgentToolUiSurface(record.ui) : void 0;
1741
+ if (record.ui && !ui) return null;
1742
+ return {
1743
+ requestId: record.requestId,
1744
+ kind: record.kind,
1745
+ prompt: record.prompt,
1746
+ action: {
1747
+ callId: actionRecord.callId,
1748
+ kind: "tool-call",
1749
+ toolName: actionRecord.toolName
1750
+ },
1751
+ ...record.display === "confirmation" || record.display === "select" || record.display === "text" ? { display: record.display } : {},
1752
+ ...record.allowFreeform === true ? { allowFreeform: true } : {},
1753
+ ...options && options.length > 0 ? {
1754
+ options: options.filter(
1755
+ (option) => option !== null
1756
+ )
1757
+ } : {},
1758
+ ...ui ? { ui } : {}
1759
+ };
1760
+ }
1761
+ function parseToolResult(value) {
1762
+ if (typeof value !== "object" || value === null) return null;
1763
+ const record = value;
1764
+ if (typeof record.id !== "string" || typeof record.toolName !== "string" || record.status !== "completed" && record.status !== "failed" && record.status !== "rejected") {
1765
+ return null;
1766
+ }
1767
+ if (record.kind === "input") {
1768
+ const surface = parseAgentToolUiSurface(
1769
+ record.surface
1770
+ );
1771
+ return surface ? {
1772
+ id: record.id,
1773
+ toolName: record.toolName,
1774
+ status: record.status,
1775
+ kind: "input",
1776
+ surface
1777
+ } : null;
1778
+ }
1779
+ if (record.kind === "entity" && typeof record.title === "string") {
1780
+ return {
1781
+ id: record.id,
1782
+ toolName: record.toolName,
1783
+ status: record.status,
1784
+ kind: "entity",
1785
+ title: record.title,
1786
+ ...typeof record.description === "string" ? { description: record.description } : {}
1787
+ };
1788
+ }
1789
+ if (record.kind === "collection" && typeof record.title === "string" && Array.isArray(record.items)) {
1790
+ return {
1791
+ id: record.id,
1792
+ toolName: record.toolName,
1793
+ status: record.status,
1794
+ kind: "collection",
1795
+ title: record.title,
1796
+ items: record.items.flatMap((item) => {
1797
+ if (typeof item !== "object" || item === null) return [];
1798
+ const entry = item;
1799
+ if (typeof entry.title !== "string") return [];
1800
+ return [
1801
+ {
1802
+ title: entry.title,
1803
+ ...typeof entry.description === "string" ? { description: entry.description } : {},
1804
+ ...typeof entry.href === "string" ? { href: entry.href } : {}
1805
+ }
1806
+ ];
1807
+ })
1808
+ };
1809
+ }
1810
+ if (record.kind === "signature" && typeof record.title === "string") {
1811
+ return {
1812
+ id: record.id,
1813
+ toolName: record.toolName,
1814
+ status: record.status,
1815
+ kind: "signature",
1816
+ title: record.title,
1817
+ ...typeof record.description === "string" ? { description: record.description } : {},
1818
+ ...typeof record.statusLabel === "string" ? { statusLabel: record.statusLabel } : {}
1819
+ };
1820
+ }
1821
+ if (record.kind !== "summary" || typeof record.title !== "string")
1822
+ return null;
1823
+ return {
1824
+ id: record.id,
1825
+ toolName: record.toolName,
1826
+ status: record.status,
1827
+ kind: "summary",
1828
+ title: record.title,
1829
+ ...typeof record.description === "string" ? { description: record.description } : {}
1830
+ };
1831
+ }
1159
1832
  function visitorTurnText(message) {
1160
1833
  return message.role === "visitor" && message.runtimeText ? message.runtimeText : message.text;
1161
1834
  }
1162
1835
  function loadPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
1163
1836
  if (typeof sessionStorage === "undefined") return null;
1164
- const raw = sessionStorage.getItem(conversationKey(storageKeyPrefix, visitorSessionId));
1837
+ const raw = sessionStorage.getItem(
1838
+ conversationKey(storageKeyPrefix, visitorSessionId)
1839
+ );
1165
1840
  if (!raw) return null;
1166
1841
  try {
1167
1842
  const value = JSON.parse(raw);
1168
1843
  if (typeof value !== "object" || value === null) return null;
1169
1844
  const record = value;
1170
- if (record.version !== 1 && record.version !== CONVERSATION_VERSION || !Array.isArray(record.messages) || typeof record.pending !== "boolean" || typeof record.streamingText !== "string" || record.version === CONVERSATION_VERSION && !Array.isArray(record.toolSteps)) {
1845
+ 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)) {
1171
1846
  return null;
1172
1847
  }
1173
1848
  const messages = record.messages.map(parseMessage);
1174
1849
  if (messages.some((message) => message === null)) return null;
1175
- const storedToolSteps = record.version === CONVERSATION_VERSION && Array.isArray(record.toolSteps) ? record.toolSteps : [];
1850
+ const storedToolSteps = (record.version === 2 || record.version === CONVERSATION_VERSION) && Array.isArray(record.toolSteps) ? record.toolSteps : [];
1176
1851
  const toolSteps = storedToolSteps.map(parseToolStep);
1177
1852
  if (toolSteps.some((step) => step === null)) return null;
1853
+ const storedToolResults = record.version === CONVERSATION_VERSION && Array.isArray(record.toolResults) ? record.toolResults : [];
1854
+ const toolResults = storedToolResults.map(parseToolResult);
1855
+ if (toolResults.some((result) => result === null)) return null;
1856
+ const storedPendingInputs = Array.isArray(record.pendingInputs) ? record.pendingInputs : [];
1857
+ const pendingInputs = storedPendingInputs.map(parseInputRequest);
1858
+ if (pendingInputs.some((request) => request === null)) return null;
1178
1859
  return {
1179
- messages: messages.filter((message) => message !== null),
1860
+ messages: messages.filter(
1861
+ (message) => message !== null
1862
+ ),
1180
1863
  pending: record.pending,
1181
1864
  streamingText: record.streamingText,
1182
- toolSteps: toolSteps.filter((step) => step !== null)
1865
+ toolSteps: toolSteps.filter((step) => step !== null),
1866
+ ...Array.isArray(record.toolResults) ? {
1867
+ toolResults: toolResults.filter(
1868
+ (result) => result !== null
1869
+ )
1870
+ } : {},
1871
+ ...pendingInputs.length > 0 ? {
1872
+ pendingInputs: pendingInputs.filter(
1873
+ (request) => request !== null
1874
+ )
1875
+ } : {}
1183
1876
  };
1184
1877
  } catch {
1185
1878
  return null;
@@ -1194,7 +1887,9 @@ function savePersistedAgentConversation(storageKeyPrefix, visitorSessionId, conv
1194
1887
  }
1195
1888
  function clearPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
1196
1889
  if (typeof sessionStorage === "undefined") return;
1197
- sessionStorage.removeItem(conversationKey(storageKeyPrefix, visitorSessionId));
1890
+ sessionStorage.removeItem(
1891
+ conversationKey(storageKeyPrefix, visitorSessionId)
1892
+ );
1198
1893
  clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId);
1199
1894
  }
1200
1895
  function pendingBookingKey(storageKeyPrefix, visitorSessionId) {
@@ -1230,45 +1925,457 @@ function savePendingWidgetBooking(storageKeyPrefix, visitorSessionId, booking) {
1230
1925
  }
1231
1926
  function clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
1232
1927
  if (typeof sessionStorage === "undefined") return;
1233
- sessionStorage.removeItem(pendingBookingKey(storageKeyPrefix, visitorSessionId));
1928
+ sessionStorage.removeItem(
1929
+ pendingBookingKey(storageKeyPrefix, visitorSessionId)
1930
+ );
1234
1931
  }
1235
1932
 
1236
- // src/react/hooks/useAgentChat.ts
1237
- var DEFAULT_GREETING = "Hi! I can search this site or bring in a specialist when it helps. What would you like to know?";
1238
- function createInitialState(greeting = DEFAULT_GREETING) {
1933
+ // src/runtime/tool-result-envelope.ts
1934
+ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
1935
+ "schemaVersion",
1936
+ "output",
1937
+ "presentationKinds",
1938
+ "ui"
1939
+ ]);
1940
+ function isRecord3(value) {
1941
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1942
+ }
1943
+ function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
1944
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
1945
+ return true;
1946
+ }
1947
+ if (typeof value === "number") return Number.isFinite(value);
1948
+ if (typeof value !== "object") return false;
1949
+ if (seen.has(value)) return false;
1950
+ seen.add(value);
1951
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
1952
+ ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
1953
+ );
1954
+ seen.delete(value);
1955
+ return valid;
1956
+ }
1957
+ function decodeEnvelope(value) {
1958
+ if (typeof value !== "string") return value;
1959
+ try {
1960
+ return JSON.parse(value);
1961
+ } catch {
1962
+ return null;
1963
+ }
1964
+ }
1965
+ function parseAgentToolResultEnvelope(value) {
1966
+ const decoded = decodeEnvelope(value);
1967
+ if (!isRecord3(decoded)) return null;
1968
+ const keys = Object.keys(decoded);
1969
+ 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) {
1970
+ return null;
1971
+ }
1972
+ const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
1973
+ if (typeof kind !== "string") return [];
1974
+ const normalized = kind.trim();
1975
+ return normalized && normalized.length <= 128 ? [normalized] : [];
1976
+ });
1977
+ if (presentationKinds.length !== decoded.presentationKinds.length) {
1978
+ return null;
1979
+ }
1980
+ const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
1981
+ if (decoded.ui !== void 0 && !ui) return null;
1239
1982
  return {
1240
- phase: "idle",
1241
- messages: [
1242
- {
1243
- id: "greeting",
1244
- role: "agent",
1245
- text: greeting,
1246
- createdAt: 0
1247
- }
1248
- ],
1249
- toolSteps: [],
1250
- journey: null,
1251
- followUps: [],
1252
- streamingText: "",
1253
- pendingOffer: null,
1254
- error: null
1983
+ schemaVersion: "webless.tool-result.v1",
1984
+ output: decoded.output,
1985
+ presentationKinds,
1986
+ ...ui ? { ui } : {}
1255
1987
  };
1256
1988
  }
1257
- function stateFromConversation(conversation, initialState) {
1258
- if (!conversation || conversation.messages.length === 0) return initialState;
1989
+
1990
+ // src/react/lib/tool-result.ts
1991
+ var MAX_TEXT_LENGTH = 240;
1992
+ var MAX_DETAILS = 6;
1993
+ var MAX_LINKS = 4;
1994
+ var MAX_COLLECTION_ITEMS = 8;
1995
+ 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;
1996
+ function safeText(value) {
1997
+ if (typeof value !== "string") return "";
1998
+ return value.trim().slice(0, MAX_TEXT_LENGTH);
1999
+ }
2000
+ function safeHref(value) {
2001
+ const text = safeText(value);
2002
+ if (!text) return "";
2003
+ try {
2004
+ const url = new URL(text);
2005
+ return url.protocol === "https:" ? url.toString() : "";
2006
+ } catch {
2007
+ return "";
2008
+ }
2009
+ }
2010
+ function fallbackTitle(result) {
2011
+ if (result.status === "failed") return "Couldn\u2019t complete this action";
2012
+ if (result.status === "rejected") return "Action not approved";
2013
+ return "Action completed";
2014
+ }
2015
+ function bookingPresenter(kind) {
1259
2016
  return {
1260
- ...initialState,
1261
- messages: conversation.messages,
1262
- phase: conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
1263
- streamingText: conversation.streamingText,
1264
- toolSteps: conversation.toolSteps
2017
+ kind,
2018
+ present: ({ envelope }) => {
2019
+ const card = bookingCardFromActionOutput(envelope.output);
2020
+ return card && card.type === kind ? { kind: "booking", card } : null;
2021
+ }
1265
2022
  };
1266
2023
  }
1267
- function upsertToolStep(steps, item) {
1268
- const next = {
1269
- id: item.id,
1270
- kind: item.kind,
1271
- label: item.label,
2024
+ function asRecord3(value) {
2025
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2026
+ }
2027
+ function parseFacts(value) {
2028
+ if (!Array.isArray(value)) return [];
2029
+ return value.flatMap((item) => {
2030
+ const fact = asRecord3(item);
2031
+ const label = safeText(fact?.label);
2032
+ const factValue = safeText(fact?.value);
2033
+ if (!label || !factValue) return [];
2034
+ if (INTERNAL_FACT_LABEL.test(label)) return [];
2035
+ return [{ label, value: factValue }];
2036
+ });
2037
+ }
2038
+ function parseLinks(output, defaultLabel) {
2039
+ const href = safeHref(output.href);
2040
+ const linkLabel = safeText(output.linkLabel) || defaultLabel;
2041
+ return href ? [{ label: linkLabel, href }] : [];
2042
+ }
2043
+ function entityResultPresenter() {
2044
+ return {
2045
+ kind: "entity_result",
2046
+ present: ({ envelope, result }) => {
2047
+ const output = asRecord3(envelope.output);
2048
+ if (!output) return null;
2049
+ const title = safeText(output.title) || fallbackTitle(result);
2050
+ const description = safeText(output.description);
2051
+ const facts = parseFacts(output.facts).filter((fact) => fact.value !== description && fact.value !== title).slice(0, MAX_DETAILS);
2052
+ return {
2053
+ kind: "entity",
2054
+ title,
2055
+ ...description && description !== title ? { description } : {},
2056
+ ...facts.length ? { details: facts } : {},
2057
+ ...parseLinks(output, "Open record").length ? { links: parseLinks(output, "Open record") } : {}
2058
+ };
2059
+ }
2060
+ };
2061
+ }
2062
+ function collectionResultPresenter() {
2063
+ return {
2064
+ kind: "collection",
2065
+ present: ({ envelope, result }) => {
2066
+ const output = asRecord3(envelope.output);
2067
+ if (!output) return null;
2068
+ const title = safeText(output.title) || fallbackTitleFromKind("collection");
2069
+ const items = Array.isArray(output.items) ? output.items.slice(0, MAX_COLLECTION_ITEMS).flatMap((item) => {
2070
+ const entry = asRecord3(item);
2071
+ if (!entry) return [];
2072
+ const itemTitle = safeText(entry.title);
2073
+ if (!itemTitle) return [];
2074
+ const description = safeText(entry.description);
2075
+ const details = parseFacts(entry.facts).slice(0, MAX_DETAILS);
2076
+ const href = safeHref(entry.href);
2077
+ return [
2078
+ {
2079
+ title: itemTitle,
2080
+ ...description ? { description } : {},
2081
+ ...details.length ? { details } : {},
2082
+ ...href ? { href } : {}
2083
+ }
2084
+ ];
2085
+ }) : [];
2086
+ return {
2087
+ kind: "collection",
2088
+ title,
2089
+ items
2090
+ };
2091
+ }
2092
+ };
2093
+ }
2094
+ function signatureResultPresenter() {
2095
+ return {
2096
+ kind: "signature",
2097
+ present: ({ envelope, result }) => {
2098
+ const output = asRecord3(envelope.output);
2099
+ if (!output) return null;
2100
+ const title = safeText(output.title) || fallbackTitleFromKind("signature");
2101
+ const description = safeText(output.description);
2102
+ const statusLabel = safeText(output.status);
2103
+ return {
2104
+ kind: "signature",
2105
+ title,
2106
+ ...description ? { description } : {},
2107
+ ...statusLabel ? { statusLabel } : {},
2108
+ ...parseLinks(output, "Sign now").length ? { links: parseLinks(output, "Sign now") } : {}
2109
+ };
2110
+ }
2111
+ };
2112
+ }
2113
+ function resultCardPresenter(kind) {
2114
+ return {
2115
+ kind,
2116
+ present: ({ envelope, result }) => {
2117
+ const output = asRecord3(envelope.output);
2118
+ if (!output) return null;
2119
+ const title = safeText(output.title) || fallbackTitleFromKind(kind);
2120
+ const description = safeText(output.description);
2121
+ const facts = parseFacts(output.facts).slice(0, MAX_DETAILS);
2122
+ return {
2123
+ kind: "summary",
2124
+ title,
2125
+ ...description && description !== title ? { description } : {},
2126
+ ...facts.length ? { details: facts } : {},
2127
+ ...parseLinks(output, "Open").length ? { links: parseLinks(output, "Open") } : {}
2128
+ };
2129
+ }
2130
+ };
2131
+ }
2132
+ function fallbackTitleFromKind(kind) {
2133
+ if (kind === "document") return "Document ready";
2134
+ if (kind === "signature") return "Ready to sign";
2135
+ if (kind === "payment") return "Payment link";
2136
+ if (kind === "collection") return "Results";
2137
+ if (kind === "confirmation_result") return "Confirmed";
2138
+ return "Saved";
2139
+ }
2140
+ var builtInVisitorToolResultRegistry = [
2141
+ bookingPresenter("booking_offer"),
2142
+ bookingPresenter("booking_confirmed"),
2143
+ bookingPresenter("booking_canceled"),
2144
+ entityResultPresenter(),
2145
+ collectionResultPresenter(),
2146
+ signatureResultPresenter(),
2147
+ resultCardPresenter("document"),
2148
+ resultCardPresenter("payment"),
2149
+ resultCardPresenter("confirmation_result")
2150
+ ];
2151
+ function resolvePresenterOutput(result, envelope, registry) {
2152
+ const presenters = [...registry, ...builtInVisitorToolResultRegistry];
2153
+ for (const presentationKind of envelope.presentationKinds) {
2154
+ const presenter = presenters.find(
2155
+ (candidate) => candidate.kind === presentationKind
2156
+ );
2157
+ if (!presenter) continue;
2158
+ try {
2159
+ const presented = presenter.present({
2160
+ envelope,
2161
+ presentationKind,
2162
+ result
2163
+ });
2164
+ if (presented) return presented;
2165
+ } catch {
2166
+ return null;
2167
+ }
2168
+ }
2169
+ return null;
2170
+ }
2171
+ function finalizeSummaryPresentation(result, proposed) {
2172
+ const title = safeText(proposed.title) || fallbackTitle(result);
2173
+ const description = safeText(proposed.description);
2174
+ const details = proposed.details?.slice(0, MAX_DETAILS).flatMap((detail) => {
2175
+ const label = safeText(detail.label);
2176
+ const value = safeText(detail.value);
2177
+ if (!label || !value) return [];
2178
+ if (INTERNAL_FACT_LABEL.test(label)) return [];
2179
+ if (value === title || value === description) return [];
2180
+ return [{ label, value }];
2181
+ });
2182
+ const links = proposed.links?.slice(0, MAX_LINKS).flatMap((link) => {
2183
+ const label = safeText(link.label);
2184
+ const href = safeHref(link.href);
2185
+ return label && href ? [{ label, href }] : [];
2186
+ });
2187
+ return {
2188
+ id: result.callId,
2189
+ toolName: result.toolName,
2190
+ status: result.status,
2191
+ kind: "summary",
2192
+ title,
2193
+ ...description && description !== title ? { description } : {},
2194
+ ...details?.length ? { details } : {},
2195
+ ...links?.length ? { links } : {}
2196
+ };
2197
+ }
2198
+ function presentVisitorToolResult(result, registry = []) {
2199
+ const envelope = parseAgentToolResultEnvelope(result.output) ?? legacyBookingEnvelope(result.output);
2200
+ const proposed = envelope ? resolvePresenterOutput(result, envelope, registry) : null;
2201
+ if (proposed?.kind === "booking") {
2202
+ return {
2203
+ id: result.callId,
2204
+ toolName: result.toolName,
2205
+ status: result.status,
2206
+ kind: "booking",
2207
+ card: proposed.card
2208
+ };
2209
+ }
2210
+ if (envelope?.ui && envelope.presentationKinds.includes("tool_input")) {
2211
+ return {
2212
+ id: result.callId,
2213
+ toolName: result.toolName,
2214
+ status: result.status,
2215
+ kind: "hidden"
2216
+ };
2217
+ }
2218
+ if (!proposed) {
2219
+ if (result.status === "failed" || result.status === "rejected") {
2220
+ return {
2221
+ id: result.callId,
2222
+ toolName: result.toolName,
2223
+ status: result.status,
2224
+ kind: "summary",
2225
+ title: fallbackTitle(result)
2226
+ };
2227
+ }
2228
+ return {
2229
+ id: result.callId,
2230
+ toolName: result.toolName,
2231
+ status: result.status,
2232
+ kind: "hidden"
2233
+ };
2234
+ }
2235
+ if (proposed.kind === "entity") {
2236
+ return {
2237
+ id: result.callId,
2238
+ toolName: result.toolName,
2239
+ status: result.status,
2240
+ ...proposed
2241
+ };
2242
+ }
2243
+ if (proposed.kind === "collection") {
2244
+ return {
2245
+ id: result.callId,
2246
+ toolName: result.toolName,
2247
+ status: result.status,
2248
+ ...proposed
2249
+ };
2250
+ }
2251
+ if (proposed.kind === "signature") {
2252
+ return {
2253
+ id: result.callId,
2254
+ toolName: result.toolName,
2255
+ status: result.status,
2256
+ ...proposed
2257
+ };
2258
+ }
2259
+ return finalizeSummaryPresentation(result, proposed);
2260
+ }
2261
+ function legacyBookingEnvelope(output) {
2262
+ const card = bookingCardFromActionOutput(output);
2263
+ return card ? {
2264
+ schemaVersion: "webless.tool-result.v1",
2265
+ output: card,
2266
+ presentationKinds: [card.type]
2267
+ } : null;
2268
+ }
2269
+
2270
+ // src/react/lib/visitor-input.ts
2271
+ function shouldRenderVisitorInputCard(request) {
2272
+ if (request.kind === "tool-approval" || request.kind === "session-limit") {
2273
+ return true;
2274
+ }
2275
+ if (request.kind === "question") {
2276
+ return (request.options?.length ?? 0) > 0;
2277
+ }
2278
+ return false;
2279
+ }
2280
+ function isChatCollectibleInputRequest(request) {
2281
+ return request.kind === "question" && (request.options?.length ?? 0) === 0;
2282
+ }
2283
+ function appendChatCollectiblePrompts(messages, requests) {
2284
+ const next = [...messages];
2285
+ for (const request of requests.filter(isChatCollectibleInputRequest)) {
2286
+ const prompt = request.prompt.trim();
2287
+ if (!prompt) continue;
2288
+ const last = next.at(-1);
2289
+ if (last?.role === "agent" && last.text.trim() === prompt) continue;
2290
+ next.push({
2291
+ id: `agent-input-${request.requestId}`,
2292
+ role: "agent",
2293
+ text: prompt,
2294
+ createdAt: Date.now()
2295
+ });
2296
+ }
2297
+ return next;
2298
+ }
2299
+ function chatInputResponseForText(requests, text) {
2300
+ const trimmed = text.trim();
2301
+ if (!trimmed) return null;
2302
+ const pending = requests.find(isChatCollectibleInputRequest);
2303
+ if (!pending) return null;
2304
+ return { requestId: pending.requestId, text: trimmed };
2305
+ }
2306
+ function normalizeAssistantDedupeKey(text) {
2307
+ return text.trim().replace(/\s+/g, " ").toLowerCase();
2308
+ }
2309
+ function isNearDuplicateAssistantText(left, right) {
2310
+ const a = normalizeAssistantDedupeKey(left);
2311
+ const b = normalizeAssistantDedupeKey(right);
2312
+ if (!a || !b) return false;
2313
+ if (a === b) return true;
2314
+ const shorter = a.length <= b.length ? a : b;
2315
+ const longer = a.length <= b.length ? b : a;
2316
+ if (shorter.length < 40) return false;
2317
+ return longer.startsWith(
2318
+ shorter.slice(0, Math.floor(shorter.length * 0.85))
2319
+ );
2320
+ }
2321
+ function appendAgentTurnMessage(messages, displayText) {
2322
+ const trimmed = displayText.trim();
2323
+ if (!trimmed) return [...messages];
2324
+ const agentMessage = {
2325
+ id: `agent-${Date.now()}`,
2326
+ role: "agent",
2327
+ text: trimmed,
2328
+ createdAt: Date.now()
2329
+ };
2330
+ const last = messages.at(-1);
2331
+ if (last?.role === "agent" && (last.id.startsWith("agent-input-") || isNearDuplicateAssistantText(last.text, trimmed))) {
2332
+ return [...messages.slice(0, -1), agentMessage];
2333
+ }
2334
+ return [...messages, agentMessage];
2335
+ }
2336
+
2337
+ // src/react/hooks/useAgentChat.ts
2338
+ var DEFAULT_GREETING = "Hi! I can search this site or bring in a specialist when it helps. What would you like to know?";
2339
+ function createInitialState(greeting = DEFAULT_GREETING) {
2340
+ return {
2341
+ phase: "idle",
2342
+ messages: [
2343
+ {
2344
+ id: "greeting",
2345
+ role: "agent",
2346
+ text: greeting,
2347
+ createdAt: 0
2348
+ }
2349
+ ],
2350
+ toolSteps: [],
2351
+ journey: null,
2352
+ followUps: [],
2353
+ streamingText: "",
2354
+ pendingOffer: null,
2355
+ pendingInputs: [],
2356
+ toolResults: [],
2357
+ error: null
2358
+ };
2359
+ }
2360
+ function stateFromConversation(conversation, initialState) {
2361
+ if (!conversation || conversation.messages.length === 0) return initialState;
2362
+ const pendingInputs = conversation.pendingInputs ?? [];
2363
+ const hasWaitingInput = pendingInputs.length > 0;
2364
+ return {
2365
+ ...initialState,
2366
+ messages: conversation.messages,
2367
+ phase: hasWaitingInput ? "waiting-input" : conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
2368
+ streamingText: conversation.streamingText,
2369
+ toolSteps: conversation.toolSteps,
2370
+ toolResults: conversation.toolResults,
2371
+ ...hasWaitingInput ? { pendingInputs } : {}
2372
+ };
2373
+ }
2374
+ function upsertToolStep(steps, item) {
2375
+ const next = {
2376
+ id: item.id,
2377
+ kind: item.kind,
2378
+ label: item.label,
1272
2379
  state: item.state,
1273
2380
  ...item.detail ? { detail: item.detail } : {}
1274
2381
  };
@@ -1276,6 +2383,24 @@ function upsertToolStep(steps, item) {
1276
2383
  if (index < 0) return [...steps, next];
1277
2384
  return steps.map((step, stepIndex) => stepIndex === index ? next : step);
1278
2385
  }
2386
+ function applyBookingOffer(prev, card) {
2387
+ const pendingOffer = preferBookingOffer(prev.pendingOffer, card);
2388
+ const last = prev.messages.at(-1);
2389
+ if (last?.role === "agent" && prev.phase === "complete") {
2390
+ return {
2391
+ ...prev,
2392
+ pendingOffer,
2393
+ messages: [
2394
+ ...prev.messages.slice(0, -1),
2395
+ {
2396
+ ...last,
2397
+ text: ensureBookingOfferText(last.text, pendingOffer)
2398
+ }
2399
+ ]
2400
+ };
2401
+ }
2402
+ return { ...prev, pendingOffer };
2403
+ }
1279
2404
  function completeActivePlanning(steps) {
1280
2405
  return steps.map(
1281
2406
  (step) => step.kind === "planning" && step.state === "active" ? {
@@ -1285,6 +2410,9 @@ function completeActivePlanning(steps) {
1285
2410
  } : step
1286
2411
  );
1287
2412
  }
2413
+ function isJsonRecord(value) {
2414
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2415
+ }
1288
2416
  function useAgentChat({
1289
2417
  customerId,
1290
2418
  getUnpublishedPreviewGrant,
@@ -1294,7 +2422,8 @@ function useAgentChat({
1294
2422
  runtimeOrigin,
1295
2423
  visitorSessionId,
1296
2424
  storageKeyPrefix,
1297
- greeting
2425
+ greeting,
2426
+ toolResultRegistry
1298
2427
  }) {
1299
2428
  const initialState = (0, import_react2.useMemo)(
1300
2429
  () => createInitialState(greeting?.trim() || DEFAULT_GREETING),
@@ -1302,6 +2431,8 @@ function useAgentChat({
1302
2431
  );
1303
2432
  const previewGrantProviderRef = (0, import_react2.useRef)(getUnpublishedPreviewGrant);
1304
2433
  previewGrantProviderRef.current = getUnpublishedPreviewGrant;
2434
+ const toolResultRegistryRef = (0, import_react2.useRef)(toolResultRegistry);
2435
+ toolResultRegistryRef.current = toolResultRegistry;
1305
2436
  const resolveUnpublishedPreviewGrant = () => {
1306
2437
  const provider = previewGrantProviderRef.current;
1307
2438
  if (!provider) {
@@ -1396,14 +2527,18 @@ function useAgentChat({
1396
2527
  messages: state.messages,
1397
2528
  pending: isAgentBusy(state.phase),
1398
2529
  streamingText: state.streamingText,
1399
- toolSteps: state.toolSteps
2530
+ toolSteps: state.toolSteps,
2531
+ toolResults: state.toolResults ?? [],
2532
+ ...state.pendingInputs && state.pendingInputs.length > 0 ? { pendingInputs: state.pendingInputs } : {}
1400
2533
  });
1401
2534
  }, [
1402
2535
  resolvedStorageKeyPrefix,
1403
2536
  state.messages,
2537
+ state.pendingInputs,
1404
2538
  state.phase,
1405
2539
  state.streamingText,
1406
2540
  state.toolSteps,
2541
+ state.toolResults,
1407
2542
  visitorId
1408
2543
  ]);
1409
2544
  const reset = (0, import_react2.useCallback)(() => {
@@ -1416,13 +2551,20 @@ function useAgentChat({
1416
2551
  }, [initialState, resolvedStorageKeyPrefix, visitorId]);
1417
2552
  const runTurn = (0, import_react2.useCallback)(
1418
2553
  async (input) => {
1419
- const { controller, initialText = "", resume, visitorText } = input;
2554
+ const {
2555
+ controller,
2556
+ initialText = "",
2557
+ responses,
2558
+ resume,
2559
+ visitorText
2560
+ } = input;
1420
2561
  const { signal } = controller;
1421
2562
  const isActiveRun = () => runRef.current === controller && !signal.aborted;
1422
2563
  try {
1423
2564
  let streamStarted = Boolean(initialText);
1424
2565
  let streamed = initialText;
1425
2566
  const capturedOffers = [];
2567
+ let capturedInputCount = 0;
1426
2568
  const handlers = {
1427
2569
  onWork: (item) => {
1428
2570
  if (!isActiveRun()) return;
@@ -1432,11 +2574,62 @@ function useAgentChat({
1432
2574
  toolSteps: upsertToolStep(prev.toolSteps, item)
1433
2575
  }));
1434
2576
  },
1435
- onActionResult: (output) => {
1436
- const offer = bookingOfferFromActionOutput(output);
1437
- if (!offer) return;
1438
- capturedOffers.push(offer);
1439
- setState((prev) => ({ ...prev, pendingOffer: offer }));
2577
+ onToolResult: (result) => {
2578
+ const presentation = presentVisitorToolResult(
2579
+ result,
2580
+ toolResultRegistryRef.current
2581
+ );
2582
+ if (presentation.kind === "booking") {
2583
+ const card = presentation.card;
2584
+ if (card.type === "booking_offer") {
2585
+ capturedOffers.push(card);
2586
+ setState((prev) => applyBookingOffer(prev, card));
2587
+ return;
2588
+ }
2589
+ if (!isActiveRun()) return;
2590
+ if (card.type === "booking_confirmed") {
2591
+ pendingBookingRef.current = card;
2592
+ savePendingWidgetBooking(
2593
+ resolvedStorageKeyPrefix,
2594
+ visitorId,
2595
+ card
2596
+ );
2597
+ } else if (card.type === "booking_canceled" && pendingBookingRef.current?.eventUri === card.eventUri) {
2598
+ pendingBookingRef.current = null;
2599
+ clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
2600
+ }
2601
+ return;
2602
+ }
2603
+ if (!isActiveRun()) return;
2604
+ if (presentation.kind === "hidden") return;
2605
+ if (presentation.kind === "input") return;
2606
+ setState((prev) => ({
2607
+ ...prev,
2608
+ toolResults: [
2609
+ ...(prev.toolResults ?? []).filter(
2610
+ (item) => item.id !== presentation.id
2611
+ ),
2612
+ presentation
2613
+ ]
2614
+ }));
2615
+ },
2616
+ onInputRequest: (requests) => {
2617
+ if (!isActiveRun()) return;
2618
+ capturedInputCount += requests.length;
2619
+ const cardRequests = requests.filter(shouldRenderVisitorInputCard);
2620
+ const chatRequests = requests.filter(isChatCollectibleInputRequest);
2621
+ setState((prev) => ({
2622
+ ...prev,
2623
+ phase: cardRequests.length > 0 ? "waiting-input" : "complete",
2624
+ pendingInputs: [...requests],
2625
+ ...chatRequests.length > 0 ? {
2626
+ messages: appendChatCollectiblePrompts(
2627
+ prev.messages,
2628
+ chatRequests
2629
+ ),
2630
+ toolSteps: completeActivePlanning(prev.toolSteps)
2631
+ } : {}
2632
+ }));
1440
2633
  },
1441
2634
  onDelta: (delta) => {
1442
2635
  if (!isActiveRun()) return;
@@ -1462,7 +2655,11 @@ function useAgentChat({
1462
2655
  streamStarted = true;
1463
2656
  }
1464
2657
  };
1465
- let finalText = resume ? await clientRef.current.resumeTurn({
2658
+ let finalText = responses ? await clientRef.current.respondTurn({
2659
+ handlers,
2660
+ responses,
2661
+ signal
2662
+ }) : resume ? await clientRef.current.resumeTurn({
1466
2663
  handlers,
1467
2664
  initialText,
1468
2665
  message: visitorText,
@@ -1474,17 +2671,15 @@ function useAgentChat({
1474
2671
  signal
1475
2672
  });
1476
2673
  }
1477
- if (!isActiveRun() || finalText === null) return;
2674
+ if (!isActiveRun() || finalText === null) return null;
2675
+ if (!finalText.trim() && capturedInputCount > 0) {
2676
+ runRef.current = null;
2677
+ return null;
2678
+ }
1478
2679
  const displayText = ensureBookingOfferText(
1479
2680
  finalText,
1480
2681
  capturedOffers.at(-1) ?? null
1481
2682
  );
1482
- const agentMessage = {
1483
- id: `agent-${Date.now()}`,
1484
- role: "agent",
1485
- text: displayText,
1486
- createdAt: Date.now()
1487
- };
1488
2683
  const parsedCards = extractToolCards(displayText);
1489
2684
  for (const card of parsedCards) {
1490
2685
  if (card.type === "booking_confirmed") {
@@ -1498,21 +2693,25 @@ function useAgentChat({
1498
2693
  }
1499
2694
  setState((prev) => ({
1500
2695
  ...prev,
1501
- phase: "complete",
1502
- messages: [...prev.messages, agentMessage],
2696
+ phase: (prev.pendingInputs ?? []).some(shouldRenderVisitorInputCard) ? "waiting-input" : "complete",
2697
+ messages: appendAgentTurnMessage(prev.messages, displayText),
1503
2698
  toolSteps: completeActivePlanning(prev.toolSteps),
1504
2699
  streamingText: "",
1505
- pendingOffer: null,
2700
+ pendingOffer: capturedOffers.at(-1) ?? prev.pendingOffer,
2701
+ pendingInputs: (prev.pendingInputs ?? []).filter(
2702
+ isChatCollectibleInputRequest
2703
+ ),
1506
2704
  followUps: [],
1507
2705
  journey: null
1508
2706
  }));
1509
2707
  runRef.current = null;
2708
+ return displayText;
1510
2709
  } catch (error) {
1511
2710
  if (error instanceof DOMException && error.name === "AbortError")
1512
- return;
1513
- if (!isActiveRun()) return;
2711
+ return null;
2712
+ if (!isActiveRun()) return null;
1514
2713
  const message = formatAgentError(error);
1515
- if (!message) return;
2714
+ if (!message) return null;
1516
2715
  setState((prev) => ({
1517
2716
  ...prev,
1518
2717
  phase: "error",
@@ -1528,6 +2727,7 @@ function useAgentChat({
1528
2727
  error: message
1529
2728
  }));
1530
2729
  runRef.current = null;
2730
+ return null;
1531
2731
  }
1532
2732
  },
1533
2733
  [resolvedStorageKeyPrefix, visitorId]
@@ -1555,6 +2755,49 @@ function useAgentChat({
1555
2755
  );
1556
2756
  const submit = (0, import_react2.useCallback)(
1557
2757
  async (visitorText, options) => {
2758
+ const trimmed = visitorText.trim();
2759
+ if (!trimmed) return null;
2760
+ const chatResponse = chatInputResponseForText(
2761
+ state.pendingInputs ?? [],
2762
+ trimmed
2763
+ );
2764
+ if (chatResponse) {
2765
+ const visitorMessage2 = {
2766
+ id: `visitor-${Date.now()}`,
2767
+ role: "visitor",
2768
+ text: trimmed,
2769
+ createdAt: Date.now()
2770
+ };
2771
+ if (runRef.current) {
2772
+ runRef.current.abort();
2773
+ clientRef.current.cancelActive();
2774
+ }
2775
+ const controller2 = new AbortController();
2776
+ runRef.current = controller2;
2777
+ setState((prev) => ({
2778
+ ...prev,
2779
+ phase: "running-tools",
2780
+ messages: [...prev.messages, visitorMessage2],
2781
+ pendingInputs: (prev.pendingInputs ?? []).filter(
2782
+ (request) => request.requestId !== chatResponse.requestId
2783
+ ),
2784
+ toolSteps: [
2785
+ {
2786
+ id: "planning",
2787
+ kind: "planning",
2788
+ label: "Understanding your question",
2789
+ state: "active"
2790
+ }
2791
+ ],
2792
+ error: null
2793
+ }));
2794
+ return await runTurn({
2795
+ controller: controller2,
2796
+ responses: [chatResponse],
2797
+ resume: false,
2798
+ visitorText: ""
2799
+ });
2800
+ }
1558
2801
  if (runRef.current) {
1559
2802
  runRef.current.abort();
1560
2803
  clientRef.current.cancelActive();
@@ -1589,11 +2832,17 @@ ${outgoing}` : outgoing;
1589
2832
  followUps: [],
1590
2833
  streamingText: "",
1591
2834
  pendingOffer: null,
2835
+ pendingInputs: [],
2836
+ toolResults: [],
1592
2837
  error: null
1593
2838
  }));
1594
- await runTurn({ controller, resume: false, visitorText: runtimeText });
2839
+ return await runTurn({
2840
+ controller,
2841
+ resume: false,
2842
+ visitorText: runtimeText
2843
+ });
1595
2844
  },
1596
- [runTurn]
2845
+ [runTurn, state.pendingInputs]
1597
2846
  );
1598
2847
  const retry = (0, import_react2.useCallback)(async () => {
1599
2848
  const visitorMessage = [...state.messages].reverse().find((message) => message.role === "visitor");
@@ -1619,6 +2868,8 @@ ${outgoing}` : outgoing;
1619
2868
  followUps: [],
1620
2869
  streamingText: "",
1621
2870
  pendingOffer: null,
2871
+ pendingInputs: [],
2872
+ toolResults: [],
1622
2873
  error: null
1623
2874
  }));
1624
2875
  await runTurn({
@@ -1627,6 +2878,50 @@ ${outgoing}` : outgoing;
1627
2878
  visitorText: visitorTurnText(visitorMessage)
1628
2879
  });
1629
2880
  }, [runTurn, state.messages]);
2881
+ const respondToToolInput = (0, import_react2.useCallback)(
2882
+ async (surface, values) => {
2883
+ const details = JSON.stringify(values);
2884
+ await submit(`${surface.title} details provided`, {
2885
+ runtimeText: [
2886
+ `Structured input provided for ${surface.toolSlug}.`,
2887
+ surface.operationId ? `Operation: ${surface.operationId}.` : "",
2888
+ `Use these exact values and retry the action: ${details}`,
2889
+ "Do not invent or alter any values."
2890
+ ].filter(Boolean).join("\n")
2891
+ });
2892
+ },
2893
+ [submit]
2894
+ );
2895
+ const respondToInput = (0, import_react2.useCallback)(
2896
+ async (response) => {
2897
+ if (runRef.current) return;
2898
+ const pending = state.pendingInputs?.find(
2899
+ (request) => request.requestId === response.requestId
2900
+ );
2901
+ if (!pending) return;
2902
+ if (pending.ui && isJsonRecord(response.value)) {
2903
+ await respondToToolInput(pending.ui, response.value);
2904
+ return;
2905
+ }
2906
+ const controller = new AbortController();
2907
+ runRef.current = controller;
2908
+ setState((prev) => ({
2909
+ ...prev,
2910
+ phase: "running-tools",
2911
+ pendingInputs: (prev.pendingInputs ?? []).filter(
2912
+ (request) => request.requestId !== response.requestId
2913
+ ),
2914
+ error: null
2915
+ }));
2916
+ await runTurn({
2917
+ controller,
2918
+ responses: [response],
2919
+ resume: false,
2920
+ visitorText: ""
2921
+ });
2922
+ },
2923
+ [respondToToolInput, runTurn, state.pendingInputs]
2924
+ );
1630
2925
  (0, import_react2.useEffect)(() => {
1631
2926
  const conversation = loadPersistedAgentConversation(
1632
2927
  resolvedStorageKeyPrefix,
@@ -1660,6 +2955,8 @@ ${outgoing}` : outgoing;
1660
2955
  state,
1661
2956
  reset,
1662
2957
  retry,
2958
+ respondToInput,
2959
+ respondToToolInput,
1663
2960
  submit,
1664
2961
  rememberBooking,
1665
2962
  forgetBooking,
@@ -1708,7 +3005,7 @@ function normalizeAgentPlacement(placement) {
1708
3005
  }
1709
3006
 
1710
3007
  // src/react/components/AgentRail/AgentRail.tsx
1711
- var import_react9 = require("react");
3008
+ var import_react10 = require("react");
1712
3009
 
1713
3010
  // src/react/types/conversation.ts
1714
3011
  var defaultAgentRailTheme = {
@@ -1722,8 +3019,8 @@ var defaultAgentRailTheme = {
1722
3019
  textMuted: "#5a6378",
1723
3020
  textSubtle: "#8a94a8",
1724
3021
  border: "rgb(42 51 70 / 0.1)",
1725
- visitorBubble: "#6f16ff",
1726
- visitorText: "#ffffff",
3022
+ visitorBubble: "#f3edff",
3023
+ visitorText: "#171b2a",
1727
3024
  success: "#18794e",
1728
3025
  danger: "#c94b63",
1729
3026
  fontBody: '"Mulish", "Avenir Next", "Segoe UI", sans-serif',
@@ -1740,7 +3037,8 @@ var defaultDarkAgentRailTheme = {
1740
3037
  textMuted: "#b6bfce",
1741
3038
  textSubtle: "#919cad",
1742
3039
  border: "rgb(226 232 240 / 0.16)",
1743
- visitorBubble: "#7c3aed",
3040
+ visitorBubble: "#2b2140",
3041
+ visitorText: "#f5f7fb",
1744
3042
  success: "#55cf91",
1745
3043
  danger: "#ff8da1"
1746
3044
  };
@@ -1778,10 +3076,20 @@ function resolveAgentColorScheme(colorScheme = "auto", prefersDarkMode) {
1778
3076
  // src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
1779
3077
  var import_react5 = require("react");
1780
3078
  var import_jsx_runtime = require("react/jsx-runtime");
3079
+ function joinLabels(labels) {
3080
+ if (labels.length <= 1) return labels[0] ?? "";
3081
+ if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
3082
+ return `${labels.slice(0, -1).join(", ")} and ${labels.at(-1) ?? ""}`;
3083
+ }
1781
3084
  function workSummary(steps, failed, brandLabel) {
3085
+ const activeSpecialists = steps.filter(
3086
+ (step) => step.kind === "specialist" && step.state === "active"
3087
+ );
3088
+ if (activeSpecialists.length > 0)
3089
+ return `Working with ${joinLabels(
3090
+ activeSpecialists.map((step) => step.label)
3091
+ )}`;
1782
3092
  const active = [...steps].reverse().find((step) => step.state === "active");
1783
- if (active?.kind === "specialist")
1784
- return `${active.label} is reviewing your question`;
1785
3093
  if (active?.kind === "search") return "Searching this site";
1786
3094
  if (active)
1787
3095
  return brandLabel ? `${brandLabel} is choosing the best way to help` : "Choosing the best way to help";
@@ -1799,7 +3107,7 @@ function workSummary(steps, failed, brandLabel) {
1799
3107
  if (specialists.length === 1)
1800
3108
  return `Brought in ${specialists[0]?.label}`;
1801
3109
  if (searched) return "Searched this site";
1802
- return "Answer ready";
3110
+ return brandLabel ? `Answered with ${brandLabel}` : "Answer ready";
1803
3111
  }
1804
3112
  function stepLabel(step, brandLabel) {
1805
3113
  return step.kind === "planning" ? brandLabel : step.label;
@@ -1817,36 +3125,11 @@ function stepDetail(step, steps) {
1817
3125
  return "Searched this site";
1818
3126
  return step.detail;
1819
3127
  }
1820
- function SearchIcon() {
1821
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
1822
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "7", cy: "7", r: "3.75", stroke: "currentColor", strokeWidth: "1.4" }),
1823
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1824
- "path",
1825
- {
1826
- d: "m10 10 3 3",
1827
- stroke: "currentColor",
1828
- strokeWidth: "1.4",
1829
- strokeLinecap: "round"
1830
- }
1831
- )
1832
- ] });
1833
- }
1834
- function PlanningIcon() {
1835
- 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)(
1836
- "path",
1837
- {
1838
- d: "M4 4.5h8M4 8h5.5M4 11.5h7",
1839
- stroke: "currentColor",
1840
- strokeWidth: "1.4",
1841
- strokeLinecap: "round"
1842
- }
1843
- ) });
1844
- }
1845
3128
  function AgentActivityBubble({
1846
3129
  brandLabel = "",
1847
- brandLogoUrl,
1848
3130
  failed = false,
1849
- steps
3131
+ steps,
3132
+ onRetryStep
1850
3133
  }) {
1851
3134
  const active = steps.some((step) => step.state === "active");
1852
3135
  const receiptId = steps.map((step) => step.id).join(":");
@@ -1854,6 +3137,10 @@ function AgentActivityBubble({
1854
3137
  null
1855
3138
  );
1856
3139
  const detailsOpen = active || expandedReceiptId === receiptId;
3140
+ const delegationCount = steps.filter(
3141
+ (step) => step.kind === "specialist"
3142
+ ).length;
3143
+ const delegated = delegationCount > 0 && steps.some((step) => step.kind === "planning");
1857
3144
  const visibleSteps = steps.filter(
1858
3145
  (step) => step.kind !== "planning" || Boolean(brandLabel)
1859
3146
  );
@@ -1886,10 +3173,11 @@ function AgentActivityBubble({
1886
3173
  ),
1887
3174
  detailsOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "agent-activity-bubble__steps", children: visibleSteps.map((step) => {
1888
3175
  const detail = stepDetail(step, steps);
3176
+ const child = delegated && step.kind === "specialist";
1889
3177
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1890
3178
  "li",
1891
3179
  {
1892
- className: "agent-activity-bubble__step",
3180
+ className: `agent-activity-bubble__step${child ? " agent-activity-bubble__step--child" : ""}`,
1893
3181
  "data-kind": step.kind,
1894
3182
  "data-state": step.state,
1895
3183
  children: [
@@ -1897,25 +3185,27 @@ function AgentActivityBubble({
1897
3185
  "span",
1898
3186
  {
1899
3187
  className: "agent-activity-bubble__step-icon",
1900
- "aria-hidden": "true",
1901
- children: step.kind === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1902
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlanningIcon, {}),
1903
- brandLogoUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1904
- "img",
1905
- {
1906
- src: brandLogoUrl,
1907
- alt: "",
1908
- onError: (event) => {
1909
- event.currentTarget.hidden = true;
1910
- }
1911
- }
1912
- ) : null
1913
- ] }) : step.kind === "search" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
3188
+ "aria-hidden": "true"
1914
3189
  }
1915
3190
  ),
1916
3191
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-copy", children: [
1917
3192
  /* @__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) }) }),
1918
- detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
3193
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null,
3194
+ delegated && step.kind === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__delegation", children: [
3195
+ "Delegated ",
3196
+ delegationCount,
3197
+ " ",
3198
+ delegationCount === 1 ? "task" : "tasks"
3199
+ ] }) : null,
3200
+ step.state === "error" && onRetryStep ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3201
+ "button",
3202
+ {
3203
+ type: "button",
3204
+ className: "agent-activity-bubble__step-retry",
3205
+ onClick: () => onRetryStep(step),
3206
+ children: "Retry"
3207
+ }
3208
+ ) : null
1919
3209
  ] })
1920
3210
  ]
1921
3211
  },
@@ -1930,59 +3220,190 @@ function AgentActivityBubble({
1930
3220
  var import_react6 = require("react");
1931
3221
  var import_jsx_runtime2 = require("react/jsx-runtime");
1932
3222
  function SendIcon() {
1933
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 12V4M8 4l-3 3M8 4l3 3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
3223
+ 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)(
3224
+ "path",
3225
+ {
3226
+ d: "M8 12V4M8 4l-3 3M8 4l3 3",
3227
+ stroke: "currentColor",
3228
+ strokeWidth: "1.5",
3229
+ strokeLinecap: "round",
3230
+ strokeLinejoin: "round"
3231
+ }
3232
+ ) });
3233
+ }
3234
+ function emptyValues(form) {
3235
+ const values = {};
3236
+ for (const field of form?.fields ?? []) values[field.id] = "";
3237
+ return values;
1934
3238
  }
1935
3239
  function Composer({
1936
3240
  disabled = false,
1937
3241
  placeholder = "Ask anything\u2026",
1938
3242
  variant = "default",
3243
+ form = null,
1939
3244
  onSubmit
1940
3245
  }) {
1941
3246
  const [value, setValue] = (0, import_react6.useState)("");
3247
+ const [values, setValues] = (0, import_react6.useState)(
3248
+ () => emptyValues(form)
3249
+ );
3250
+ const [blurred, setBlurred] = (0, import_react6.useState)({});
1942
3251
  const inputRef = (0, import_react6.useRef)(null);
1943
- function submitCurrent() {
3252
+ const firstFieldRef = (0, import_react6.useRef)(null);
3253
+ const formId = (0, import_react6.useId)();
3254
+ const activeForm = form;
3255
+ const canSendForm = activeForm ? isComposerFormComplete(activeForm, values) : Boolean(value.trim());
3256
+ (0, import_react6.useEffect)(() => {
3257
+ setValues(emptyValues(form));
3258
+ setBlurred({});
3259
+ }, [form?.id]);
3260
+ (0, import_react6.useEffect)(() => {
3261
+ if (activeForm) firstFieldRef.current?.focus();
3262
+ }, [activeForm?.id]);
3263
+ function submitChat() {
1944
3264
  const trimmed = value.trim();
1945
3265
  if (!trimmed || disabled) return;
1946
3266
  onSubmit?.(trimmed);
1947
3267
  setValue("");
1948
3268
  inputRef.current?.focus();
1949
3269
  }
3270
+ function submitForm() {
3271
+ if (!activeForm || disabled || !canSendForm) return;
3272
+ onSubmit?.(formatComposerFormMessage(activeForm, values));
3273
+ setValues(emptyValues(activeForm));
3274
+ setBlurred({});
3275
+ }
1950
3276
  function handleSubmit(event) {
1951
3277
  event.preventDefault();
1952
- submitCurrent();
3278
+ if (activeForm) submitForm();
3279
+ else submitChat();
1953
3280
  }
1954
- function handleKeyDown(event) {
3281
+ function handleChatKeyDown(event) {
1955
3282
  if (event.key === "Enter" && !event.shiftKey) {
1956
3283
  event.preventDefault();
1957
- submitCurrent();
3284
+ submitChat();
1958
3285
  }
1959
3286
  }
1960
- 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: [
1961
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1962
- "textarea",
1963
- {
1964
- ref: inputRef,
1965
- className: "composer__input",
1966
- rows: 1,
1967
- value,
1968
- placeholder,
1969
- disabled,
1970
- "aria-label": "Message",
1971
- onChange: (event) => setValue(event.target.value),
1972
- onKeyDown: handleKeyDown
1973
- }
1974
- ),
1975
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1976
- "button",
1977
- {
1978
- type: "submit",
1979
- className: "composer__send",
1980
- disabled: disabled || !value.trim(),
1981
- "aria-label": "Send message",
1982
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
1983
- }
1984
- )
1985
- ] }) });
3287
+ function handleFormKeyDown(event) {
3288
+ const target = event.target;
3289
+ const isTextarea = Boolean(target && "tagName" in target && target.tagName === "TEXTAREA");
3290
+ if (event.key === "Enter" && !event.shiftKey && !isTextarea) {
3291
+ event.preventDefault();
3292
+ submitForm();
3293
+ }
3294
+ }
3295
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3296
+ "form",
3297
+ {
3298
+ className: [
3299
+ "composer",
3300
+ variant === "dock" ? "composer--dock" : "",
3301
+ activeForm ? "composer--form" : ""
3302
+ ].filter(Boolean).join(" "),
3303
+ onSubmit: handleSubmit,
3304
+ children: activeForm ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "composer__sheet", role: "group", "aria-label": "Required details", children: [
3305
+ activeForm.fields.map((field, index) => {
3306
+ const fieldId = `${formId}-${field.id}`;
3307
+ const invalid = Boolean(blurred[field.id]) && !isValidComposerFieldValue(field, values[field.id] ?? "");
3308
+ const controlProps = {
3309
+ id: fieldId,
3310
+ name: field.id,
3311
+ disabled,
3312
+ required: field.required,
3313
+ autoComplete: field.autocomplete,
3314
+ placeholder: field.placeholder,
3315
+ spellCheck: false,
3316
+ value: values[field.id] ?? "",
3317
+ "aria-invalid": invalid || void 0,
3318
+ "aria-describedby": invalid ? `${fieldId}-error` : void 0,
3319
+ onBlur: () => setBlurred((current) => ({ ...current, [field.id]: true })),
3320
+ onChange: (event) => {
3321
+ const next = readComposerControlValue(event);
3322
+ setValues((current) => ({
3323
+ ...current,
3324
+ [field.id]: next
3325
+ }));
3326
+ },
3327
+ onKeyDown: handleFormKeyDown
3328
+ };
3329
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3330
+ "div",
3331
+ {
3332
+ className: [
3333
+ "composer__row",
3334
+ field.kind === "textarea" ? "composer__row--grow" : ""
3335
+ ].filter(Boolean).join(" "),
3336
+ children: [
3337
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "composer__label", htmlFor: fieldId, children: [
3338
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "composer__sr-only", children: field.label }),
3339
+ field.kind === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3340
+ "textarea",
3341
+ {
3342
+ ...controlProps,
3343
+ ref: index === 0 ? (node) => {
3344
+ firstFieldRef.current = node;
3345
+ } : void 0,
3346
+ className: "composer__control composer__control--area",
3347
+ rows: 3
3348
+ }
3349
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3350
+ "input",
3351
+ {
3352
+ ...controlProps,
3353
+ ref: index === 0 ? (node) => {
3354
+ firstFieldRef.current = node;
3355
+ } : void 0,
3356
+ className: "composer__control",
3357
+ type: field.kind,
3358
+ inputMode: field.kind === "tel" ? "tel" : void 0
3359
+ }
3360
+ )
3361
+ ] }),
3362
+ 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
3363
+ ]
3364
+ },
3365
+ field.id
3366
+ );
3367
+ }),
3368
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "composer__toolbar", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3369
+ "button",
3370
+ {
3371
+ type: "submit",
3372
+ className: "composer__send",
3373
+ disabled: disabled || !canSendForm,
3374
+ "aria-label": "Send details",
3375
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
3376
+ }
3377
+ ) })
3378
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "composer__field", children: [
3379
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3380
+ "textarea",
3381
+ {
3382
+ ref: inputRef,
3383
+ className: "composer__input",
3384
+ rows: 1,
3385
+ value,
3386
+ placeholder,
3387
+ disabled,
3388
+ spellCheck: false,
3389
+ "aria-label": "Message",
3390
+ onChange: (event) => setValue(event.target.value),
3391
+ onKeyDown: handleChatKeyDown
3392
+ }
3393
+ ),
3394
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3395
+ "button",
3396
+ {
3397
+ type: "submit",
3398
+ className: "composer__send",
3399
+ disabled: disabled || !value.trim(),
3400
+ "aria-label": "Send message",
3401
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
3402
+ }
3403
+ )
3404
+ ] })
3405
+ }
3406
+ );
1986
3407
  }
1987
3408
 
1988
3409
  // src/react/components/FollowUpChips/FollowUpChips.tsx
@@ -2030,9 +3451,15 @@ var import_react8 = require("react");
2030
3451
  // src/react/components/BookingCard/BookingCard.tsx
2031
3452
  var import_react7 = require("react");
2032
3453
  var import_jsx_runtime4 = require("react/jsx-runtime");
3454
+ var BOOKING_STEPS = [
3455
+ { id: "date", label: "Date" },
3456
+ { id: "time", label: "Time" },
3457
+ { id: "details", label: "Details" }
3458
+ ];
2033
3459
  function monthFromKey(key) {
2034
3460
  const [year, month] = key.split("-").map(Number);
2035
- if (!year || !month) return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
3461
+ if (!year || !month)
3462
+ return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
2036
3463
  return { year, month: month - 1 };
2037
3464
  }
2038
3465
  function dateKeyFromParts(year, month, day) {
@@ -2065,6 +3492,7 @@ function BookingCard({
2065
3492
  const [startTime, setStartTime] = (0, import_react7.useState)("");
2066
3493
  const [name, setName] = (0, import_react7.useState)("");
2067
3494
  const [email, setEmail] = (0, import_react7.useState)("");
3495
+ const stepIndex = BOOKING_STEPS.findIndex((item) => item.id === step);
2068
3496
  const slots = (0, import_react7.useMemo)(
2069
3497
  () => bookingSlotsForEventType(offer.slots, eventTypeUri),
2070
3498
  [eventTypeUri, offer.slots]
@@ -2085,14 +3513,18 @@ function BookingCard({
2085
3513
  setSelectedDate("");
2086
3514
  setStartTime("");
2087
3515
  setVisibleMonth(
2088
- firstAvailableBookingMonth(bookingSlotsForEventType(offer.slots, nextType))
3516
+ firstAvailableBookingMonth(
3517
+ bookingSlotsForEventType(offer.slots, nextType)
3518
+ )
2089
3519
  );
2090
3520
  }
2091
3521
  const daySlots = (0, import_react7.useMemo)(
2092
3522
  () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
2093
3523
  [selectedDate, slots]
2094
3524
  );
2095
- const selectedType = offer.eventTypes.find((item) => item.uri === eventTypeUri);
3525
+ const selectedType = offer.eventTypes.find(
3526
+ (item) => item.uri === eventTypeUri
3527
+ );
2096
3528
  const selectedSample = availableByDate.get(selectedDate) ?? startTime;
2097
3529
  const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
2098
3530
  const weekdays = (0, import_react7.useMemo)(() => weekdayLabels(), []);
@@ -2138,24 +3570,47 @@ function BookingCard({
2138
3570
  });
2139
3571
  }
2140
3572
  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: [
3573
+ /* @__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)(
3574
+ "li",
3575
+ {
3576
+ className: [
3577
+ "booking-card__step-indicator",
3578
+ index === stepIndex ? "booking-card__step-indicator--active" : "",
3579
+ index < stepIndex ? "booking-card__step-indicator--complete" : ""
3580
+ ].filter(Boolean).join(" "),
3581
+ "aria-current": index === stepIndex ? "step" : void 0,
3582
+ children: [
3583
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", children: index + 1 }),
3584
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: item.label })
3585
+ ]
3586
+ },
3587
+ item.id
3588
+ )) }),
2141
3589
  step === "date" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2142
3590
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__title", children: selectedType?.name || "Pick a date" }),
2143
3591
  timeZone ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "booking-card__tz", children: [
2144
3592
  "Times in ",
2145
3593
  timeZone
2146
3594
  ] }) : null,
2147
- offer.eventTypes.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-type`, children: [
2148
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Meeting" }),
2149
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2150
- "select",
2151
- {
2152
- id: `${fieldId}-type`,
2153
- value: eventTypeUri,
2154
- onChange: (event) => selectEventType(event.target.value),
2155
- children: offer.eventTypes.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: item.uri, children: item.name }, item.uri))
2156
- }
2157
- )
2158
- ] }) : null,
3595
+ offer.eventTypes.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3596
+ "label",
3597
+ {
3598
+ className: "booking-card__field",
3599
+ htmlFor: `${fieldId}-type`,
3600
+ children: [
3601
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Meeting" }),
3602
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3603
+ "select",
3604
+ {
3605
+ id: `${fieldId}-type`,
3606
+ value: eventTypeUri,
3607
+ onChange: (event) => selectEventType(event.target.value),
3608
+ children: offer.eventTypes.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: item.uri, children: item.name }, item.uri))
3609
+ }
3610
+ )
3611
+ ]
3612
+ }
3613
+ ) : null,
2159
3614
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__month", children: [
2160
3615
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2161
3616
  "button",
@@ -2182,29 +3637,44 @@ function BookingCard({
2182
3637
  )
2183
3638
  ] }),
2184
3639
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "booking-card__weekdays", children: weekdays.map((label) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: label }, label)) }),
2185
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "booking-card__calendar", role: "grid", "aria-label": "Available dates", children: cells.map((cell, index) => {
2186
- if (!cell) {
2187
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "booking-card__day" }, `empty-${index}`);
3640
+ offer.slots.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__loading", role: "status", children: "Finding available times\u2026" }) : null,
3641
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3642
+ "div",
3643
+ {
3644
+ className: "booking-card__calendar",
3645
+ role: "grid",
3646
+ "aria-label": "Available dates",
3647
+ children: cells.map((cell, index) => {
3648
+ if (!cell) {
3649
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3650
+ "span",
3651
+ {
3652
+ className: "booking-card__day"
3653
+ },
3654
+ `empty-${index}`
3655
+ );
3656
+ }
3657
+ const available = availableByDate.has(cell.key);
3658
+ const selected = cell.key === selectedDate;
3659
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3660
+ "button",
3661
+ {
3662
+ type: "button",
3663
+ className: [
3664
+ "booking-card__day",
3665
+ available ? "booking-card__day--available" : "",
3666
+ selected ? "booking-card__day--selected" : ""
3667
+ ].filter(Boolean).join(" "),
3668
+ disabled: !available,
3669
+ "aria-pressed": selected,
3670
+ onClick: () => selectDate(cell.key),
3671
+ children: cell.day
3672
+ },
3673
+ cell.key
3674
+ );
3675
+ })
2188
3676
  }
2189
- const available = availableByDate.has(cell.key);
2190
- const selected = cell.key === selectedDate;
2191
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2192
- "button",
2193
- {
2194
- type: "button",
2195
- className: [
2196
- "booking-card__day",
2197
- available ? "booking-card__day--available" : "",
2198
- selected ? "booking-card__day--selected" : ""
2199
- ].filter(Boolean).join(" "),
2200
- disabled: !available,
2201
- "aria-pressed": selected,
2202
- onClick: () => selectDate(cell.key),
2203
- children: cell.day
2204
- },
2205
- cell.key
2206
- );
2207
- }) })
3677
+ )
2208
3678
  ] }, "date") : null,
2209
3679
  step === "time" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2210
3680
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step-bar", children: [
@@ -2256,35 +3726,57 @@ function BookingCard({
2256
3726
  ] })
2257
3727
  ] }),
2258
3728
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__identity", children: [
2259
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-name`, children: [
2260
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Name" }),
2261
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2262
- "input",
2263
- {
2264
- id: `${fieldId}-name`,
2265
- autoComplete: "name",
2266
- value: name,
2267
- onChange: (event) => setName(event.target.value),
2268
- required: true
2269
- }
2270
- )
2271
- ] }),
2272
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "booking-card__field", htmlFor: `${fieldId}-email`, children: [
2273
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Email" }),
2274
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2275
- "input",
2276
- {
2277
- id: `${fieldId}-email`,
2278
- type: "email",
2279
- autoComplete: "email",
2280
- value: email,
2281
- onChange: (event) => setEmail(event.target.value),
2282
- required: true
2283
- }
2284
- )
2285
- ] })
3729
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3730
+ "label",
3731
+ {
3732
+ className: "booking-card__field",
3733
+ htmlFor: `${fieldId}-name`,
3734
+ children: [
3735
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Name" }),
3736
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3737
+ "input",
3738
+ {
3739
+ id: `${fieldId}-name`,
3740
+ autoComplete: "name",
3741
+ value: name,
3742
+ onChange: (event) => setName(event.target.value),
3743
+ required: true
3744
+ }
3745
+ )
3746
+ ]
3747
+ }
3748
+ ),
3749
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3750
+ "label",
3751
+ {
3752
+ className: "booking-card__field",
3753
+ htmlFor: `${fieldId}-email`,
3754
+ children: [
3755
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Email" }),
3756
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3757
+ "input",
3758
+ {
3759
+ id: `${fieldId}-email`,
3760
+ type: "email",
3761
+ autoComplete: "email",
3762
+ value: email,
3763
+ onChange: (event) => setEmail(event.target.value),
3764
+ required: true
3765
+ }
3766
+ )
3767
+ ]
3768
+ }
3769
+ )
2286
3770
  ] }),
2287
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", className: "booking-card__submit", children: "Book this time" })
3771
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3772
+ "button",
3773
+ {
3774
+ type: "submit",
3775
+ className: "booking-card__submit",
3776
+ disabled: !name.trim() || !email.trim(),
3777
+ children: "Book this time"
3778
+ }
3779
+ )
2288
3780
  ] }, "details") : null
2289
3781
  ] }) });
2290
3782
  }
@@ -2293,6 +3785,43 @@ function BookingCard({
2293
3785
  var import_streamdown = require("streamdown");
2294
3786
  var import_styles = require("streamdown/styles.css");
2295
3787
  var import_jsx_runtime5 = require("react/jsx-runtime");
3788
+ function normalizeDedupeText(text) {
3789
+ return text.trim().replace(/\s+/g, " ").toLowerCase();
3790
+ }
3791
+ function paragraphsAreNearDuplicates(first, second) {
3792
+ const left = normalizeDedupeText(first);
3793
+ const right = normalizeDedupeText(second);
3794
+ if (left.length < 40 || right.length < 40) return false;
3795
+ if (left === right) return true;
3796
+ const shorter = left.length <= right.length ? left : right;
3797
+ const longer = left.length <= right.length ? right : left;
3798
+ return longer.startsWith(
3799
+ shorter.slice(0, Math.floor(shorter.length * 0.85))
3800
+ );
3801
+ }
3802
+ function paragraphsShareOpening(first, second) {
3803
+ const opening = first.split("\n")[0]?.trim();
3804
+ if (!opening || opening.length < 20) return false;
3805
+ return second.trim().startsWith(opening);
3806
+ }
3807
+ function collapseRepeatedText(text) {
3808
+ const trimmed = text.trim();
3809
+ if (trimmed.length < 40) return trimmed;
3810
+ const paragraphs = trimmed.split(/\n{2,}/u).map((part) => part.trim()).filter(Boolean);
3811
+ if (paragraphs.length === 2 && (paragraphsAreNearDuplicates(paragraphs[0], paragraphs[1]) || paragraphsShareOpening(paragraphs[0], paragraphs[1]))) {
3812
+ return paragraphs[0];
3813
+ }
3814
+ if (paragraphs.length >= 2 && paragraphs.length % 2 === 0) {
3815
+ const mid2 = paragraphs.length / 2;
3816
+ const first = paragraphs.slice(0, mid2).join("\n\n");
3817
+ const second = paragraphs.slice(mid2).join("\n\n");
3818
+ if (first === second) return first;
3819
+ }
3820
+ const mid = Math.floor(trimmed.length / 2);
3821
+ const left = trimmed.slice(0, mid).trim();
3822
+ const right = trimmed.slice(mid).trim();
3823
+ return left.length >= 20 && left === right ? left : trimmed;
3824
+ }
2296
3825
  function MessageBubble({
2297
3826
  message,
2298
3827
  brandLogoUrl,
@@ -2309,24 +3838,41 @@ function MessageBubble({
2309
3838
  const offers = offer ? [offer] : extractedOffers;
2310
3839
  const visibleText = hideToolCardFences(message.text);
2311
3840
  const isStreaming = message.role === "agent" && Boolean(message.streaming);
2312
- const displayText = offers.length > 0 ? visibleText || "Pick a date and time that works for you." : visibleText || (isStreaming ? "" : message.text);
3841
+ const displayText = collapseRepeatedText(
3842
+ offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
3843
+ );
2313
3844
  if (message.role === "visitor") {
2314
3845
  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 }) });
2315
3846
  }
2316
- const agentText = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "message-bubble__text", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2317
- import_streamdown.Streamdown,
2318
- {
2319
- animated: true,
2320
- caret: "circle",
2321
- className: "message-bubble__markdown",
2322
- controls: false,
2323
- isAnimating: isStreaming,
2324
- linkSafety: { enabled: false },
2325
- mode: isStreaming ? "streaming" : "static",
2326
- skipHtml: true,
2327
- children: displayText
2328
- }
2329
- ) });
3847
+ const citations = message.citations ?? [];
3848
+ const agentText = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "message-bubble__text", children: [
3849
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3850
+ import_streamdown.Streamdown,
3851
+ {
3852
+ animated: isStreaming,
3853
+ caret: "circle",
3854
+ className: "message-bubble__markdown",
3855
+ controls: false,
3856
+ isAnimating: isStreaming,
3857
+ linkSafety: { enabled: false },
3858
+ mode: isStreaming ? "streaming" : "static",
3859
+ skipHtml: true,
3860
+ children: displayText
3861
+ }
3862
+ ),
3863
+ 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)(
3864
+ "a",
3865
+ {
3866
+ href: citation.url,
3867
+ target: "_blank",
3868
+ rel: "noreferrer",
3869
+ children: [
3870
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "message-bubble__source-icon", "aria-hidden": "true", children: "\u25A6" }),
3871
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: citation.label })
3872
+ ]
3873
+ }
3874
+ ) }, citation.id)) }) : null
3875
+ ] });
2330
3876
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
2331
3877
  displayText ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "message-bubble__agent-row", children: [
2332
3878
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -2352,10 +3898,262 @@ function MessageBubble({
2352
3898
  ] });
2353
3899
  }
2354
3900
 
2355
- // src/react/components/AgentRail/AgentRail.tsx
3901
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3902
+ var import_react9 = require("react");
3903
+
3904
+ // src/react/components/ConfirmationCard/ConfirmationCard.tsx
2356
3905
  var import_jsx_runtime6 = require("react/jsx-runtime");
3906
+ function ConfirmationCard({
3907
+ disabled = false,
3908
+ request,
3909
+ onRespond
3910
+ }) {
3911
+ const options = request.options ?? [];
3912
+ const heading = request.kind === "tool-approval" ? "Confirm this action" : request.prompt;
3913
+ const prompt = request.kind === "tool-approval" ? request.prompt : void 0;
3914
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3915
+ "section",
3916
+ {
3917
+ className: "confirmation-card",
3918
+ "aria-labelledby": `confirmation-${request.requestId}`,
3919
+ children: [
3920
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "confirmation-card__heading", children: [
3921
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { id: `confirmation-${request.requestId}`, children: heading }),
3922
+ prompt ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: prompt }) : null
3923
+ ] }),
3924
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "confirmation-card__actions", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3925
+ "button",
3926
+ {
3927
+ type: "button",
3928
+ className: `confirmation-card__action confirmation-card__action--${option.style ?? "default"}`,
3929
+ disabled,
3930
+ onClick: () => onRespond?.({
3931
+ requestId: request.requestId,
3932
+ optionId: option.id
3933
+ }),
3934
+ children: option.label
3935
+ },
3936
+ option.id
3937
+ )) })
3938
+ ]
3939
+ }
3940
+ );
3941
+ }
3942
+
3943
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3944
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3945
+ function HumanInputCard({
3946
+ disabled = false,
3947
+ request,
3948
+ onRespond
3949
+ }) {
3950
+ const [text, setText] = (0, import_react9.useState)("");
3951
+ const options = request.options ?? [];
3952
+ const showText = request.display === "text" || request.allowFreeform && options.length === 0;
3953
+ if (options.length > 0) {
3954
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3955
+ ConfirmationCard,
3956
+ {
3957
+ disabled,
3958
+ request,
3959
+ onRespond
3960
+ }
3961
+ );
3962
+ }
3963
+ function submitText(event) {
3964
+ event.preventDefault();
3965
+ const value = text.trim();
3966
+ if (!value || disabled) return;
3967
+ onRespond?.({ requestId: request.requestId, text: value });
3968
+ }
3969
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3970
+ "section",
3971
+ {
3972
+ className: "human-input-card",
3973
+ "aria-labelledby": `human-input-${request.requestId}`,
3974
+ children: [
3975
+ /* @__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 }) }),
3976
+ showText ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("form", { onSubmit: submitText, children: [
3977
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { htmlFor: `human-input-text-${request.requestId}`, children: "Response" }),
3978
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
3979
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3980
+ "input",
3981
+ {
3982
+ id: `human-input-text-${request.requestId}`,
3983
+ value: text,
3984
+ disabled,
3985
+ onChange: (event) => setText(event.target.value)
3986
+ }
3987
+ ),
3988
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "submit", disabled: disabled || !text.trim(), children: "Send" })
3989
+ ] })
3990
+ ] }) : null,
3991
+ !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
3992
+ ]
3993
+ }
3994
+ );
3995
+ }
3996
+
3997
+ // src/react/components/CollectionResultCard/CollectionResultCard.tsx
3998
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3999
+ function CollectionResultCard({
4000
+ result
4001
+ }) {
4002
+ const empty = result.items.length === 0;
4003
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4004
+ "section",
4005
+ {
4006
+ className: `collection-result-card tool-result-card tool-result-card--${result.status}`,
4007
+ "aria-label": result.title,
4008
+ children: [
4009
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "tool-result-card__heading", children: [
4010
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4011
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: result.title })
4012
+ ] }),
4013
+ 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: [
4014
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "collection-result-card__item-title", children: item.title }),
4015
+ item.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: item.description }) : null,
4016
+ item.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dl", { children: item.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
4017
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dt", { children: detail.label }),
4018
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dd", { children: detail.value })
4019
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
4020
+ item.href ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { href: item.href, target: "_blank", rel: "noreferrer", children: "Open record" }) : null
4021
+ ] }, item.title)) })
4022
+ ]
4023
+ }
4024
+ );
4025
+ }
4026
+
4027
+ // src/react/components/EntityResultCard/EntityResultCard.tsx
4028
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4029
+ function EntityResultCard({
4030
+ result
4031
+ }) {
4032
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4033
+ "section",
4034
+ {
4035
+ className: `entity-result-card tool-result-card tool-result-card--${result.status}`,
4036
+ "aria-label": result.title,
4037
+ children: [
4038
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "tool-result-card__heading", children: [
4039
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4040
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: result.title })
4041
+ ] }),
4042
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: result.description }) : null,
4043
+ result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
4044
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dt", { children: detail.label }),
4045
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dd", { children: detail.value })
4046
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
4047
+ 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)(
4048
+ "a",
4049
+ {
4050
+ href: link.href,
4051
+ target: "_blank",
4052
+ rel: "noreferrer",
4053
+ children: link.label
4054
+ },
4055
+ link.href
4056
+ )) }) : null
4057
+ ]
4058
+ }
4059
+ );
4060
+ }
4061
+
4062
+ // src/react/components/SignatureResultCard/SignatureResultCard.tsx
4063
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4064
+ function SignatureResultCard({
4065
+ result
4066
+ }) {
4067
+ const primaryLink = result.links?.[0];
4068
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4069
+ "section",
4070
+ {
4071
+ className: `signature-result-card tool-result-card tool-result-card--${result.status}`,
4072
+ "aria-label": result.title,
4073
+ children: [
4074
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tool-result-card__heading", children: [
4075
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4076
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: result.title })
4077
+ ] }),
4078
+ result.statusLabel ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "signature-result-card__badge", children: result.statusLabel }) : null,
4079
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: result.description }) : null,
4080
+ primaryLink ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4081
+ "a",
4082
+ {
4083
+ className: "signature-result-card__cta",
4084
+ href: primaryLink.href,
4085
+ target: "_blank",
4086
+ rel: "noreferrer",
4087
+ children: primaryLink.label
4088
+ }
4089
+ ) : null
4090
+ ]
4091
+ }
4092
+ );
4093
+ }
4094
+
4095
+ // src/react/components/ToolResultCard/ToolResultCard.tsx
4096
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4097
+ function ToolResultCard({
4098
+ result
4099
+ }) {
4100
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4101
+ "section",
4102
+ {
4103
+ className: `tool-result-card tool-result-card--${result.status}`,
4104
+ "aria-label": result.title,
4105
+ children: [
4106
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "tool-result-card__heading", children: [
4107
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4108
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: result.title })
4109
+ ] }),
4110
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { children: result.description }) : null,
4111
+ result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
4112
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dt", { children: detail.label }),
4113
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dd", { children: detail.value })
4114
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
4115
+ 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)(
4116
+ "a",
4117
+ {
4118
+ href: link.href,
4119
+ target: "_blank",
4120
+ rel: "noreferrer",
4121
+ children: link.label
4122
+ },
4123
+ link.href
4124
+ )) }) : null
4125
+ ]
4126
+ }
4127
+ );
4128
+ }
4129
+
4130
+ // src/react/components/VisitorToolResultView/VisitorToolResultView.tsx
4131
+ var import_jsx_runtime12 = require("react/jsx-runtime");
4132
+ function VisitorToolResultView({
4133
+ result
4134
+ }) {
4135
+ if (result.kind === "entity") {
4136
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EntityResultCard, { result });
4137
+ }
4138
+ if (result.kind === "collection") {
4139
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CollectionResultCard, { result });
4140
+ }
4141
+ if (result.kind === "signature") {
4142
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SignatureResultCard, { result });
4143
+ }
4144
+ if (result.kind === "summary") {
4145
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToolResultCard, { result });
4146
+ }
4147
+ return null;
4148
+ }
4149
+ function isRenderableVisitorToolResult(result) {
4150
+ return result.kind === "summary" || result.kind === "entity" || result.kind === "collection" || result.kind === "signature";
4151
+ }
4152
+
4153
+ // src/react/components/AgentRail/AgentRail.tsx
4154
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2357
4155
  function MinimizeIcon() {
2358
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
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)(
2359
4157
  "path",
2360
4158
  {
2361
4159
  d: "M3.5 8h9",
@@ -2366,7 +4164,7 @@ function MinimizeIcon() {
2366
4164
  ) });
2367
4165
  }
2368
4166
  function CloseIcon() {
2369
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
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)(
2370
4168
  "path",
2371
4169
  {
2372
4170
  d: "M4 4l8 8M12 4l-8 8",
@@ -2377,7 +4175,7 @@ function CloseIcon() {
2377
4175
  ) });
2378
4176
  }
2379
4177
  function NewChatIcon() {
2380
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4178
+ 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)(
2381
4179
  "path",
2382
4180
  {
2383
4181
  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",
@@ -2389,7 +4187,7 @@ function NewChatIcon() {
2389
4187
  ) });
2390
4188
  }
2391
4189
  function ExpandIcon() {
2392
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4190
+ 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)(
2393
4191
  "path",
2394
4192
  {
2395
4193
  d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
@@ -2401,7 +4199,7 @@ function ExpandIcon() {
2401
4199
  ) });
2402
4200
  }
2403
4201
  function RestoreIcon() {
2404
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4202
+ 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)(
2405
4203
  "path",
2406
4204
  {
2407
4205
  d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
@@ -2429,28 +4227,29 @@ function AgentRail({
2429
4227
  onRetry,
2430
4228
  onSubmit,
2431
4229
  onFollowUpSelect,
2432
- onBook
4230
+ onBook,
4231
+ onInputResponse
2433
4232
  }) {
2434
- const transcriptRef = (0, import_react9.useRef)(null);
4233
+ const transcriptRef = (0, import_react10.useRef)(null);
2435
4234
  const resolvedBrandLabel = brandLabel.trim();
2436
4235
  const resolvedBrandLogoUrl = brandLogoUrl?.trim();
2437
- const [failedLogoUrl, setFailedLogoUrl] = (0, import_react9.useState)(null);
4236
+ const [failedLogoUrl, setFailedLogoUrl] = (0, import_react10.useState)(null);
2438
4237
  const showBrandLogo = Boolean(resolvedBrandLogoUrl) && failedLogoUrl !== resolvedBrandLogoUrl;
2439
4238
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2440
4239
  const brandedTheme = { ...defaultAgentRailTheme, ...theme };
2441
4240
  const resolvedTheme = resolvedColorScheme === "dark" ? {
2442
4241
  ...brandedTheme,
2443
4242
  brand: theme?.brand ?? defaultDarkAgentRailTheme.brand,
2444
- brandDeep: defaultDarkAgentRailTheme.brandDeep,
2445
- brandSoft: `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
2446
- border: defaultDarkAgentRailTheme.border,
2447
- danger: defaultDarkAgentRailTheme.danger,
2448
- success: defaultDarkAgentRailTheme.success,
2449
- surface: defaultDarkAgentRailTheme.surface,
2450
- surfaceMuted: defaultDarkAgentRailTheme.surfaceMuted,
2451
- text: defaultDarkAgentRailTheme.text,
2452
- textMuted: defaultDarkAgentRailTheme.textMuted,
2453
- textSubtle: defaultDarkAgentRailTheme.textSubtle,
4243
+ brandDeep: theme?.brandDeep ?? defaultDarkAgentRailTheme.brandDeep,
4244
+ brandSoft: theme?.brandSoft ?? `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
4245
+ border: theme?.border ?? defaultDarkAgentRailTheme.border,
4246
+ danger: theme?.danger ?? defaultDarkAgentRailTheme.danger,
4247
+ success: theme?.success ?? defaultDarkAgentRailTheme.success,
4248
+ surface: theme?.surface ?? defaultDarkAgentRailTheme.surface,
4249
+ surfaceMuted: theme?.surfaceMuted ?? defaultDarkAgentRailTheme.surfaceMuted,
4250
+ text: theme?.text ?? defaultDarkAgentRailTheme.text,
4251
+ textMuted: theme?.textMuted ?? defaultDarkAgentRailTheme.textMuted,
4252
+ textSubtle: theme?.textSubtle ?? defaultDarkAgentRailTheme.textSubtle,
2454
4253
  visitorBubble: theme?.visitorBubble ?? theme?.brand ?? defaultDarkAgentRailTheme.visitorBubble
2455
4254
  } : brandedTheme;
2456
4255
  const railStyle = {
@@ -2473,8 +4272,15 @@ function AgentRail({
2473
4272
  "--as-font-display": resolvedTheme.fontDisplay,
2474
4273
  colorScheme: resolvedColorScheme
2475
4274
  };
2476
- const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming";
4275
+ const pendingInputRequests = (state.pendingInputs ?? []).filter(
4276
+ (request) => shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
4277
+ );
4278
+ const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming" || state.phase === "waiting-input" && pendingInputRequests.length > 0;
4279
+ const semanticSurfaceDisabled = isBusy && state.phase !== "waiting-input";
2477
4280
  const showActivity = state.toolSteps.length > 0;
4281
+ const visitorToolResults = (state.toolResults ?? []).filter(
4282
+ isRenderableVisitorToolResult
4283
+ );
2478
4284
  const hasVisitorMessages2 = state.messages.some(
2479
4285
  (message) => message.role === "visitor"
2480
4286
  );
@@ -2484,22 +4290,44 @@ function AgentRail({
2484
4290
  );
2485
4291
  const visibleMessages = hasVisitorMessages2 ? state.messages : [];
2486
4292
  const lastMessage = visibleMessages.at(-1);
2487
- const completedAnswer = showActivity && state.phase === "complete" && lastMessage?.role === "agent" ? lastMessage : null;
2488
- const transcriptMessages = completedAnswer ? visibleMessages.slice(0, -1) : visibleMessages;
2489
- const streamingMessage = state.phase === "streaming" && state.streamingText ? {
4293
+ let lastAgentIndex = -1;
4294
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4295
+ if (visibleMessages[index]?.role === "agent") {
4296
+ lastAgentIndex = index;
4297
+ break;
4298
+ }
4299
+ }
4300
+ let lastVisitorIndex = -1;
4301
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4302
+ if (visibleMessages[index]?.role === "visitor") {
4303
+ lastVisitorIndex = index;
4304
+ break;
4305
+ }
4306
+ }
4307
+ const lastIsAgent = lastMessage?.role === "agent";
4308
+ const streamingMessage = state.phase === "streaming" && state.streamingText && !lastIsAgent ? {
2490
4309
  createdAt: 0,
2491
4310
  id: "streaming-response",
2492
4311
  role: "agent",
2493
4312
  streaming: true,
2494
4313
  text: state.streamingText
2495
- } : state.pendingOffer ? {
4314
+ } : state.pendingOffer && !lastIsAgent ? {
2496
4315
  createdAt: 0,
2497
4316
  id: "pending-booking",
2498
4317
  role: "agent",
2499
4318
  streaming: false,
2500
4319
  text: "Pick a date and time that works for you."
2501
4320
  } : null;
2502
- (0, import_react9.useEffect)(() => {
4321
+ const bookingReadyText = state.streamingText || (lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "");
4322
+ const waitingForBooking = !state.pendingOffer && looksLikeBookingReady(bookingReadyText) && (state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming");
4323
+ const lastAgentText = lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "";
4324
+ const composerForm = resolveComposerForm({
4325
+ agentText: lastAgentText,
4326
+ cards: extractToolCards(lastAgentText),
4327
+ hasBookingOffer: Boolean(state.pendingOffer) || waitingForBooking,
4328
+ enabled: lastIsAgent && !isBusy
4329
+ });
4330
+ (0, import_react10.useEffect)(() => {
2503
4331
  const node = transcriptRef.current;
2504
4332
  if (!node) return;
2505
4333
  node.scrollTop = node.scrollHeight;
@@ -2510,11 +4338,13 @@ function AgentRail({
2510
4338
  state.followUps,
2511
4339
  state.journey
2512
4340
  ]);
2513
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4341
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2514
4342
  "aside",
2515
4343
  {
2516
- className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4344
+ className: `agent-rail not-typeset${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4345
+ "data-not-typeset": "",
2517
4346
  "data-color-scheme": resolvedColorScheme,
4347
+ spellCheck: false,
2518
4348
  style: railStyle,
2519
4349
  "aria-label": "Agent conversation",
2520
4350
  "aria-modal": mobileFullscreen || expanded ? true : void 0,
@@ -2522,28 +4352,28 @@ function AgentRail({
2522
4352
  role: mobileFullscreen || expanded ? "dialog" : void 0,
2523
4353
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
2524
4354
  children: [
2525
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("header", { className: "agent-rail__header", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__brand-row", children: [
2526
- onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4355
+ /* @__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: [
4356
+ onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2527
4357
  "button",
2528
4358
  {
2529
4359
  type: "button",
2530
4360
  className: "agent-rail__collapse",
2531
4361
  "aria-label": "Collapse assist",
2532
4362
  onClick: onCollapse,
2533
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MinimizeIcon, {})
4363
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MinimizeIcon, {})
2534
4364
  }
2535
- ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4365
+ ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2536
4366
  "button",
2537
4367
  {
2538
4368
  type: "button",
2539
4369
  className: "agent-rail__close",
2540
4370
  "aria-label": "Close agent",
2541
4371
  onClick: onClose,
2542
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, {})
4372
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CloseIcon, {})
2543
4373
  }
2544
- ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
2545
- resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__identity", children: [
2546
- showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4374
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
4375
+ resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "agent-rail__identity", children: [
4376
+ showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2547
4377
  "img",
2548
4378
  {
2549
4379
  className: "agent-rail__brand-logo",
@@ -2554,10 +4384,10 @@ function AgentRail({
2554
4384
  }
2555
4385
  }
2556
4386
  ) }) : null,
2557
- resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
4387
+ resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
2558
4388
  ] }) : null,
2559
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__actions", children: [
2560
- onReset ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4389
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "agent-rail__actions", children: [
4390
+ onReset ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2561
4391
  "button",
2562
4392
  {
2563
4393
  type: "button",
@@ -2565,24 +4395,24 @@ function AgentRail({
2565
4395
  "aria-label": "Start a new conversation",
2566
4396
  disabled: !hasVisitorMessages2,
2567
4397
  onClick: onReset,
2568
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NewChatIcon, {})
4398
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(NewChatIcon, {})
2569
4399
  }
2570
4400
  ) : null,
2571
- onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4401
+ onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2572
4402
  "button",
2573
4403
  {
2574
4404
  type: "button",
2575
4405
  className: "agent-rail__expand",
2576
4406
  "aria-label": expanded ? "Exit full screen" : "Open full screen",
2577
4407
  onClick: onExpandToggle,
2578
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExpandIcon, {})
4408
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ExpandIcon, {})
2579
4409
  }
2580
4410
  ) : null
2581
4411
  ] })
2582
4412
  ] }) }),
2583
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__thread", children: [
2584
- !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
2585
- greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4413
+ /* @__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: [
4414
+ !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
4415
+ greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2586
4416
  MessageBubble,
2587
4417
  {
2588
4418
  message: greeting,
@@ -2590,7 +4420,7 @@ function AgentRail({
2590
4420
  onBook
2591
4421
  }
2592
4422
  ) : null,
2593
- showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4423
+ showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2594
4424
  FollowUpChips,
2595
4425
  {
2596
4426
  suggestions: state.followUps,
@@ -2598,64 +4428,94 @@ function AgentRail({
2598
4428
  label: "Start here",
2599
4429
  onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
2600
4430
  }
2601
- ) }) : null
4431
+ ) }) : null,
4432
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4433
+ AgentActivityBubble,
4434
+ {
4435
+ brandLabel: resolvedBrandLabel,
4436
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4437
+ failed: state.phase === "error",
4438
+ steps: state.toolSteps
4439
+ }
4440
+ ) : null,
4441
+ visitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VisitorToolResultView, { result }, result.id)),
4442
+ pendingInputRequests.map((request) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4443
+ HumanInputCard,
4444
+ {
4445
+ request,
4446
+ onRespond: onInputResponse
4447
+ },
4448
+ request.requestId
4449
+ ))
2602
4450
  ] }) : null,
2603
- transcriptMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2604
- MessageBubble,
2605
- {
2606
- message,
2607
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2608
- onBook
2609
- },
2610
- message.id
2611
- )),
2612
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2613
- AgentActivityBubble,
2614
- {
2615
- brandLabel: resolvedBrandLabel,
2616
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2617
- failed: state.phase === "error",
2618
- steps: state.toolSteps
2619
- }
2620
- ) : null,
2621
- completedAnswer ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4451
+ visibleMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "agent-rail__turn-block", children: [
4452
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4453
+ MessageBubble,
4454
+ {
4455
+ message,
4456
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4457
+ offer: index === lastAgentIndex ? state.pendingOffer : void 0,
4458
+ onBook
4459
+ }
4460
+ ),
4461
+ index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
4462
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4463
+ AgentActivityBubble,
4464
+ {
4465
+ brandLabel: resolvedBrandLabel,
4466
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4467
+ failed: state.phase === "error",
4468
+ steps: state.toolSteps
4469
+ }
4470
+ ) : null,
4471
+ visitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VisitorToolResultView, { result }, result.id)),
4472
+ pendingInputRequests.map((request) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4473
+ HumanInputCard,
4474
+ {
4475
+ request,
4476
+ onRespond: onInputResponse
4477
+ },
4478
+ request.requestId
4479
+ ))
4480
+ ] }) : null
4481
+ ] }, message.id)),
4482
+ streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2622
4483
  MessageBubble,
2623
4484
  {
2624
- message: completedAnswer,
4485
+ message: streamingMessage,
2625
4486
  brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4487
+ offer: state.pendingOffer,
2626
4488
  onBook
2627
4489
  }
2628
4490
  ) : null,
2629
- streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2630
- MessageBubble,
4491
+ waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4492
+ BookingCard,
2631
4493
  {
2632
- message: streamingMessage,
2633
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2634
- offer: state.pendingOffer,
2635
- onBook
4494
+ offer: { type: "booking_offer", eventTypes: [], slots: [] }
2636
4495
  }
2637
4496
  ) : null,
2638
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
2639
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
2640
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Something went wrong" }),
2641
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: state.error })
4497
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
4498
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
4499
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: "Something went wrong" }),
4500
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: state.error })
2642
4501
  ] }),
2643
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
4502
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
2644
4503
  ] }) : null
2645
4504
  ] }) }),
2646
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
2647
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4505
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
4506
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2648
4507
  Composer,
2649
4508
  {
2650
4509
  variant: expanded || mobileFullscreen ? "dock" : "default",
2651
4510
  disabled: isBusy,
4511
+ form: composerForm,
2652
4512
  placeholder: composerPlaceholder,
2653
4513
  onSubmit
2654
4514
  }
2655
4515
  ),
2656
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
2657
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "AI can make mistakes. Check important info." }),
2658
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: poweredByLabel })
4516
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { children: [
4517
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "AI can make mistakes. Check important info." }),
4518
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: poweredByLabel })
2659
4519
  ] }) })
2660
4520
  ] })
2661
4521
  ]
@@ -2664,9 +4524,9 @@ function AgentRail({
2664
4524
  }
2665
4525
 
2666
4526
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
2667
- var import_jsx_runtime7 = require("react/jsx-runtime");
4527
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2668
4528
  function SparklesIcon() {
2669
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4529
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2670
4530
  "svg",
2671
4531
  {
2672
4532
  className: "assist-edge-tab__sparkles",
@@ -2674,21 +4534,21 @@ function SparklesIcon() {
2674
4534
  fill: "none",
2675
4535
  "aria-hidden": "true",
2676
4536
  children: [
2677
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4537
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2678
4538
  "path",
2679
4539
  {
2680
4540
  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",
2681
4541
  fill: "currentColor"
2682
4542
  }
2683
4543
  ),
2684
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4544
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2685
4545
  "path",
2686
4546
  {
2687
4547
  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",
2688
4548
  fill: "currentColor"
2689
4549
  }
2690
4550
  ),
2691
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4551
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2692
4552
  "path",
2693
4553
  {
2694
4554
  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",
@@ -2702,7 +4562,7 @@ function SparklesIcon() {
2702
4562
  function TabMarkIcon({ customIconUrl }) {
2703
4563
  const url = customIconUrl?.trim();
2704
4564
  if (url) {
2705
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4565
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2706
4566
  "img",
2707
4567
  {
2708
4568
  alt: "",
@@ -2712,10 +4572,10 @@ function TabMarkIcon({ customIconUrl }) {
2712
4572
  }
2713
4573
  );
2714
4574
  }
2715
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparklesIcon, {});
4575
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SparklesIcon, {});
2716
4576
  }
2717
4577
  function ChevronLeftIcon() {
2718
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4578
+ 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)(
2719
4579
  "path",
2720
4580
  {
2721
4581
  d: "M10 4L6 8l4 4",
@@ -2727,7 +4587,7 @@ function ChevronLeftIcon() {
2727
4587
  ) });
2728
4588
  }
2729
4589
  function ChevronDownIcon() {
2730
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4590
+ 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)(
2731
4591
  "path",
2732
4592
  {
2733
4593
  d: "M4 6l4 4 4-4",
@@ -2739,7 +4599,7 @@ function ChevronDownIcon() {
2739
4599
  ) });
2740
4600
  }
2741
4601
  function DragDots() {
2742
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", {}, index)) });
4602
+ 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)) });
2743
4603
  }
2744
4604
  var VARIANT_COPY = {
2745
4605
  outline: { label: "Ask anything", aria: "Ask anything" },
@@ -2768,6 +4628,7 @@ function AssistEdgeTab({
2768
4628
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2769
4629
  const copy = VARIANT_COPY[variant];
2770
4630
  const visibleLabel = label?.trim() || copy.label;
4631
+ const alignment = along < 50 ? "start" : along > 50 ? "end" : "center";
2771
4632
  const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
2772
4633
  const resolvedBrandColor = brandColor ?? (resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
2773
4634
  const resolvedBorderColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.border : borderColor;
@@ -2784,11 +4645,11 @@ function AssistEdgeTab({
2784
4645
  ...resolvedTextColor ? { "--as-text": resolvedTextColor } : {},
2785
4646
  colorScheme: resolvedColorScheme
2786
4647
  };
2787
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4648
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2788
4649
  "button",
2789
4650
  {
2790
4651
  type: "button",
2791
- className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
4652
+ 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" : ""}`,
2792
4653
  "data-color-scheme": resolvedColorScheme,
2793
4654
  style,
2794
4655
  "aria-label": `Open ${visibleLabel}`,
@@ -2796,15 +4657,15 @@ function AssistEdgeTab({
2796
4657
  tabIndex: visible ? 0 : -1,
2797
4658
  onClick: onOpen,
2798
4659
  children: [
2799
- mobile ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2800
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4660
+ mobile ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4661
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2801
4662
  "span",
2802
4663
  {
2803
4664
  className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
2804
4665
  "aria-hidden": "true",
2805
4666
  children: [
2806
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2807
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4667
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TabMarkIcon, { customIconUrl }),
4668
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2808
4669
  "img",
2809
4670
  {
2810
4671
  className: "assist-edge-tab__logo",
@@ -2818,11 +4679,11 @@ function AssistEdgeTab({
2818
4679
  ]
2819
4680
  }
2820
4681
  ),
2821
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel })
2822
- ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2823
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2824
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2825
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4682
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel })
4683
+ ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4684
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4685
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TabMarkIcon, { customIconUrl }),
4686
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2826
4687
  "img",
2827
4688
  {
2828
4689
  className: "assist-edge-tab__logo",
@@ -2834,18 +4695,18 @@ function AssistEdgeTab({
2834
4695
  }
2835
4696
  ) : null
2836
4697
  ] }),
2837
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2838
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {})
4698
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4699
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronDownIcon, {})
2839
4700
  ] }) : null,
2840
- variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2841
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {}),
2842
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2843
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DragDots, {})
4701
+ variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4702
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronLeftIcon, {}),
4703
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4704
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DragDots, {})
2844
4705
  ] }) : null,
2845
- variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2846
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2847
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2848
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4706
+ variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4707
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4708
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TabMarkIcon, { customIconUrl }),
4709
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2849
4710
  "img",
2850
4711
  {
2851
4712
  className: "assist-edge-tab__logo",
@@ -2857,8 +4718,8 @@ function AssistEdgeTab({
2857
4718
  }
2858
4719
  ) : null
2859
4720
  ] }),
2860
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2861
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {})
4721
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4722
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronLeftIcon, {})
2862
4723
  ] }) : null
2863
4724
  ]
2864
4725
  }
@@ -2866,7 +4727,7 @@ function AssistEdgeTab({
2866
4727
  }
2867
4728
 
2868
4729
  // src/react/components/AgentWidget/AgentWidget.tsx
2869
- var import_jsx_runtime8 = require("react/jsx-runtime");
4730
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2870
4731
  function AgentWidget({
2871
4732
  indexId,
2872
4733
  customerId,
@@ -2879,13 +4740,14 @@ function AgentWidget({
2879
4740
  pageShift = true,
2880
4741
  registerPanelController = false,
2881
4742
  colorScheme = "auto",
2882
- branding
4743
+ branding,
4744
+ toolResultRegistry
2883
4745
  }) {
2884
4746
  const isMobile = useIsMobile();
2885
4747
  const placement = normalizeAgentPlacement(placementInput);
2886
- const railSlotRef = (0, import_react10.useRef)(null);
2887
- const [railCollapsed, setRailCollapsed] = (0, import_react10.useState)(defaultCollapsed);
2888
- const [railExpanded, setRailExpanded] = (0, import_react10.useState)(false);
4748
+ const railSlotRef = (0, import_react11.useRef)(null);
4749
+ const [railCollapsed, setRailCollapsed] = (0, import_react11.useState)(defaultCollapsed);
4750
+ const [railExpanded, setRailExpanded] = (0, import_react11.useState)(false);
2889
4751
  const pageShiftActive = shouldApplyPageShift({
2890
4752
  pageShift,
2891
4753
  isMobile,
@@ -2896,14 +4758,15 @@ function AgentWidget({
2896
4758
  active: pageShiftActive,
2897
4759
  railSlotRef
2898
4760
  });
2899
- const { state, reset, retry, submit } = useAgentChat({
4761
+ const { state, reset, retry, respondToInput, respondToToolInput, submit } = useAgentChat({
2900
4762
  customerId,
2901
4763
  getUnpublishedPreviewGrant,
2902
4764
  indexId,
2903
4765
  previewBuildId,
2904
4766
  version,
2905
4767
  runtimeOrigin,
2906
- greeting: branding?.greeting
4768
+ greeting: branding?.greeting,
4769
+ toolResultRegistry
2907
4770
  });
2908
4771
  const agentName = branding?.agentName ?? "";
2909
4772
  const tabLabel = branding?.tabLabel ?? agentName;
@@ -2924,22 +4787,24 @@ function AgentWidget({
2924
4787
  } : {},
2925
4788
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
2926
4789
  };
2927
- (0, import_react10.useEffect)(() => {
4790
+ (0, import_react11.useEffect)(() => {
2928
4791
  if (!registerPanelController) return;
2929
4792
  registerAgentPanelController(customerId, {
2930
4793
  open: () => setRailCollapsed(false),
2931
4794
  close: () => {
2932
4795
  setRailCollapsed(true);
2933
4796
  setRailExpanded(false);
2934
- }
4797
+ },
4798
+ reset,
4799
+ submit
2935
4800
  });
2936
4801
  return () => unregisterAgentPanelController(customerId);
2937
- }, [customerId, registerPanelController]);
4802
+ }, [customerId, registerPanelController, reset, submit]);
2938
4803
  async function handleSubmit(message) {
2939
4804
  if (isMobile) setRailCollapsed(false);
2940
4805
  await submit(message);
2941
4806
  }
2942
- (0, import_react10.useEffect)(() => {
4807
+ (0, import_react11.useEffect)(() => {
2943
4808
  if (railCollapsed) return;
2944
4809
  const handleKeyDown = (event) => {
2945
4810
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -2973,19 +4838,19 @@ function AgentWidget({
2973
4838
  window.addEventListener("keydown", handleKeyDown);
2974
4839
  return () => window.removeEventListener("keydown", handleKeyDown);
2975
4840
  }, [isMobile, railCollapsed, railExpanded]);
2976
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "webless-agent-root", children: [
2977
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4841
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "webless-agent-root", children: [
4842
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2978
4843
  "div",
2979
4844
  {
2980
4845
  className: `webless-agent-root__shell${railCollapsed ? " webless-agent-root__shell--collapsed" : ""}${railExpanded ? " webless-agent-root__shell--expanded" : ""}`,
2981
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4846
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2982
4847
  "div",
2983
4848
  {
2984
4849
  ref: railSlotRef,
2985
4850
  className: "webless-agent-root__rail-slot",
2986
4851
  inert: railCollapsed || void 0,
2987
4852
  "aria-hidden": railCollapsed,
2988
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4853
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2989
4854
  AgentRail,
2990
4855
  {
2991
4856
  theme,
@@ -3003,6 +4868,8 @@ function AgentWidget({
3003
4868
  onSubmit: handleSubmit,
3004
4869
  onReset: reset,
3005
4870
  onRetry: () => void retry(),
4871
+ onInputResponse: (response) => void respondToInput(response),
4872
+ onToolInput: (surface, values) => void respondToToolInput(surface, values),
3006
4873
  onFollowUpSelect: (label) => void handleSubmit(label),
3007
4874
  onBook: (input) => void submit(input.displayText, { runtimeText: input.runtimeText })
3008
4875
  }
@@ -3011,7 +4878,7 @@ function AgentWidget({
3011
4878
  )
3012
4879
  }
3013
4880
  ),
3014
- railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4881
+ railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3015
4882
  AssistEdgeTab,
3016
4883
  {
3017
4884
  variant: placement.variant,
@@ -3050,12 +4917,12 @@ function readUnpublishedPreviewBuildId(href) {
3050
4917
  }
3051
4918
 
3052
4919
  // src/embed/AgentWidget.tsx
3053
- var import_jsx_runtime9 = require("react/jsx-runtime");
4920
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3054
4921
  function AgentWidget2({
3055
4922
  manifest
3056
4923
  }) {
3057
4924
  const defaultCollapsed = manifest.version !== "unpublished";
3058
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4925
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3059
4926
  AgentWidget,
3060
4927
  {
3061
4928
  indexId: manifest.indexId,
@@ -3139,7 +5006,7 @@ function normalizeAgentBranding(branding) {
3139
5006
  }
3140
5007
 
3141
5008
  // src/embed/mount.tsx
3142
- var import_jsx_runtime10 = require("react/jsx-runtime");
5009
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3143
5010
  var mountedHandles = /* @__PURE__ */ new Map();
3144
5011
  var latestCustomerId = null;
3145
5012
  function resolveMountHost(manifest, script) {
@@ -3169,7 +5036,7 @@ function mountAgent(input) {
3169
5036
  const host = createHost(manifest.customerId);
3170
5037
  mountTarget.append(host);
3171
5038
  const root = (0, import_client5.createRoot)(host);
3172
- root.render(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AgentWidget2, { manifest }));
5039
+ root.render(/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(AgentWidget2, { manifest }));
3173
5040
  const handle = {
3174
5041
  customerId: manifest.customerId,
3175
5042
  manifest,
@@ -3262,6 +5129,8 @@ function createWeblessAgentPublicApi(manifest) {
3262
5129
  mountAgent,
3263
5130
  normalizeAgentPlacement,
3264
5131
  normalizeAgentTagManifest,
5132
+ resetAgentPanel,
5133
+ submitAgentPanel,
3265
5134
  unmountAgent
3266
5135
  });
3267
5136
  //# sourceMappingURL=embed.cjs.map