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