@webless/agent 0.6.2 → 0.6.4

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
  );
@@ -1869,7 +3156,7 @@ function AgentActivityBubble({
1869
3156
  workSummary(steps, failed, brandLabel)
1870
3157
  ] }),
1871
3158
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "agent-activity-bubble__details", children: [
1872
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1873
3160
  "button",
1874
3161
  {
1875
3162
  type: "button",
@@ -1881,15 +3168,19 @@ function AgentActivityBubble({
1881
3168
  (current) => current === receiptId ? null : receiptId
1882
3169
  );
1883
3170
  },
1884
- children: "How this answer was made"
3171
+ children: [
3172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__summary-title", children: "How this answer was made" }),
3173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__summary-toggle", children: detailsOpen ? "Hide work" : "Show work" })
3174
+ ]
1885
3175
  }
1886
3176
  ),
1887
3177
  detailsOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "agent-activity-bubble__steps", children: visibleSteps.map((step) => {
1888
3178
  const detail = stepDetail(step, steps);
3179
+ const child = delegated && step.kind === "specialist";
1889
3180
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1890
3181
  "li",
1891
3182
  {
1892
- className: "agent-activity-bubble__step",
3183
+ className: `agent-activity-bubble__step${child ? " agent-activity-bubble__step--child" : ""}`,
1893
3184
  "data-kind": step.kind,
1894
3185
  "data-state": step.state,
1895
3186
  children: [
@@ -1897,25 +3188,27 @@ function AgentActivityBubble({
1897
3188
  "span",
1898
3189
  {
1899
3190
  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()
3191
+ "aria-hidden": "true"
1914
3192
  }
1915
3193
  ),
1916
3194
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__step-copy", children: [
1917
3195
  /* @__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
3196
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null,
3197
+ delegated && step.kind === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "agent-activity-bubble__delegation", children: [
3198
+ "Delegated ",
3199
+ delegationCount,
3200
+ " ",
3201
+ delegationCount === 1 ? "task" : "tasks"
3202
+ ] }) : null,
3203
+ step.state === "error" && onRetryStep ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3204
+ "button",
3205
+ {
3206
+ type: "button",
3207
+ className: "agent-activity-bubble__step-retry",
3208
+ onClick: () => onRetryStep(step),
3209
+ children: "Retry"
3210
+ }
3211
+ ) : null
1919
3212
  ] })
1920
3213
  ]
1921
3214
  },
@@ -1930,59 +3223,190 @@ function AgentActivityBubble({
1930
3223
  var import_react6 = require("react");
1931
3224
  var import_jsx_runtime2 = require("react/jsx-runtime");
1932
3225
  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" }) });
3226
+ 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)(
3227
+ "path",
3228
+ {
3229
+ d: "M8 12V4M8 4l-3 3M8 4l3 3",
3230
+ stroke: "currentColor",
3231
+ strokeWidth: "1.5",
3232
+ strokeLinecap: "round",
3233
+ strokeLinejoin: "round"
3234
+ }
3235
+ ) });
3236
+ }
3237
+ function emptyValues(form) {
3238
+ const values = {};
3239
+ for (const field of form?.fields ?? []) values[field.id] = "";
3240
+ return values;
1934
3241
  }
1935
3242
  function Composer({
1936
3243
  disabled = false,
1937
3244
  placeholder = "Ask anything\u2026",
1938
3245
  variant = "default",
3246
+ form = null,
1939
3247
  onSubmit
1940
3248
  }) {
1941
3249
  const [value, setValue] = (0, import_react6.useState)("");
3250
+ const [values, setValues] = (0, import_react6.useState)(
3251
+ () => emptyValues(form)
3252
+ );
3253
+ const [blurred, setBlurred] = (0, import_react6.useState)({});
1942
3254
  const inputRef = (0, import_react6.useRef)(null);
1943
- function submitCurrent() {
3255
+ const firstFieldRef = (0, import_react6.useRef)(null);
3256
+ const formId = (0, import_react6.useId)();
3257
+ const activeForm = form;
3258
+ const canSendForm = activeForm ? isComposerFormComplete(activeForm, values) : Boolean(value.trim());
3259
+ (0, import_react6.useEffect)(() => {
3260
+ setValues(emptyValues(form));
3261
+ setBlurred({});
3262
+ }, [form?.id]);
3263
+ (0, import_react6.useEffect)(() => {
3264
+ if (activeForm) firstFieldRef.current?.focus();
3265
+ }, [activeForm?.id]);
3266
+ function submitChat() {
1944
3267
  const trimmed = value.trim();
1945
3268
  if (!trimmed || disabled) return;
1946
3269
  onSubmit?.(trimmed);
1947
3270
  setValue("");
1948
3271
  inputRef.current?.focus();
1949
3272
  }
3273
+ function submitForm() {
3274
+ if (!activeForm || disabled || !canSendForm) return;
3275
+ onSubmit?.(formatComposerFormMessage(activeForm, values));
3276
+ setValues(emptyValues(activeForm));
3277
+ setBlurred({});
3278
+ }
1950
3279
  function handleSubmit(event) {
1951
3280
  event.preventDefault();
1952
- submitCurrent();
3281
+ if (activeForm) submitForm();
3282
+ else submitChat();
1953
3283
  }
1954
- function handleKeyDown(event) {
3284
+ function handleChatKeyDown(event) {
1955
3285
  if (event.key === "Enter" && !event.shiftKey) {
1956
3286
  event.preventDefault();
1957
- submitCurrent();
3287
+ submitChat();
1958
3288
  }
1959
3289
  }
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
- ] }) });
3290
+ function handleFormKeyDown(event) {
3291
+ const target = event.target;
3292
+ const isTextarea = Boolean(target && "tagName" in target && target.tagName === "TEXTAREA");
3293
+ if (event.key === "Enter" && !event.shiftKey && !isTextarea) {
3294
+ event.preventDefault();
3295
+ submitForm();
3296
+ }
3297
+ }
3298
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3299
+ "form",
3300
+ {
3301
+ className: [
3302
+ "composer",
3303
+ variant === "dock" ? "composer--dock" : "",
3304
+ activeForm ? "composer--form" : ""
3305
+ ].filter(Boolean).join(" "),
3306
+ onSubmit: handleSubmit,
3307
+ children: activeForm ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "composer__sheet", role: "group", "aria-label": "Required details", children: [
3308
+ activeForm.fields.map((field, index) => {
3309
+ const fieldId = `${formId}-${field.id}`;
3310
+ const invalid = Boolean(blurred[field.id]) && !isValidComposerFieldValue(field, values[field.id] ?? "");
3311
+ const controlProps = {
3312
+ id: fieldId,
3313
+ name: field.id,
3314
+ disabled,
3315
+ required: field.required,
3316
+ autoComplete: field.autocomplete,
3317
+ placeholder: field.placeholder,
3318
+ spellCheck: false,
3319
+ value: values[field.id] ?? "",
3320
+ "aria-invalid": invalid || void 0,
3321
+ "aria-describedby": invalid ? `${fieldId}-error` : void 0,
3322
+ onBlur: () => setBlurred((current) => ({ ...current, [field.id]: true })),
3323
+ onChange: (event) => {
3324
+ const next = readComposerControlValue(event);
3325
+ setValues((current) => ({
3326
+ ...current,
3327
+ [field.id]: next
3328
+ }));
3329
+ },
3330
+ onKeyDown: handleFormKeyDown
3331
+ };
3332
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3333
+ "div",
3334
+ {
3335
+ className: [
3336
+ "composer__row",
3337
+ field.kind === "textarea" ? "composer__row--grow" : ""
3338
+ ].filter(Boolean).join(" "),
3339
+ children: [
3340
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "composer__label", htmlFor: fieldId, children: [
3341
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "composer__sr-only", children: field.label }),
3342
+ field.kind === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3343
+ "textarea",
3344
+ {
3345
+ ...controlProps,
3346
+ ref: index === 0 ? (node) => {
3347
+ firstFieldRef.current = node;
3348
+ } : void 0,
3349
+ className: "composer__control composer__control--area",
3350
+ rows: 3
3351
+ }
3352
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3353
+ "input",
3354
+ {
3355
+ ...controlProps,
3356
+ ref: index === 0 ? (node) => {
3357
+ firstFieldRef.current = node;
3358
+ } : void 0,
3359
+ className: "composer__control",
3360
+ type: field.kind,
3361
+ inputMode: field.kind === "tel" ? "tel" : void 0
3362
+ }
3363
+ )
3364
+ ] }),
3365
+ 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
3366
+ ]
3367
+ },
3368
+ field.id
3369
+ );
3370
+ }),
3371
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "composer__toolbar", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3372
+ "button",
3373
+ {
3374
+ type: "submit",
3375
+ className: "composer__send",
3376
+ disabled: disabled || !canSendForm,
3377
+ "aria-label": "Send details",
3378
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
3379
+ }
3380
+ ) })
3381
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "composer__field", children: [
3382
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3383
+ "textarea",
3384
+ {
3385
+ ref: inputRef,
3386
+ className: "composer__input",
3387
+ rows: 1,
3388
+ value,
3389
+ placeholder,
3390
+ disabled,
3391
+ spellCheck: false,
3392
+ "aria-label": "Message",
3393
+ onChange: (event) => setValue(event.target.value),
3394
+ onKeyDown: handleChatKeyDown
3395
+ }
3396
+ ),
3397
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3398
+ "button",
3399
+ {
3400
+ type: "submit",
3401
+ className: "composer__send",
3402
+ disabled: disabled || !value.trim(),
3403
+ "aria-label": "Send message",
3404
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
3405
+ }
3406
+ )
3407
+ ] })
3408
+ }
3409
+ );
1986
3410
  }
1987
3411
 
1988
3412
  // src/react/components/FollowUpChips/FollowUpChips.tsx
@@ -2030,9 +3454,15 @@ var import_react8 = require("react");
2030
3454
  // src/react/components/BookingCard/BookingCard.tsx
2031
3455
  var import_react7 = require("react");
2032
3456
  var import_jsx_runtime4 = require("react/jsx-runtime");
3457
+ var BOOKING_STEPS = [
3458
+ { id: "date", label: "Date" },
3459
+ { id: "time", label: "Time" },
3460
+ { id: "details", label: "Details" }
3461
+ ];
2033
3462
  function monthFromKey(key) {
2034
3463
  const [year, month] = key.split("-").map(Number);
2035
- if (!year || !month) return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
3464
+ if (!year || !month)
3465
+ return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
2036
3466
  return { year, month: month - 1 };
2037
3467
  }
2038
3468
  function dateKeyFromParts(year, month, day) {
@@ -2065,6 +3495,7 @@ function BookingCard({
2065
3495
  const [startTime, setStartTime] = (0, import_react7.useState)("");
2066
3496
  const [name, setName] = (0, import_react7.useState)("");
2067
3497
  const [email, setEmail] = (0, import_react7.useState)("");
3498
+ const stepIndex = BOOKING_STEPS.findIndex((item) => item.id === step);
2068
3499
  const slots = (0, import_react7.useMemo)(
2069
3500
  () => bookingSlotsForEventType(offer.slots, eventTypeUri),
2070
3501
  [eventTypeUri, offer.slots]
@@ -2085,14 +3516,18 @@ function BookingCard({
2085
3516
  setSelectedDate("");
2086
3517
  setStartTime("");
2087
3518
  setVisibleMonth(
2088
- firstAvailableBookingMonth(bookingSlotsForEventType(offer.slots, nextType))
3519
+ firstAvailableBookingMonth(
3520
+ bookingSlotsForEventType(offer.slots, nextType)
3521
+ )
2089
3522
  );
2090
3523
  }
2091
3524
  const daySlots = (0, import_react7.useMemo)(
2092
3525
  () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
2093
3526
  [selectedDate, slots]
2094
3527
  );
2095
- const selectedType = offer.eventTypes.find((item) => item.uri === eventTypeUri);
3528
+ const selectedType = offer.eventTypes.find(
3529
+ (item) => item.uri === eventTypeUri
3530
+ );
2096
3531
  const selectedSample = availableByDate.get(selectedDate) ?? startTime;
2097
3532
  const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
2098
3533
  const weekdays = (0, import_react7.useMemo)(() => weekdayLabels(), []);
@@ -2138,24 +3573,47 @@ function BookingCard({
2138
3573
  });
2139
3574
  }
2140
3575
  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: [
3576
+ /* @__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)(
3577
+ "li",
3578
+ {
3579
+ className: [
3580
+ "booking-card__step-indicator",
3581
+ index === stepIndex ? "booking-card__step-indicator--active" : "",
3582
+ index < stepIndex ? "booking-card__step-indicator--complete" : ""
3583
+ ].filter(Boolean).join(" "),
3584
+ "aria-current": index === stepIndex ? "step" : void 0,
3585
+ children: [
3586
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", children: index + 1 }),
3587
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: item.label })
3588
+ ]
3589
+ },
3590
+ item.id
3591
+ )) }),
2141
3592
  step === "date" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2142
3593
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__title", children: selectedType?.name || "Pick a date" }),
2143
3594
  timeZone ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "booking-card__tz", children: [
2144
3595
  "Times in ",
2145
3596
  timeZone
2146
3597
  ] }) : 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,
3598
+ offer.eventTypes.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3599
+ "label",
3600
+ {
3601
+ className: "booking-card__field",
3602
+ htmlFor: `${fieldId}-type`,
3603
+ children: [
3604
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Meeting" }),
3605
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3606
+ "select",
3607
+ {
3608
+ id: `${fieldId}-type`,
3609
+ value: eventTypeUri,
3610
+ onChange: (event) => selectEventType(event.target.value),
3611
+ children: offer.eventTypes.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: item.uri, children: item.name }, item.uri))
3612
+ }
3613
+ )
3614
+ ]
3615
+ }
3616
+ ) : null,
2159
3617
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__month", children: [
2160
3618
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2161
3619
  "button",
@@ -2182,29 +3640,44 @@ function BookingCard({
2182
3640
  )
2183
3641
  ] }),
2184
3642
  /* @__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}`);
3643
+ offer.slots.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "booking-card__loading", role: "status", children: "Finding available times\u2026" }) : null,
3644
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3645
+ "div",
3646
+ {
3647
+ className: "booking-card__calendar",
3648
+ role: "grid",
3649
+ "aria-label": "Available dates",
3650
+ children: cells.map((cell, index) => {
3651
+ if (!cell) {
3652
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3653
+ "span",
3654
+ {
3655
+ className: "booking-card__day"
3656
+ },
3657
+ `empty-${index}`
3658
+ );
3659
+ }
3660
+ const available = availableByDate.has(cell.key);
3661
+ const selected = cell.key === selectedDate;
3662
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3663
+ "button",
3664
+ {
3665
+ type: "button",
3666
+ className: [
3667
+ "booking-card__day",
3668
+ available ? "booking-card__day--available" : "",
3669
+ selected ? "booking-card__day--selected" : ""
3670
+ ].filter(Boolean).join(" "),
3671
+ disabled: !available,
3672
+ "aria-pressed": selected,
3673
+ onClick: () => selectDate(cell.key),
3674
+ children: cell.day
3675
+ },
3676
+ cell.key
3677
+ );
3678
+ })
2188
3679
  }
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
- }) })
3680
+ )
2208
3681
  ] }, "date") : null,
2209
3682
  step === "time" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step", children: [
2210
3683
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "booking-card__step-bar", children: [
@@ -2256,35 +3729,57 @@ function BookingCard({
2256
3729
  ] })
2257
3730
  ] }),
2258
3731
  /* @__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
- ] })
3732
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3733
+ "label",
3734
+ {
3735
+ className: "booking-card__field",
3736
+ htmlFor: `${fieldId}-name`,
3737
+ children: [
3738
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Name" }),
3739
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3740
+ "input",
3741
+ {
3742
+ id: `${fieldId}-name`,
3743
+ autoComplete: "name",
3744
+ value: name,
3745
+ onChange: (event) => setName(event.target.value),
3746
+ required: true
3747
+ }
3748
+ )
3749
+ ]
3750
+ }
3751
+ ),
3752
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3753
+ "label",
3754
+ {
3755
+ className: "booking-card__field",
3756
+ htmlFor: `${fieldId}-email`,
3757
+ children: [
3758
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Email" }),
3759
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3760
+ "input",
3761
+ {
3762
+ id: `${fieldId}-email`,
3763
+ type: "email",
3764
+ autoComplete: "email",
3765
+ value: email,
3766
+ onChange: (event) => setEmail(event.target.value),
3767
+ required: true
3768
+ }
3769
+ )
3770
+ ]
3771
+ }
3772
+ )
2286
3773
  ] }),
2287
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", className: "booking-card__submit", children: "Book this time" })
3774
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3775
+ "button",
3776
+ {
3777
+ type: "submit",
3778
+ className: "booking-card__submit",
3779
+ disabled: !name.trim() || !email.trim(),
3780
+ children: "Book this time"
3781
+ }
3782
+ )
2288
3783
  ] }, "details") : null
2289
3784
  ] }) });
2290
3785
  }
@@ -2293,6 +3788,43 @@ function BookingCard({
2293
3788
  var import_streamdown = require("streamdown");
2294
3789
  var import_styles = require("streamdown/styles.css");
2295
3790
  var import_jsx_runtime5 = require("react/jsx-runtime");
3791
+ function normalizeDedupeText(text) {
3792
+ return text.trim().replace(/\s+/g, " ").toLowerCase();
3793
+ }
3794
+ function paragraphsAreNearDuplicates(first, second) {
3795
+ const left = normalizeDedupeText(first);
3796
+ const right = normalizeDedupeText(second);
3797
+ if (left.length < 40 || right.length < 40) return false;
3798
+ if (left === right) return true;
3799
+ const shorter = left.length <= right.length ? left : right;
3800
+ const longer = left.length <= right.length ? right : left;
3801
+ return longer.startsWith(
3802
+ shorter.slice(0, Math.floor(shorter.length * 0.85))
3803
+ );
3804
+ }
3805
+ function paragraphsShareOpening(first, second) {
3806
+ const opening = first.split("\n")[0]?.trim();
3807
+ if (!opening || opening.length < 20) return false;
3808
+ return second.trim().startsWith(opening);
3809
+ }
3810
+ function collapseRepeatedText(text) {
3811
+ const trimmed = text.trim();
3812
+ if (trimmed.length < 40) return trimmed;
3813
+ const paragraphs = trimmed.split(/\n{2,}/u).map((part) => part.trim()).filter(Boolean);
3814
+ if (paragraphs.length === 2 && (paragraphsAreNearDuplicates(paragraphs[0], paragraphs[1]) || paragraphsShareOpening(paragraphs[0], paragraphs[1]))) {
3815
+ return paragraphs[0];
3816
+ }
3817
+ if (paragraphs.length >= 2 && paragraphs.length % 2 === 0) {
3818
+ const mid2 = paragraphs.length / 2;
3819
+ const first = paragraphs.slice(0, mid2).join("\n\n");
3820
+ const second = paragraphs.slice(mid2).join("\n\n");
3821
+ if (first === second) return first;
3822
+ }
3823
+ const mid = Math.floor(trimmed.length / 2);
3824
+ const left = trimmed.slice(0, mid).trim();
3825
+ const right = trimmed.slice(mid).trim();
3826
+ return left.length >= 20 && left === right ? left : trimmed;
3827
+ }
2296
3828
  function MessageBubble({
2297
3829
  message,
2298
3830
  brandLogoUrl,
@@ -2309,24 +3841,41 @@ function MessageBubble({
2309
3841
  const offers = offer ? [offer] : extractedOffers;
2310
3842
  const visibleText = hideToolCardFences(message.text);
2311
3843
  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);
3844
+ const displayText = collapseRepeatedText(
3845
+ offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
3846
+ );
2313
3847
  if (message.role === "visitor") {
2314
3848
  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
3849
  }
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
- ) });
3850
+ const citations = message.citations ?? [];
3851
+ const agentText = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "message-bubble__text", children: [
3852
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3853
+ import_streamdown.Streamdown,
3854
+ {
3855
+ animated: isStreaming,
3856
+ caret: "circle",
3857
+ className: "message-bubble__markdown",
3858
+ controls: false,
3859
+ isAnimating: isStreaming,
3860
+ linkSafety: { enabled: false },
3861
+ mode: isStreaming ? "streaming" : "static",
3862
+ skipHtml: true,
3863
+ children: displayText
3864
+ }
3865
+ ),
3866
+ 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)(
3867
+ "a",
3868
+ {
3869
+ href: citation.url,
3870
+ target: "_blank",
3871
+ rel: "noreferrer",
3872
+ children: [
3873
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "message-bubble__source-icon", "aria-hidden": "true", children: "\u25A6" }),
3874
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: citation.label })
3875
+ ]
3876
+ }
3877
+ ) }, citation.id)) }) : null
3878
+ ] });
2330
3879
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
2331
3880
  displayText ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "message-bubble__agent-row", children: [
2332
3881
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -2352,10 +3901,262 @@ function MessageBubble({
2352
3901
  ] });
2353
3902
  }
2354
3903
 
2355
- // src/react/components/AgentRail/AgentRail.tsx
3904
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3905
+ var import_react9 = require("react");
3906
+
3907
+ // src/react/components/ConfirmationCard/ConfirmationCard.tsx
2356
3908
  var import_jsx_runtime6 = require("react/jsx-runtime");
3909
+ function ConfirmationCard({
3910
+ disabled = false,
3911
+ request,
3912
+ onRespond
3913
+ }) {
3914
+ const options = request.options ?? [];
3915
+ const heading = request.kind === "tool-approval" ? "Confirm this action" : request.prompt;
3916
+ const prompt = request.kind === "tool-approval" ? request.prompt : void 0;
3917
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3918
+ "section",
3919
+ {
3920
+ className: "confirmation-card",
3921
+ "aria-labelledby": `confirmation-${request.requestId}`,
3922
+ children: [
3923
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "confirmation-card__heading", children: [
3924
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { id: `confirmation-${request.requestId}`, children: heading }),
3925
+ prompt ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: prompt }) : null
3926
+ ] }),
3927
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "confirmation-card__actions", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3928
+ "button",
3929
+ {
3930
+ type: "button",
3931
+ className: `confirmation-card__action confirmation-card__action--${option.style ?? "default"}`,
3932
+ disabled,
3933
+ onClick: () => onRespond?.({
3934
+ requestId: request.requestId,
3935
+ optionId: option.id
3936
+ }),
3937
+ children: option.label
3938
+ },
3939
+ option.id
3940
+ )) })
3941
+ ]
3942
+ }
3943
+ );
3944
+ }
3945
+
3946
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3947
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3948
+ function HumanInputCard({
3949
+ disabled = false,
3950
+ request,
3951
+ onRespond
3952
+ }) {
3953
+ const [text, setText] = (0, import_react9.useState)("");
3954
+ const options = request.options ?? [];
3955
+ const showText = request.display === "text" || request.allowFreeform && options.length === 0;
3956
+ if (options.length > 0) {
3957
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3958
+ ConfirmationCard,
3959
+ {
3960
+ disabled,
3961
+ request,
3962
+ onRespond
3963
+ }
3964
+ );
3965
+ }
3966
+ function submitText(event) {
3967
+ event.preventDefault();
3968
+ const value = text.trim();
3969
+ if (!value || disabled) return;
3970
+ onRespond?.({ requestId: request.requestId, text: value });
3971
+ }
3972
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3973
+ "section",
3974
+ {
3975
+ className: "human-input-card",
3976
+ "aria-labelledby": `human-input-${request.requestId}`,
3977
+ children: [
3978
+ /* @__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 }) }),
3979
+ showText ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("form", { onSubmit: submitText, children: [
3980
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { htmlFor: `human-input-text-${request.requestId}`, children: "Response" }),
3981
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
3982
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3983
+ "input",
3984
+ {
3985
+ id: `human-input-text-${request.requestId}`,
3986
+ value: text,
3987
+ disabled,
3988
+ onChange: (event) => setText(event.target.value)
3989
+ }
3990
+ ),
3991
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "submit", disabled: disabled || !text.trim(), children: "Send" })
3992
+ ] })
3993
+ ] }) : null,
3994
+ !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
3995
+ ]
3996
+ }
3997
+ );
3998
+ }
3999
+
4000
+ // src/react/components/CollectionResultCard/CollectionResultCard.tsx
4001
+ var import_jsx_runtime8 = require("react/jsx-runtime");
4002
+ function CollectionResultCard({
4003
+ result
4004
+ }) {
4005
+ const empty = result.items.length === 0;
4006
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4007
+ "section",
4008
+ {
4009
+ className: `collection-result-card tool-result-card tool-result-card--${result.status}`,
4010
+ "aria-label": result.title,
4011
+ children: [
4012
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "tool-result-card__heading", children: [
4013
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4014
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: result.title })
4015
+ ] }),
4016
+ 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: [
4017
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "collection-result-card__item-title", children: item.title }),
4018
+ item.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: item.description }) : null,
4019
+ item.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dl", { children: item.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
4020
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dt", { children: detail.label }),
4021
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dd", { children: detail.value })
4022
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
4023
+ item.href ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { href: item.href, target: "_blank", rel: "noreferrer", children: "Open record" }) : null
4024
+ ] }, item.title)) })
4025
+ ]
4026
+ }
4027
+ );
4028
+ }
4029
+
4030
+ // src/react/components/EntityResultCard/EntityResultCard.tsx
4031
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4032
+ function EntityResultCard({
4033
+ result
4034
+ }) {
4035
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4036
+ "section",
4037
+ {
4038
+ className: `entity-result-card tool-result-card tool-result-card--${result.status}`,
4039
+ "aria-label": result.title,
4040
+ children: [
4041
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "tool-result-card__heading", children: [
4042
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4043
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: result.title })
4044
+ ] }),
4045
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: result.description }) : null,
4046
+ result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
4047
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dt", { children: detail.label }),
4048
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dd", { children: detail.value })
4049
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
4050
+ 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)(
4051
+ "a",
4052
+ {
4053
+ href: link.href,
4054
+ target: "_blank",
4055
+ rel: "noreferrer",
4056
+ children: link.label
4057
+ },
4058
+ link.href
4059
+ )) }) : null
4060
+ ]
4061
+ }
4062
+ );
4063
+ }
4064
+
4065
+ // src/react/components/SignatureResultCard/SignatureResultCard.tsx
4066
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4067
+ function SignatureResultCard({
4068
+ result
4069
+ }) {
4070
+ const primaryLink = result.links?.[0];
4071
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4072
+ "section",
4073
+ {
4074
+ className: `signature-result-card tool-result-card tool-result-card--${result.status}`,
4075
+ "aria-label": result.title,
4076
+ children: [
4077
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tool-result-card__heading", children: [
4078
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4079
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: result.title })
4080
+ ] }),
4081
+ result.statusLabel ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "signature-result-card__badge", children: result.statusLabel }) : null,
4082
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: result.description }) : null,
4083
+ primaryLink ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4084
+ "a",
4085
+ {
4086
+ className: "signature-result-card__cta",
4087
+ href: primaryLink.href,
4088
+ target: "_blank",
4089
+ rel: "noreferrer",
4090
+ children: primaryLink.label
4091
+ }
4092
+ ) : null
4093
+ ]
4094
+ }
4095
+ );
4096
+ }
4097
+
4098
+ // src/react/components/ToolResultCard/ToolResultCard.tsx
4099
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4100
+ function ToolResultCard({
4101
+ result
4102
+ }) {
4103
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4104
+ "section",
4105
+ {
4106
+ className: `tool-result-card tool-result-card--${result.status}`,
4107
+ "aria-label": result.title,
4108
+ children: [
4109
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "tool-result-card__heading", children: [
4110
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
4111
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: result.title })
4112
+ ] }),
4113
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { children: result.description }) : null,
4114
+ result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
4115
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dt", { children: detail.label }),
4116
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dd", { children: detail.value })
4117
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
4118
+ 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)(
4119
+ "a",
4120
+ {
4121
+ href: link.href,
4122
+ target: "_blank",
4123
+ rel: "noreferrer",
4124
+ children: link.label
4125
+ },
4126
+ link.href
4127
+ )) }) : null
4128
+ ]
4129
+ }
4130
+ );
4131
+ }
4132
+
4133
+ // src/react/components/VisitorToolResultView/VisitorToolResultView.tsx
4134
+ var import_jsx_runtime12 = require("react/jsx-runtime");
4135
+ function VisitorToolResultView({
4136
+ result
4137
+ }) {
4138
+ if (result.kind === "entity") {
4139
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EntityResultCard, { result });
4140
+ }
4141
+ if (result.kind === "collection") {
4142
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CollectionResultCard, { result });
4143
+ }
4144
+ if (result.kind === "signature") {
4145
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SignatureResultCard, { result });
4146
+ }
4147
+ if (result.kind === "summary") {
4148
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToolResultCard, { result });
4149
+ }
4150
+ return null;
4151
+ }
4152
+ function isRenderableVisitorToolResult(result) {
4153
+ return result.kind === "summary" || result.kind === "entity" || result.kind === "collection" || result.kind === "signature";
4154
+ }
4155
+
4156
+ // src/react/components/AgentRail/AgentRail.tsx
4157
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2357
4158
  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)(
4159
+ 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
4160
  "path",
2360
4161
  {
2361
4162
  d: "M3.5 8h9",
@@ -2366,7 +4167,7 @@ function MinimizeIcon() {
2366
4167
  ) });
2367
4168
  }
2368
4169
  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)(
4170
+ 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
4171
  "path",
2371
4172
  {
2372
4173
  d: "M4 4l8 8M12 4l-8 8",
@@ -2377,7 +4178,7 @@ function CloseIcon() {
2377
4178
  ) });
2378
4179
  }
2379
4180
  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)(
4181
+ 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
4182
  "path",
2382
4183
  {
2383
4184
  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 +4190,7 @@ function NewChatIcon() {
2389
4190
  ) });
2390
4191
  }
2391
4192
  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)(
4193
+ 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
4194
  "path",
2394
4195
  {
2395
4196
  d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
@@ -2401,7 +4202,7 @@ function ExpandIcon() {
2401
4202
  ) });
2402
4203
  }
2403
4204
  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)(
4205
+ 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
4206
  "path",
2406
4207
  {
2407
4208
  d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
@@ -2429,28 +4230,29 @@ function AgentRail({
2429
4230
  onRetry,
2430
4231
  onSubmit,
2431
4232
  onFollowUpSelect,
2432
- onBook
4233
+ onBook,
4234
+ onInputResponse
2433
4235
  }) {
2434
- const transcriptRef = (0, import_react9.useRef)(null);
4236
+ const transcriptRef = (0, import_react10.useRef)(null);
2435
4237
  const resolvedBrandLabel = brandLabel.trim();
2436
4238
  const resolvedBrandLogoUrl = brandLogoUrl?.trim();
2437
- const [failedLogoUrl, setFailedLogoUrl] = (0, import_react9.useState)(null);
4239
+ const [failedLogoUrl, setFailedLogoUrl] = (0, import_react10.useState)(null);
2438
4240
  const showBrandLogo = Boolean(resolvedBrandLogoUrl) && failedLogoUrl !== resolvedBrandLogoUrl;
2439
4241
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2440
4242
  const brandedTheme = { ...defaultAgentRailTheme, ...theme };
2441
4243
  const resolvedTheme = resolvedColorScheme === "dark" ? {
2442
4244
  ...brandedTheme,
2443
4245
  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,
4246
+ brandDeep: theme?.brandDeep ?? defaultDarkAgentRailTheme.brandDeep,
4247
+ brandSoft: theme?.brandSoft ?? `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
4248
+ border: theme?.border ?? defaultDarkAgentRailTheme.border,
4249
+ danger: theme?.danger ?? defaultDarkAgentRailTheme.danger,
4250
+ success: theme?.success ?? defaultDarkAgentRailTheme.success,
4251
+ surface: theme?.surface ?? defaultDarkAgentRailTheme.surface,
4252
+ surfaceMuted: theme?.surfaceMuted ?? defaultDarkAgentRailTheme.surfaceMuted,
4253
+ text: theme?.text ?? defaultDarkAgentRailTheme.text,
4254
+ textMuted: theme?.textMuted ?? defaultDarkAgentRailTheme.textMuted,
4255
+ textSubtle: theme?.textSubtle ?? defaultDarkAgentRailTheme.textSubtle,
2454
4256
  visitorBubble: theme?.visitorBubble ?? theme?.brand ?? defaultDarkAgentRailTheme.visitorBubble
2455
4257
  } : brandedTheme;
2456
4258
  const railStyle = {
@@ -2473,8 +4275,15 @@ function AgentRail({
2473
4275
  "--as-font-display": resolvedTheme.fontDisplay,
2474
4276
  colorScheme: resolvedColorScheme
2475
4277
  };
2476
- const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming";
4278
+ const pendingInputRequests = (state.pendingInputs ?? []).filter(
4279
+ (request) => shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
4280
+ );
4281
+ const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming" || state.phase === "waiting-input" && pendingInputRequests.length > 0;
4282
+ const semanticSurfaceDisabled = isBusy && state.phase !== "waiting-input";
2477
4283
  const showActivity = state.toolSteps.length > 0;
4284
+ const visitorToolResults = (state.toolResults ?? []).filter(
4285
+ isRenderableVisitorToolResult
4286
+ );
2478
4287
  const hasVisitorMessages2 = state.messages.some(
2479
4288
  (message) => message.role === "visitor"
2480
4289
  );
@@ -2484,22 +4293,44 @@ function AgentRail({
2484
4293
  );
2485
4294
  const visibleMessages = hasVisitorMessages2 ? state.messages : [];
2486
4295
  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 ? {
4296
+ let lastAgentIndex = -1;
4297
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4298
+ if (visibleMessages[index]?.role === "agent") {
4299
+ lastAgentIndex = index;
4300
+ break;
4301
+ }
4302
+ }
4303
+ let lastVisitorIndex = -1;
4304
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4305
+ if (visibleMessages[index]?.role === "visitor") {
4306
+ lastVisitorIndex = index;
4307
+ break;
4308
+ }
4309
+ }
4310
+ const lastIsAgent = lastMessage?.role === "agent";
4311
+ const streamingMessage = state.phase === "streaming" && state.streamingText && !lastIsAgent ? {
2490
4312
  createdAt: 0,
2491
4313
  id: "streaming-response",
2492
4314
  role: "agent",
2493
4315
  streaming: true,
2494
4316
  text: state.streamingText
2495
- } : state.pendingOffer ? {
4317
+ } : state.pendingOffer && !lastIsAgent ? {
2496
4318
  createdAt: 0,
2497
4319
  id: "pending-booking",
2498
4320
  role: "agent",
2499
4321
  streaming: false,
2500
4322
  text: "Pick a date and time that works for you."
2501
4323
  } : null;
2502
- (0, import_react9.useEffect)(() => {
4324
+ const bookingReadyText = state.streamingText || (lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "");
4325
+ const waitingForBooking = !state.pendingOffer && looksLikeBookingReady(bookingReadyText) && (state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming");
4326
+ const lastAgentText = lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "";
4327
+ const composerForm = resolveComposerForm({
4328
+ agentText: lastAgentText,
4329
+ cards: extractToolCards(lastAgentText),
4330
+ hasBookingOffer: Boolean(state.pendingOffer) || waitingForBooking,
4331
+ enabled: lastIsAgent && !isBusy
4332
+ });
4333
+ (0, import_react10.useEffect)(() => {
2503
4334
  const node = transcriptRef.current;
2504
4335
  if (!node) return;
2505
4336
  node.scrollTop = node.scrollHeight;
@@ -2510,11 +4341,13 @@ function AgentRail({
2510
4341
  state.followUps,
2511
4342
  state.journey
2512
4343
  ]);
2513
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4344
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2514
4345
  "aside",
2515
4346
  {
2516
- className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4347
+ className: `agent-rail not-typeset${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4348
+ "data-not-typeset": "",
2517
4349
  "data-color-scheme": resolvedColorScheme,
4350
+ spellCheck: false,
2518
4351
  style: railStyle,
2519
4352
  "aria-label": "Agent conversation",
2520
4353
  "aria-modal": mobileFullscreen || expanded ? true : void 0,
@@ -2522,28 +4355,28 @@ function AgentRail({
2522
4355
  role: mobileFullscreen || expanded ? "dialog" : void 0,
2523
4356
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
2524
4357
  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)(
4358
+ /* @__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: [
4359
+ onCollapse ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2527
4360
  "button",
2528
4361
  {
2529
4362
  type: "button",
2530
4363
  className: "agent-rail__collapse",
2531
4364
  "aria-label": "Collapse assist",
2532
4365
  onClick: onCollapse,
2533
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MinimizeIcon, {})
4366
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MinimizeIcon, {})
2534
4367
  }
2535
- ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4368
+ ) : onClose ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2536
4369
  "button",
2537
4370
  {
2538
4371
  type: "button",
2539
4372
  className: "agent-rail__close",
2540
4373
  "aria-label": "Close agent",
2541
4374
  onClick: onClose,
2542
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, {})
4375
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CloseIcon, {})
2543
4376
  }
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)(
4377
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
4378
+ resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "agent-rail__identity", children: [
4379
+ showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2547
4380
  "img",
2548
4381
  {
2549
4382
  className: "agent-rail__brand-logo",
@@ -2554,10 +4387,10 @@ function AgentRail({
2554
4387
  }
2555
4388
  }
2556
4389
  ) }) : null,
2557
- resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
4390
+ resolvedBrandLabel ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
2558
4391
  ] }) : null,
2559
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agent-rail__actions", children: [
2560
- onReset ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4392
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "agent-rail__actions", children: [
4393
+ onReset ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2561
4394
  "button",
2562
4395
  {
2563
4396
  type: "button",
@@ -2565,24 +4398,24 @@ function AgentRail({
2565
4398
  "aria-label": "Start a new conversation",
2566
4399
  disabled: !hasVisitorMessages2,
2567
4400
  onClick: onReset,
2568
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NewChatIcon, {})
4401
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(NewChatIcon, {})
2569
4402
  }
2570
4403
  ) : null,
2571
- onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4404
+ onExpandToggle ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2572
4405
  "button",
2573
4406
  {
2574
4407
  type: "button",
2575
4408
  className: "agent-rail__expand",
2576
4409
  "aria-label": expanded ? "Exit full screen" : "Open full screen",
2577
4410
  onClick: onExpandToggle,
2578
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExpandIcon, {})
4411
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ExpandIcon, {})
2579
4412
  }
2580
4413
  ) : null
2581
4414
  ] })
2582
4415
  ] }) }),
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)(
4416
+ /* @__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: [
4417
+ !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
4418
+ greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2586
4419
  MessageBubble,
2587
4420
  {
2588
4421
  message: greeting,
@@ -2590,7 +4423,7 @@ function AgentRail({
2590
4423
  onBook
2591
4424
  }
2592
4425
  ) : null,
2593
- showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4426
+ showIdleFollowUps ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2594
4427
  FollowUpChips,
2595
4428
  {
2596
4429
  suggestions: state.followUps,
@@ -2598,64 +4431,94 @@ function AgentRail({
2598
4431
  label: "Start here",
2599
4432
  onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
2600
4433
  }
2601
- ) }) : null
4434
+ ) }) : null,
4435
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4436
+ AgentActivityBubble,
4437
+ {
4438
+ brandLabel: resolvedBrandLabel,
4439
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4440
+ failed: state.phase === "error",
4441
+ steps: state.toolSteps
4442
+ }
4443
+ ) : null,
4444
+ visitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VisitorToolResultView, { result }, result.id)),
4445
+ pendingInputRequests.map((request) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4446
+ HumanInputCard,
4447
+ {
4448
+ request,
4449
+ onRespond: onInputResponse
4450
+ },
4451
+ request.requestId
4452
+ ))
2602
4453
  ] }) : 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)(
4454
+ visibleMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "agent-rail__turn-block", children: [
4455
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4456
+ MessageBubble,
4457
+ {
4458
+ message,
4459
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4460
+ offer: index === lastAgentIndex ? state.pendingOffer : void 0,
4461
+ onBook
4462
+ }
4463
+ ),
4464
+ index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
4465
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4466
+ AgentActivityBubble,
4467
+ {
4468
+ brandLabel: resolvedBrandLabel,
4469
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4470
+ failed: state.phase === "error",
4471
+ steps: state.toolSteps
4472
+ }
4473
+ ) : null,
4474
+ visitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VisitorToolResultView, { result }, result.id)),
4475
+ pendingInputRequests.map((request) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4476
+ HumanInputCard,
4477
+ {
4478
+ request,
4479
+ onRespond: onInputResponse
4480
+ },
4481
+ request.requestId
4482
+ ))
4483
+ ] }) : null
4484
+ ] }, message.id)),
4485
+ streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2622
4486
  MessageBubble,
2623
4487
  {
2624
- message: completedAnswer,
4488
+ message: streamingMessage,
2625
4489
  brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4490
+ offer: state.pendingOffer,
2626
4491
  onBook
2627
4492
  }
2628
4493
  ) : null,
2629
- streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2630
- MessageBubble,
4494
+ waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4495
+ BookingCard,
2631
4496
  {
2632
- message: streamingMessage,
2633
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2634
- offer: state.pendingOffer,
2635
- onBook
4497
+ offer: { type: "booking_offer", eventTypes: [], slots: [] }
2636
4498
  }
2637
4499
  ) : 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 })
4500
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
4501
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
4502
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: "Something went wrong" }),
4503
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: state.error })
2642
4504
  ] }),
2643
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
4505
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
2644
4506
  ] }) : null
2645
4507
  ] }) }),
2646
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
2647
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4508
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "agent-rail__composer-wrap", children: [
4509
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2648
4510
  Composer,
2649
4511
  {
2650
4512
  variant: expanded || mobileFullscreen ? "dock" : "default",
2651
4513
  disabled: isBusy,
4514
+ form: composerForm,
2652
4515
  placeholder: composerPlaceholder,
2653
4516
  onSubmit
2654
4517
  }
2655
4518
  ),
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 })
4519
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { children: [
4520
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "AI can make mistakes. Check important info." }),
4521
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: poweredByLabel })
2659
4522
  ] }) })
2660
4523
  ] })
2661
4524
  ]
@@ -2664,9 +4527,9 @@ function AgentRail({
2664
4527
  }
2665
4528
 
2666
4529
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
2667
- var import_jsx_runtime7 = require("react/jsx-runtime");
4530
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2668
4531
  function SparklesIcon() {
2669
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4532
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2670
4533
  "svg",
2671
4534
  {
2672
4535
  className: "assist-edge-tab__sparkles",
@@ -2674,21 +4537,21 @@ function SparklesIcon() {
2674
4537
  fill: "none",
2675
4538
  "aria-hidden": "true",
2676
4539
  children: [
2677
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4540
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2678
4541
  "path",
2679
4542
  {
2680
4543
  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
4544
  fill: "currentColor"
2682
4545
  }
2683
4546
  ),
2684
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4547
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2685
4548
  "path",
2686
4549
  {
2687
4550
  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
4551
  fill: "currentColor"
2689
4552
  }
2690
4553
  ),
2691
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4554
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2692
4555
  "path",
2693
4556
  {
2694
4557
  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 +4565,7 @@ function SparklesIcon() {
2702
4565
  function TabMarkIcon({ customIconUrl }) {
2703
4566
  const url = customIconUrl?.trim();
2704
4567
  if (url) {
2705
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4568
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2706
4569
  "img",
2707
4570
  {
2708
4571
  alt: "",
@@ -2712,10 +4575,10 @@ function TabMarkIcon({ customIconUrl }) {
2712
4575
  }
2713
4576
  );
2714
4577
  }
2715
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparklesIcon, {});
4578
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SparklesIcon, {});
2716
4579
  }
2717
4580
  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)(
4581
+ 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
4582
  "path",
2720
4583
  {
2721
4584
  d: "M10 4L6 8l4 4",
@@ -2727,7 +4590,7 @@ function ChevronLeftIcon() {
2727
4590
  ) });
2728
4591
  }
2729
4592
  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)(
4593
+ 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
4594
  "path",
2732
4595
  {
2733
4596
  d: "M4 6l4 4 4-4",
@@ -2739,7 +4602,7 @@ function ChevronDownIcon() {
2739
4602
  ) });
2740
4603
  }
2741
4604
  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)) });
4605
+ 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
4606
  }
2744
4607
  var VARIANT_COPY = {
2745
4608
  outline: { label: "Ask anything", aria: "Ask anything" },
@@ -2768,6 +4631,7 @@ function AssistEdgeTab({
2768
4631
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2769
4632
  const copy = VARIANT_COPY[variant];
2770
4633
  const visibleLabel = label?.trim() || copy.label;
4634
+ const alignment = along < 50 ? "start" : along > 50 ? "end" : "center";
2771
4635
  const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
2772
4636
  const resolvedBrandColor = brandColor ?? (resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
2773
4637
  const resolvedBorderColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.border : borderColor;
@@ -2784,11 +4648,11 @@ function AssistEdgeTab({
2784
4648
  ...resolvedTextColor ? { "--as-text": resolvedTextColor } : {},
2785
4649
  colorScheme: resolvedColorScheme
2786
4650
  };
2787
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4651
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2788
4652
  "button",
2789
4653
  {
2790
4654
  type: "button",
2791
- className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
4655
+ 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
4656
  "data-color-scheme": resolvedColorScheme,
2793
4657
  style,
2794
4658
  "aria-label": `Open ${visibleLabel}`,
@@ -2796,15 +4660,15 @@ function AssistEdgeTab({
2796
4660
  tabIndex: visible ? 0 : -1,
2797
4661
  onClick: onOpen,
2798
4662
  children: [
2799
- mobile ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2800
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4663
+ mobile ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4664
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2801
4665
  "span",
2802
4666
  {
2803
4667
  className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
2804
4668
  "aria-hidden": "true",
2805
4669
  children: [
2806
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabMarkIcon, { customIconUrl }),
2807
- showLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4670
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TabMarkIcon, { customIconUrl }),
4671
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2808
4672
  "img",
2809
4673
  {
2810
4674
  className: "assist-edge-tab__logo",
@@ -2818,11 +4682,11 @@ function AssistEdgeTab({
2818
4682
  ]
2819
4683
  }
2820
4684
  ),
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)(
4685
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel })
4686
+ ] }) : variant === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4687
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4688
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TabMarkIcon, { customIconUrl }),
4689
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2826
4690
  "img",
2827
4691
  {
2828
4692
  className: "assist-edge-tab__logo",
@@ -2834,18 +4698,18 @@ function AssistEdgeTab({
2834
4698
  }
2835
4699
  ) : null
2836
4700
  ] }),
2837
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2838
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {})
4701
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4702
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronDownIcon, {})
2839
4703
  ] }) : 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, {})
4704
+ variant === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4705
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronLeftIcon, {}),
4706
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4707
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DragDots, {})
2844
4708
  ] }) : 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)(
4709
+ variant === "fill" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
4710
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4711
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TabMarkIcon, { customIconUrl }),
4712
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2849
4713
  "img",
2850
4714
  {
2851
4715
  className: "assist-edge-tab__logo",
@@ -2857,8 +4721,8 @@ function AssistEdgeTab({
2857
4721
  }
2858
4722
  ) : null
2859
4723
  ] }),
2860
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2861
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {})
4724
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4725
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronLeftIcon, {})
2862
4726
  ] }) : null
2863
4727
  ]
2864
4728
  }
@@ -2866,7 +4730,7 @@ function AssistEdgeTab({
2866
4730
  }
2867
4731
 
2868
4732
  // src/react/components/AgentWidget/AgentWidget.tsx
2869
- var import_jsx_runtime8 = require("react/jsx-runtime");
4733
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2870
4734
  function AgentWidget({
2871
4735
  indexId,
2872
4736
  customerId,
@@ -2879,13 +4743,14 @@ function AgentWidget({
2879
4743
  pageShift = true,
2880
4744
  registerPanelController = false,
2881
4745
  colorScheme = "auto",
2882
- branding
4746
+ branding,
4747
+ toolResultRegistry
2883
4748
  }) {
2884
4749
  const isMobile = useIsMobile();
2885
4750
  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);
4751
+ const railSlotRef = (0, import_react11.useRef)(null);
4752
+ const [railCollapsed, setRailCollapsed] = (0, import_react11.useState)(defaultCollapsed);
4753
+ const [railExpanded, setRailExpanded] = (0, import_react11.useState)(false);
2889
4754
  const pageShiftActive = shouldApplyPageShift({
2890
4755
  pageShift,
2891
4756
  isMobile,
@@ -2896,14 +4761,15 @@ function AgentWidget({
2896
4761
  active: pageShiftActive,
2897
4762
  railSlotRef
2898
4763
  });
2899
- const { state, reset, retry, submit } = useAgentChat({
4764
+ const { state, reset, retry, respondToInput, respondToToolInput, submit } = useAgentChat({
2900
4765
  customerId,
2901
4766
  getUnpublishedPreviewGrant,
2902
4767
  indexId,
2903
4768
  previewBuildId,
2904
4769
  version,
2905
4770
  runtimeOrigin,
2906
- greeting: branding?.greeting
4771
+ greeting: branding?.greeting,
4772
+ toolResultRegistry
2907
4773
  });
2908
4774
  const agentName = branding?.agentName ?? "";
2909
4775
  const tabLabel = branding?.tabLabel ?? agentName;
@@ -2924,22 +4790,24 @@ function AgentWidget({
2924
4790
  } : {},
2925
4791
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
2926
4792
  };
2927
- (0, import_react10.useEffect)(() => {
4793
+ (0, import_react11.useEffect)(() => {
2928
4794
  if (!registerPanelController) return;
2929
4795
  registerAgentPanelController(customerId, {
2930
4796
  open: () => setRailCollapsed(false),
2931
4797
  close: () => {
2932
4798
  setRailCollapsed(true);
2933
4799
  setRailExpanded(false);
2934
- }
4800
+ },
4801
+ reset,
4802
+ submit
2935
4803
  });
2936
4804
  return () => unregisterAgentPanelController(customerId);
2937
- }, [customerId, registerPanelController]);
4805
+ }, [customerId, registerPanelController, reset, submit]);
2938
4806
  async function handleSubmit(message) {
2939
4807
  if (isMobile) setRailCollapsed(false);
2940
4808
  await submit(message);
2941
4809
  }
2942
- (0, import_react10.useEffect)(() => {
4810
+ (0, import_react11.useEffect)(() => {
2943
4811
  if (railCollapsed) return;
2944
4812
  const handleKeyDown = (event) => {
2945
4813
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -2973,19 +4841,19 @@ function AgentWidget({
2973
4841
  window.addEventListener("keydown", handleKeyDown);
2974
4842
  return () => window.removeEventListener("keydown", handleKeyDown);
2975
4843
  }, [isMobile, railCollapsed, railExpanded]);
2976
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "webless-agent-root", children: [
2977
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4844
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "webless-agent-root", children: [
4845
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2978
4846
  "div",
2979
4847
  {
2980
4848
  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)(
4849
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2982
4850
  "div",
2983
4851
  {
2984
4852
  ref: railSlotRef,
2985
4853
  className: "webless-agent-root__rail-slot",
2986
4854
  inert: railCollapsed || void 0,
2987
4855
  "aria-hidden": railCollapsed,
2988
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4856
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2989
4857
  AgentRail,
2990
4858
  {
2991
4859
  theme,
@@ -3003,6 +4871,8 @@ function AgentWidget({
3003
4871
  onSubmit: handleSubmit,
3004
4872
  onReset: reset,
3005
4873
  onRetry: () => void retry(),
4874
+ onInputResponse: (response) => void respondToInput(response),
4875
+ onToolInput: (surface, values) => void respondToToolInput(surface, values),
3006
4876
  onFollowUpSelect: (label) => void handleSubmit(label),
3007
4877
  onBook: (input) => void submit(input.displayText, { runtimeText: input.runtimeText })
3008
4878
  }
@@ -3011,7 +4881,7 @@ function AgentWidget({
3011
4881
  )
3012
4882
  }
3013
4883
  ),
3014
- railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4884
+ railCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3015
4885
  AssistEdgeTab,
3016
4886
  {
3017
4887
  variant: placement.variant,
@@ -3050,12 +4920,12 @@ function readUnpublishedPreviewBuildId(href) {
3050
4920
  }
3051
4921
 
3052
4922
  // src/embed/AgentWidget.tsx
3053
- var import_jsx_runtime9 = require("react/jsx-runtime");
4923
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3054
4924
  function AgentWidget2({
3055
4925
  manifest
3056
4926
  }) {
3057
4927
  const defaultCollapsed = manifest.version !== "unpublished";
3058
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4928
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3059
4929
  AgentWidget,
3060
4930
  {
3061
4931
  indexId: manifest.indexId,
@@ -3139,7 +5009,7 @@ function normalizeAgentBranding(branding) {
3139
5009
  }
3140
5010
 
3141
5011
  // src/embed/mount.tsx
3142
- var import_jsx_runtime10 = require("react/jsx-runtime");
5012
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3143
5013
  var mountedHandles = /* @__PURE__ */ new Map();
3144
5014
  var latestCustomerId = null;
3145
5015
  function resolveMountHost(manifest, script) {
@@ -3169,7 +5039,7 @@ function mountAgent(input) {
3169
5039
  const host = createHost(manifest.customerId);
3170
5040
  mountTarget.append(host);
3171
5041
  const root = (0, import_client5.createRoot)(host);
3172
- root.render(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AgentWidget2, { manifest }));
5042
+ root.render(/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(AgentWidget2, { manifest }));
3173
5043
  const handle = {
3174
5044
  customerId: manifest.customerId,
3175
5045
  manifest,
@@ -3262,6 +5132,8 @@ function createWeblessAgentPublicApi(manifest) {
3262
5132
  mountAgent,
3263
5133
  normalizeAgentPlacement,
3264
5134
  normalizeAgentTagManifest,
5135
+ resetAgentPanel,
5136
+ submitAgentPanel,
3265
5137
  unmountAgent
3266
5138
  });
3267
5139
  //# sourceMappingURL=embed.cjs.map