@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.
@@ -1,3 +1,993 @@
1
+ // src/runtime/tool-ui.ts
2
+ var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
3
+ function isRecord(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+ function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
7
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
8
+ return true;
9
+ }
10
+ if (typeof value === "number") return Number.isFinite(value);
11
+ if (typeof value !== "object") return false;
12
+ if (seen.has(value)) return false;
13
+ seen.add(value);
14
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
15
+ ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
16
+ );
17
+ seen.delete(value);
18
+ return valid;
19
+ }
20
+ function boundedString(value, max) {
21
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
22
+ return void 0;
23
+ }
24
+ return value.trim();
25
+ }
26
+ function numberValue(value) {
27
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
28
+ }
29
+ function integerValue(value) {
30
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
31
+ }
32
+ function isFieldKind(value) {
33
+ return typeof value === "string" && [
34
+ "text",
35
+ "textarea",
36
+ "email",
37
+ "number",
38
+ "select",
39
+ "multi-select",
40
+ "checkbox",
41
+ "confirmation",
42
+ "radio",
43
+ "date",
44
+ "time",
45
+ "date-time",
46
+ "calendar",
47
+ "range",
48
+ "json"
49
+ ].includes(value);
50
+ }
51
+ function parseField(value) {
52
+ if (!isRecord(value)) return null;
53
+ if (!hasOnlyKeys(value, [
54
+ "description",
55
+ "kind",
56
+ "label",
57
+ "max",
58
+ "maxItems",
59
+ "maxLength",
60
+ "min",
61
+ "minLength",
62
+ "options",
63
+ "path",
64
+ "placeholder",
65
+ "required",
66
+ "step",
67
+ "defaultValue"
68
+ ])) {
69
+ return null;
70
+ }
71
+ if (!isFieldKind(value.kind)) return null;
72
+ const path = boundedString(value.path, 160);
73
+ const label = boundedString(value.label, 160);
74
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
75
+ const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
76
+ const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
77
+ const min = value.min === void 0 ? void 0 : numberValue(value.min);
78
+ const max = value.max === void 0 ? void 0 : numberValue(value.max);
79
+ const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
80
+ const step = value.step === void 0 ? void 0 : numberValue(value.step);
81
+ const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
82
+ const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
83
+ const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
84
+ if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
85
+ return null;
86
+ }
87
+ if (value.description !== void 0 && !description) return null;
88
+ if (value.placeholder !== void 0 && placeholder === void 0) return null;
89
+ if (value.required !== void 0 && required === void 0) return null;
90
+ if (value.min !== void 0 && min === void 0) return null;
91
+ if (value.max !== void 0 && max === void 0) return null;
92
+ if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
93
+ return null;
94
+ if (value.step !== void 0 && step === void 0) return null;
95
+ if (value.minLength !== void 0 && minLength === void 0) return null;
96
+ if (value.maxLength !== void 0 && maxLength === void 0) return null;
97
+ if (value.defaultValue !== void 0 && defaultValue === void 0)
98
+ return null;
99
+ if (value.options !== void 0) {
100
+ if (!Array.isArray(value.options) || value.options.length > 100)
101
+ return null;
102
+ for (const option of value.options) {
103
+ if (!isRecord(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
104
+ return null;
105
+ }
106
+ }
107
+ }
108
+ return {
109
+ kind: value.kind,
110
+ path,
111
+ label,
112
+ ...description ? { description } : {},
113
+ ...placeholder !== void 0 ? { placeholder } : {},
114
+ ...required !== void 0 ? { required } : {},
115
+ ...defaultValue !== void 0 ? { defaultValue } : {},
116
+ ...value.options !== void 0 ? { options: value.options } : {},
117
+ ...min !== void 0 ? { min } : {},
118
+ ...max !== void 0 ? { max } : {},
119
+ ...maxItems !== void 0 ? { maxItems } : {},
120
+ ...step !== void 0 ? { step } : {},
121
+ ...minLength !== void 0 ? { minLength } : {},
122
+ ...maxLength !== void 0 ? { maxLength } : {}
123
+ };
124
+ }
125
+ function parseStep(value) {
126
+ if (!isRecord(value)) return null;
127
+ if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
128
+ return null;
129
+ }
130
+ const id = boundedString(value.id, 80);
131
+ const label = boundedString(value.label, 160);
132
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
133
+ 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(
134
+ (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
135
+ )) {
136
+ return null;
137
+ }
138
+ return {
139
+ id,
140
+ label,
141
+ fieldPaths: value.fieldPaths,
142
+ ...description ? { description } : {}
143
+ };
144
+ }
145
+ function parseAction(value) {
146
+ if (!isRecord(value)) return null;
147
+ if (!hasOnlyKeys(value, ["id", "label"])) return null;
148
+ const label = boundedString(value.label, 80);
149
+ if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
150
+ return null;
151
+ }
152
+ return {
153
+ id: value.id,
154
+ label
155
+ };
156
+ }
157
+ function parseAgentToolUiSurface(value) {
158
+ if (!isRecord(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
159
+ return null;
160
+ if (!hasOnlyKeys(value, [
161
+ "actions",
162
+ "description",
163
+ "fields",
164
+ "id",
165
+ "operationId",
166
+ "requestId",
167
+ "schemaVersion",
168
+ "steps",
169
+ "submitLabel",
170
+ "title",
171
+ "toolSlug",
172
+ "values"
173
+ ])) {
174
+ return null;
175
+ }
176
+ const id = boundedString(value.id, 200);
177
+ const title = boundedString(value.title, 200);
178
+ const toolSlug = boundedString(value.toolSlug, 200);
179
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
180
+ const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
181
+ const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
182
+ const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
183
+ if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
184
+ return null;
185
+ }
186
+ const fields = value.fields.map(parseField);
187
+ if (fields.some((field) => field === null)) return null;
188
+ const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
189
+ if (steps?.some((step) => step === null)) return null;
190
+ const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
191
+ if (actions?.some((action) => action === null)) return null;
192
+ if (value.description !== void 0 && !description) return null;
193
+ if (value.operationId !== void 0 && !operationId) return null;
194
+ if (value.requestId !== void 0 && !requestId) return null;
195
+ if (value.submitLabel !== void 0 && !submitLabel) return null;
196
+ const values = value.values !== void 0 && isRecord(value.values) ? value.values : void 0;
197
+ if (value.values !== void 0) {
198
+ if (!values || !Object.values(values).every((item) => isJsonValue(item)))
199
+ return null;
200
+ }
201
+ return {
202
+ schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
203
+ id,
204
+ title,
205
+ toolSlug,
206
+ fields,
207
+ ...actions ? { actions } : {},
208
+ ...description ? { description } : {},
209
+ ...operationId ? { operationId } : {},
210
+ ...requestId ? { requestId } : {},
211
+ ...submitLabel ? { submitLabel } : {},
212
+ ...steps ? { steps } : {},
213
+ ...values ? { values } : {}
214
+ };
215
+ }
216
+ function hasOnlyKeys(value, allowed) {
217
+ const allowedKeys = new Set(allowed);
218
+ return Object.keys(value).every((key) => allowedKeys.has(key));
219
+ }
220
+
221
+ // src/runtime/tool-result-envelope.ts
222
+ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
223
+ "schemaVersion",
224
+ "output",
225
+ "presentationKinds",
226
+ "ui"
227
+ ]);
228
+ function isRecord2(value) {
229
+ return value !== null && typeof value === "object" && !Array.isArray(value);
230
+ }
231
+ function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
232
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
233
+ return true;
234
+ }
235
+ if (typeof value === "number") return Number.isFinite(value);
236
+ if (typeof value !== "object") return false;
237
+ if (seen.has(value)) return false;
238
+ seen.add(value);
239
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
240
+ ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
241
+ );
242
+ seen.delete(value);
243
+ return valid;
244
+ }
245
+ function decodeEnvelope(value) {
246
+ if (typeof value !== "string") return value;
247
+ try {
248
+ return JSON.parse(value);
249
+ } catch {
250
+ return null;
251
+ }
252
+ }
253
+ function parseAgentToolResultEnvelope(value) {
254
+ const decoded = decodeEnvelope(value);
255
+ if (!isRecord2(decoded)) return null;
256
+ const keys = Object.keys(decoded);
257
+ 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) {
258
+ return null;
259
+ }
260
+ const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
261
+ if (typeof kind !== "string") return [];
262
+ const normalized = kind.trim();
263
+ return normalized && normalized.length <= 128 ? [normalized] : [];
264
+ });
265
+ if (presentationKinds.length !== decoded.presentationKinds.length) {
266
+ return null;
267
+ }
268
+ const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
269
+ if (decoded.ui !== void 0 && !ui) return null;
270
+ return {
271
+ schemaVersion: "webless.tool-result.v1",
272
+ output: decoded.output,
273
+ presentationKinds,
274
+ ...ui ? { ui } : {}
275
+ };
276
+ }
277
+
278
+ // src/react/lib/composer-form.ts
279
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
280
+ var MIN_FORM_FIELDS = 2;
281
+ var MAX_FORM_FIELDS = 5;
282
+ var FIELD_LIBRARY = [
283
+ {
284
+ id: "email",
285
+ kind: "email",
286
+ label: "Email",
287
+ placeholder: "email@example.com",
288
+ required: true,
289
+ autocomplete: "email",
290
+ patterns: [/\be-?mails?\b/i]
291
+ },
292
+ {
293
+ id: "name",
294
+ kind: "text",
295
+ label: "Name",
296
+ placeholder: "Your name",
297
+ required: true,
298
+ autocomplete: "name",
299
+ patterns: [/\b((full|first|last)\s+)?names?\b/i],
300
+ exclude: /\bcompany\s+names?\b/i
301
+ },
302
+ {
303
+ id: "phone",
304
+ kind: "tel",
305
+ label: "Phone",
306
+ placeholder: "+1 555 0100",
307
+ required: true,
308
+ autocomplete: "tel",
309
+ patterns: [/\b(phone|mobile|cell)\b/i]
310
+ },
311
+ {
312
+ id: "company",
313
+ kind: "text",
314
+ label: "Company",
315
+ placeholder: "Company name",
316
+ required: true,
317
+ autocomplete: "organization",
318
+ patterns: [/\bcompan(y|ies)\b/i]
319
+ },
320
+ {
321
+ id: "message",
322
+ kind: "textarea",
323
+ label: "Message",
324
+ placeholder: "Message\u2026",
325
+ required: true,
326
+ patterns: [/\b(your message|a message|the message|inquiry|enquiry)\b/i],
327
+ exclude: /\bin one message\b/i
328
+ }
329
+ ];
330
+ function asRecord(value) {
331
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
332
+ }
333
+ function asString(value) {
334
+ return typeof value === "string" ? value.trim() : "";
335
+ }
336
+ function isFieldKind2(value) {
337
+ return value === "text" || value === "email" || value === "tel" || value === "textarea";
338
+ }
339
+ function parseVisitorFormFields(value) {
340
+ if (!Array.isArray(value)) return [];
341
+ const fields = [];
342
+ const seen = /* @__PURE__ */ new Set();
343
+ for (const item of value) {
344
+ const record = asRecord(item);
345
+ const id = asString(record?.id).toLowerCase().replace(/[^a-z0-9_-]/g, "");
346
+ const kind = asString(record?.kind);
347
+ if (!record || !id || seen.has(id) || !isFieldKind2(kind)) continue;
348
+ seen.add(id);
349
+ const label = asString(record.label) || id;
350
+ fields.push({
351
+ id,
352
+ kind,
353
+ label,
354
+ placeholder: asString(record.placeholder) || label,
355
+ required: record.required !== false,
356
+ ...asString(record.autocomplete) ? { autocomplete: asString(record.autocomplete) } : {}
357
+ });
358
+ if (fields.length >= MAX_FORM_FIELDS) break;
359
+ }
360
+ return fields;
361
+ }
362
+ function readComposerControlValue(event) {
363
+ const node = event.target ?? event.currentTarget ?? null;
364
+ if (node && typeof node === "object" && "value" in node && typeof node.value === "string") {
365
+ return node.value;
366
+ }
367
+ return "";
368
+ }
369
+ function isValidComposerFieldValue(field, value) {
370
+ const trimmed = value.trim();
371
+ if (!trimmed) return !field.required;
372
+ if (field.kind === "email") return EMAIL_PATTERN.test(trimmed);
373
+ if (field.kind === "tel") return trimmed.replace(/\D/g, "").length >= 7;
374
+ return trimmed.length > 0;
375
+ }
376
+ function isComposerFormComplete(form, values) {
377
+ return form.fields.every(
378
+ (field) => isValidComposerFieldValue(field, values[field.id] ?? "")
379
+ );
380
+ }
381
+ function formatComposerFormMessage(form, values) {
382
+ return form.fields.map((field) => {
383
+ const value = (values[field.id] ?? "").trim();
384
+ return value ? `${field.label}: ${value}` : "";
385
+ }).filter(Boolean).join("\n");
386
+ }
387
+ function looksLikeFieldCollection(text) {
388
+ return /\b(please|need|send|share|enter|provide|add|collect|what|which|use)\b/i.test(
389
+ text
390
+ ) || /:\s*$/m.test(text) || /^[-*•]\s+/m.test(text);
391
+ }
392
+ function looksLikeBookingCopy(text) {
393
+ return /book(ing)? (card|a demo|this time)|pick a (date|time)|available (times|slots)|calendly/i.test(
394
+ text
395
+ );
396
+ }
397
+ function inferComposerForm(text) {
398
+ const cleaned = text.trim();
399
+ if (!cleaned || looksLikeBookingCopy(cleaned) || !looksLikeFieldCollection(cleaned)) {
400
+ return null;
401
+ }
402
+ const fields = FIELD_LIBRARY.flatMap((field) => {
403
+ if (field.exclude?.test(cleaned)) {
404
+ const leftover = cleaned.replace(field.exclude, " ");
405
+ if (!field.patterns.some((pattern) => pattern.test(leftover))) return [];
406
+ } else if (!field.patterns.some((pattern) => pattern.test(cleaned))) {
407
+ return [];
408
+ }
409
+ const { patterns: _patterns, exclude: _exclude, ...next } = field;
410
+ return [next];
411
+ }).slice(0, MAX_FORM_FIELDS);
412
+ if (fields.length < MIN_FORM_FIELDS) return null;
413
+ return {
414
+ id: `inferred:${fields.map((field) => field.id).join("+")}`,
415
+ fields
416
+ };
417
+ }
418
+ function resolveComposerForm(input) {
419
+ if (input.enabled === false || input.hasBookingOffer) return null;
420
+ const text = input.agentText.trim();
421
+ if (!text) return null;
422
+ const card = input.cards?.find((item) => item.type === "visitor_form");
423
+ if (card?.fields && card.fields.length >= MIN_FORM_FIELDS) {
424
+ return {
425
+ id: `card:${card.fields.map((field) => field.id).join("+")}`,
426
+ fields: card.fields.slice(0, MAX_FORM_FIELDS)
427
+ };
428
+ }
429
+ return inferComposerForm(text);
430
+ }
431
+
432
+ // src/react/lib/tool-card.ts
433
+ function preferBookingOffer(current, next) {
434
+ if (!current) return next;
435
+ if (next.slots.length !== current.slots.length) {
436
+ return next.slots.length > current.slots.length ? next : current;
437
+ }
438
+ if (next.eventTypes.length !== current.eventTypes.length) {
439
+ return next.eventTypes.length > current.eventTypes.length ? next : current;
440
+ }
441
+ return next;
442
+ }
443
+ function looksLikeBookingReady(text) {
444
+ return /demo option|options are ready|pick a (date|time)|available (times|slots)|book(ing)? (card|the demo)|schedule/i.test(
445
+ text
446
+ );
447
+ }
448
+ function bookingOfferIdentityKey(offer) {
449
+ const eventTypes = offer.eventTypes.map(
450
+ (item) => `${item.uri}:${item.duration ?? ""}:${item.locationKind ?? ""}:${item.location ?? ""}`
451
+ ).join("|");
452
+ const slots = offer.slots.map((slot) => `${slot.eventTypeUri ?? ""}:${slot.startTime}`).join("|");
453
+ return `${eventTypes}::${slots}` || "offer";
454
+ }
455
+ var FENCE_PATTERN = /```(?:webless-tool-card|json)\s*([\s\S]*?)```/gi;
456
+ function asRecord2(value) {
457
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
458
+ }
459
+ function asString2(value) {
460
+ return typeof value === "string" ? value.trim() : "";
461
+ }
462
+ function isEventUri(value) {
463
+ return /^https:\/\/api\.calendly\.com\/scheduled_events\/[^/]+$/i.test(value);
464
+ }
465
+ function isEventTypeUri(value) {
466
+ return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
467
+ }
468
+ function parseToolCard(value) {
469
+ const record = asRecord2(value);
470
+ if (!record) return null;
471
+ if (record.booking_offer && asString2(record.type) !== "booking_offer") {
472
+ const nested = parseToolCard(record.booking_offer);
473
+ if (nested) return nested;
474
+ }
475
+ if (record.visitor_booking && asString2(record.type) !== "booking_confirmed") {
476
+ const nested = parseToolCard(record.visitor_booking);
477
+ if (nested) return nested;
478
+ }
479
+ const type = asString2(record.type);
480
+ if (type === "booking_offer") {
481
+ const eventTypes = Array.isArray(record.eventTypes) ? record.eventTypes.flatMap((item) => {
482
+ const entry = asRecord2(item);
483
+ const uri = asString2(entry?.uri);
484
+ if (!entry || !uri) return [];
485
+ const duration = entry.duration;
486
+ const locationKind = asString2(entry.locationKind);
487
+ const location = asString2(entry.location);
488
+ return [
489
+ {
490
+ name: asString2(entry.name) || "Meeting",
491
+ uri,
492
+ ...typeof duration === "number" ? { duration } : {},
493
+ ...locationKind ? { locationKind } : {},
494
+ ...location ? { location } : {}
495
+ }
496
+ ];
497
+ }) : [];
498
+ const slots = Array.isArray(record.slots) ? record.slots.flatMap((item) => {
499
+ const entry = asRecord2(item);
500
+ const startTime = asString2(entry?.startTime);
501
+ if (!entry || !startTime) return [];
502
+ const eventTypeUri = asString2(entry.eventTypeUri);
503
+ return [
504
+ {
505
+ startTime,
506
+ ...isEventTypeUri(eventTypeUri) ? { eventTypeUri } : {}
507
+ }
508
+ ];
509
+ }) : [];
510
+ if (eventTypes.length === 0 && slots.length === 0) return null;
511
+ return { type: "booking_offer", eventTypes, slots };
512
+ }
513
+ if (type === "booking_confirmed") {
514
+ const eventUri = asString2(record.eventUri);
515
+ if (!isEventUri(eventUri)) return null;
516
+ const inviteeUri = asString2(record.inviteeUri);
517
+ const inviteeEmail = asString2(record.inviteeEmail);
518
+ const startTime = asString2(record.startTime);
519
+ return {
520
+ type: "booking_confirmed",
521
+ eventUri,
522
+ ...inviteeUri ? { inviteeUri } : {},
523
+ ...inviteeEmail ? { inviteeEmail } : {},
524
+ ...startTime ? { startTime } : {}
525
+ };
526
+ }
527
+ if (type === "booking_canceled") {
528
+ const eventUri = asString2(record.eventUri);
529
+ if (!isEventUri(eventUri)) return null;
530
+ return { type: "booking_canceled", eventUri };
531
+ }
532
+ if (type === "visitor_form") {
533
+ const fields = parseVisitorFormFields(record.fields);
534
+ if (fields.length < 2) return null;
535
+ return { type: "visitor_form", fields };
536
+ }
537
+ return null;
538
+ }
539
+ function formatBookingOfferFence(offer) {
540
+ return [
541
+ "```webless-tool-card",
542
+ JSON.stringify({
543
+ type: "booking_offer",
544
+ eventTypes: offer.eventTypes,
545
+ slots: offer.slots
546
+ }),
547
+ "```"
548
+ ].join("\n");
549
+ }
550
+ function bookingCardFromActionOutput(output) {
551
+ const record = asRecord2(output);
552
+ const data = asRecord2(record?.data) ?? record;
553
+ return parseToolCard(data);
554
+ }
555
+ function ensureBookingOfferText(text, offer) {
556
+ if (!offer) return text;
557
+ if (extractToolCards(text).some((card) => card.type === "booking_offer")) {
558
+ return text;
559
+ }
560
+ const visible = stripToolCards(text).trim() || text.trim();
561
+ return `${visible}
562
+
563
+ ${formatBookingOfferFence(offer)}`;
564
+ }
565
+ function hideToolCardFences(text) {
566
+ 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();
567
+ }
568
+ var BOOKING_CARD_FALLBACK = "Pick a date and time that works for you.";
569
+ function looksLikeBookingAvailabilityDump(text) {
570
+ const cleaned = text.trim();
571
+ if (!cleaned) return false;
572
+ const isoCount = (cleaned.match(/\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/g) ?? []).length;
573
+ const utcCount = (cleaned.match(/\bUTC\b/g) ?? []).length;
574
+ return isoCount >= 2 || utcCount >= 2 || /api\.calendly\.com|calendly\.com\//i.test(cleaned) || /webless-tool-card/i.test(cleaned);
575
+ }
576
+ function sanitizeBookingOfferCopy(text) {
577
+ const cleaned = hideToolCardFences(text);
578
+ if (!cleaned || looksLikeBookingAvailabilityDump(cleaned)) {
579
+ return BOOKING_CARD_FALLBACK;
580
+ }
581
+ return cleaned;
582
+ }
583
+ function visitorTimeZone() {
584
+ try {
585
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
586
+ } catch {
587
+ return "UTC";
588
+ }
589
+ }
590
+ function extractToolCards(text) {
591
+ const cards = [];
592
+ for (const match of text.matchAll(FENCE_PATTERN)) {
593
+ try {
594
+ const card = parseToolCard(JSON.parse(match[1] ?? ""));
595
+ if (card) cards.push(card);
596
+ } catch {
597
+ }
598
+ }
599
+ return cards;
600
+ }
601
+ function stripToolCards(text) {
602
+ return text.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
603
+ }
604
+ function localDateKey(date) {
605
+ if (Number.isNaN(date.getTime())) return "";
606
+ return [
607
+ date.getFullYear(),
608
+ String(date.getMonth() + 1).padStart(2, "0"),
609
+ String(date.getDate()).padStart(2, "0")
610
+ ].join("-");
611
+ }
612
+ function slotDateKey(startTime) {
613
+ return localDateKey(new Date(startTime)) || startTime;
614
+ }
615
+ function bookingSlotsForEventType(slots, eventTypeUri) {
616
+ return slots.filter(
617
+ (slot) => !eventTypeUri || !slot.eventTypeUri || slot.eventTypeUri === eventTypeUri
618
+ );
619
+ }
620
+ function firstAvailableBookingMonth(slots) {
621
+ let earliest;
622
+ for (const slot of slots) {
623
+ const key = slotDateKey(slot.startTime);
624
+ if (!earliest || key < earliest) earliest = key;
625
+ }
626
+ const [year, month] = (earliest ?? slotDateKey((/* @__PURE__ */ new Date()).toISOString())).split("-").map(Number);
627
+ if (!year || !month) {
628
+ const now = /* @__PURE__ */ new Date();
629
+ return { year: now.getFullYear(), month: now.getMonth() };
630
+ }
631
+ return { year, month: month - 1 };
632
+ }
633
+ function formatMonthTitle(year, month) {
634
+ return new Intl.DateTimeFormat(void 0, {
635
+ month: "long",
636
+ year: "numeric"
637
+ }).format(new Date(year, month, 1));
638
+ }
639
+ function formatLongDate(startTime) {
640
+ const date = new Date(startTime);
641
+ if (Number.isNaN(date.getTime())) return startTime;
642
+ return new Intl.DateTimeFormat(void 0, {
643
+ weekday: "long",
644
+ month: "long",
645
+ day: "numeric"
646
+ }).format(date);
647
+ }
648
+ function weekdayLabels() {
649
+ return Array.from(
650
+ { length: 7 },
651
+ (_, index) => new Intl.DateTimeFormat(void 0, { weekday: "short" }).format(
652
+ new Date(2026, 7, 3 + index)
653
+ )
654
+ );
655
+ }
656
+ function formatTimeChip(startTime) {
657
+ const date = new Date(startTime);
658
+ if (Number.isNaN(date.getTime())) return startTime;
659
+ return new Intl.DateTimeFormat(void 0, {
660
+ hour: "numeric",
661
+ minute: "2-digit"
662
+ }).format(date);
663
+ }
664
+ function formatSlotTimeZone(startTime) {
665
+ const date = new Date(startTime);
666
+ if (Number.isNaN(date.getTime())) return "";
667
+ return new Intl.DateTimeFormat(void 0, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
668
+ }
669
+ function formatSlotLabel(startTime) {
670
+ const date = new Date(startTime);
671
+ if (Number.isNaN(date.getTime())) return startTime;
672
+ return new Intl.DateTimeFormat(void 0, {
673
+ weekday: "short",
674
+ month: "short",
675
+ day: "numeric",
676
+ hour: "numeric",
677
+ minute: "2-digit",
678
+ timeZoneName: "short"
679
+ }).format(date);
680
+ }
681
+ function formatBookingRequest(input) {
682
+ return [
683
+ "Book this meeting now with CALENDLY_POST_INVITEE.",
684
+ "Execute CALENDLY_POST_INVITEE in this turn with the fields below.",
685
+ "Do not open a Calendly URL and do not list other scheduled events.",
686
+ "Do not invent a location kind. Use only the location fields below.",
687
+ `event_type: ${input.eventTypeUri}`,
688
+ `start_time: ${input.startTime}`,
689
+ `invitee.name: ${input.inviteeName}`,
690
+ `invitee.email: ${input.inviteeEmail}`,
691
+ `invitee.timezone: ${input.timezone}`,
692
+ ...input.locationKind ? [
693
+ `location.kind: ${input.locationKind}`,
694
+ ...input.location ? [`location.location: ${input.location}`] : []
695
+ ] : ["Do not send a location field."],
696
+ "After it succeeds, reply with one short confirmation and a webless-tool-card booking_confirmed block using visitor_booking."
697
+ ].join("\n");
698
+ }
699
+ function visitorBookingPrefix(booking) {
700
+ return [
701
+ "This visitor already booked a meeting. Use only this meeting:",
702
+ `- scheduled event URI: ${booking.eventUri}`,
703
+ ...booking.inviteeUri ? [`- invitee URI: ${booking.inviteeUri}`] : [],
704
+ ...booking.inviteeEmail ? [`- invitee email: ${booking.inviteeEmail}`] : [],
705
+ "For details call CALENDLY_GET_EVENT or CALENDLY_GET_EVENT_INVITEE with those URIs.",
706
+ "If you must list events, pass this invitee_email. Never describe any other scheduled event.",
707
+ "start_time values from Calendly are UTC."
708
+ ].join("\n");
709
+ }
710
+
711
+ // src/react/lib/tool-result.ts
712
+ var MAX_TEXT_LENGTH = 240;
713
+ var MAX_DETAILS = 6;
714
+ var MAX_LINKS = 4;
715
+ var MAX_COLLECTION_ITEMS = 8;
716
+ 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;
717
+ function safeText(value) {
718
+ if (typeof value !== "string") return "";
719
+ return value.trim().slice(0, MAX_TEXT_LENGTH);
720
+ }
721
+ function safeHref(value) {
722
+ const text = safeText(value);
723
+ if (!text) return "";
724
+ try {
725
+ const url = new URL(text);
726
+ return url.protocol === "https:" ? url.toString() : "";
727
+ } catch {
728
+ return "";
729
+ }
730
+ }
731
+ function fallbackTitle(result) {
732
+ if (result.status === "failed") return "Couldn\u2019t complete this action";
733
+ if (result.status === "rejected") return "Action not approved";
734
+ return "Action completed";
735
+ }
736
+ function bookingPresenter(kind) {
737
+ return {
738
+ kind,
739
+ present: ({ envelope }) => {
740
+ const card = bookingCardFromActionOutput(envelope.output);
741
+ return card && card.type === kind ? { kind: "booking", card } : null;
742
+ }
743
+ };
744
+ }
745
+ function asRecord3(value) {
746
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
747
+ }
748
+ function parseFacts(value) {
749
+ if (!Array.isArray(value)) return [];
750
+ return value.flatMap((item) => {
751
+ const fact = asRecord3(item);
752
+ const label = safeText(fact?.label);
753
+ const factValue = safeText(fact?.value);
754
+ if (!label || !factValue) return [];
755
+ if (INTERNAL_FACT_LABEL.test(label)) return [];
756
+ return [{ label, value: factValue }];
757
+ });
758
+ }
759
+ function parseLinks(output, defaultLabel) {
760
+ const href = safeHref(output.href);
761
+ const linkLabel = safeText(output.linkLabel) || defaultLabel;
762
+ return href ? [{ label: linkLabel, href }] : [];
763
+ }
764
+ function entityResultPresenter() {
765
+ return {
766
+ kind: "entity_result",
767
+ present: ({ envelope, result }) => {
768
+ const output = asRecord3(envelope.output);
769
+ if (!output) return null;
770
+ const title = safeText(output.title) || fallbackTitle(result);
771
+ const description = safeText(output.description);
772
+ const facts = parseFacts(output.facts).filter((fact) => fact.value !== description && fact.value !== title).slice(0, MAX_DETAILS);
773
+ return {
774
+ kind: "entity",
775
+ title,
776
+ ...description && description !== title ? { description } : {},
777
+ ...facts.length ? { details: facts } : {},
778
+ ...parseLinks(output, "Open record").length ? { links: parseLinks(output, "Open record") } : {}
779
+ };
780
+ }
781
+ };
782
+ }
783
+ function collectionResultPresenter() {
784
+ return {
785
+ kind: "collection",
786
+ present: ({ envelope, result }) => {
787
+ const output = asRecord3(envelope.output);
788
+ if (!output) return null;
789
+ const title = safeText(output.title) || fallbackTitleFromKind("collection");
790
+ const items = Array.isArray(output.items) ? output.items.slice(0, MAX_COLLECTION_ITEMS).flatMap((item) => {
791
+ const entry = asRecord3(item);
792
+ if (!entry) return [];
793
+ const itemTitle = safeText(entry.title);
794
+ if (!itemTitle) return [];
795
+ const description = safeText(entry.description);
796
+ const details = parseFacts(entry.facts).slice(0, MAX_DETAILS);
797
+ const href = safeHref(entry.href);
798
+ return [
799
+ {
800
+ title: itemTitle,
801
+ ...description ? { description } : {},
802
+ ...details.length ? { details } : {},
803
+ ...href ? { href } : {}
804
+ }
805
+ ];
806
+ }) : [];
807
+ return {
808
+ kind: "collection",
809
+ title,
810
+ items
811
+ };
812
+ }
813
+ };
814
+ }
815
+ function signatureResultPresenter() {
816
+ return {
817
+ kind: "signature",
818
+ present: ({ envelope, result }) => {
819
+ const output = asRecord3(envelope.output);
820
+ if (!output) return null;
821
+ const title = safeText(output.title) || fallbackTitleFromKind("signature");
822
+ const description = safeText(output.description);
823
+ const statusLabel = safeText(output.status);
824
+ return {
825
+ kind: "signature",
826
+ title,
827
+ ...description ? { description } : {},
828
+ ...statusLabel ? { statusLabel } : {},
829
+ ...parseLinks(output, "Sign now").length ? { links: parseLinks(output, "Sign now") } : {}
830
+ };
831
+ }
832
+ };
833
+ }
834
+ function resultCardPresenter(kind) {
835
+ return {
836
+ kind,
837
+ present: ({ envelope, result }) => {
838
+ const output = asRecord3(envelope.output);
839
+ if (!output) return null;
840
+ const title = safeText(output.title) || fallbackTitleFromKind(kind);
841
+ const description = safeText(output.description);
842
+ const facts = parseFacts(output.facts).slice(0, MAX_DETAILS);
843
+ return {
844
+ kind: "summary",
845
+ title,
846
+ ...description && description !== title ? { description } : {},
847
+ ...facts.length ? { details: facts } : {},
848
+ ...parseLinks(output, "Open").length ? { links: parseLinks(output, "Open") } : {}
849
+ };
850
+ }
851
+ };
852
+ }
853
+ function fallbackTitleFromKind(kind) {
854
+ if (kind === "document") return "Document ready";
855
+ if (kind === "signature") return "Ready to sign";
856
+ if (kind === "payment") return "Payment link";
857
+ if (kind === "collection") return "Results";
858
+ if (kind === "confirmation_result") return "Confirmed";
859
+ return "Saved";
860
+ }
861
+ var builtInVisitorToolResultRegistry = [
862
+ bookingPresenter("booking_offer"),
863
+ bookingPresenter("booking_confirmed"),
864
+ bookingPresenter("booking_canceled"),
865
+ entityResultPresenter(),
866
+ collectionResultPresenter(),
867
+ signatureResultPresenter(),
868
+ resultCardPresenter("document"),
869
+ resultCardPresenter("payment"),
870
+ resultCardPresenter("confirmation_result")
871
+ ];
872
+ function resolvePresenterOutput(result, envelope, registry) {
873
+ const presenters = [...registry, ...builtInVisitorToolResultRegistry];
874
+ for (const presentationKind of envelope.presentationKinds) {
875
+ const presenter = presenters.find(
876
+ (candidate) => candidate.kind === presentationKind
877
+ );
878
+ if (!presenter) continue;
879
+ try {
880
+ const presented = presenter.present({
881
+ envelope,
882
+ presentationKind,
883
+ result
884
+ });
885
+ if (presented) return presented;
886
+ } catch {
887
+ return null;
888
+ }
889
+ }
890
+ return null;
891
+ }
892
+ function finalizeSummaryPresentation(result, proposed) {
893
+ const title = safeText(proposed.title) || fallbackTitle(result);
894
+ const description = safeText(proposed.description);
895
+ const details = proposed.details?.slice(0, MAX_DETAILS).flatMap((detail) => {
896
+ const label = safeText(detail.label);
897
+ const value = safeText(detail.value);
898
+ if (!label || !value) return [];
899
+ if (INTERNAL_FACT_LABEL.test(label)) return [];
900
+ if (value === title || value === description) return [];
901
+ return [{ label, value }];
902
+ });
903
+ const links = proposed.links?.slice(0, MAX_LINKS).flatMap((link) => {
904
+ const label = safeText(link.label);
905
+ const href = safeHref(link.href);
906
+ return label && href ? [{ label, href }] : [];
907
+ });
908
+ return {
909
+ id: result.callId,
910
+ toolName: result.toolName,
911
+ status: result.status,
912
+ kind: "summary",
913
+ title,
914
+ ...description && description !== title ? { description } : {},
915
+ ...details?.length ? { details } : {},
916
+ ...links?.length ? { links } : {}
917
+ };
918
+ }
919
+ function presentVisitorToolResult(result, registry = []) {
920
+ const envelope = parseAgentToolResultEnvelope(result.output) ?? legacyBookingEnvelope(result.output);
921
+ const proposed = envelope ? resolvePresenterOutput(result, envelope, registry) : null;
922
+ if (proposed?.kind === "booking") {
923
+ return {
924
+ id: result.callId,
925
+ toolName: result.toolName,
926
+ status: result.status,
927
+ kind: "booking",
928
+ card: proposed.card
929
+ };
930
+ }
931
+ if (envelope?.ui && envelope.presentationKinds.includes("tool_input")) {
932
+ return {
933
+ id: result.callId,
934
+ toolName: result.toolName,
935
+ status: result.status,
936
+ kind: "hidden"
937
+ };
938
+ }
939
+ if (!proposed) {
940
+ if (result.status === "failed" || result.status === "rejected") {
941
+ return {
942
+ id: result.callId,
943
+ toolName: result.toolName,
944
+ status: result.status,
945
+ kind: "summary",
946
+ title: fallbackTitle(result)
947
+ };
948
+ }
949
+ return {
950
+ id: result.callId,
951
+ toolName: result.toolName,
952
+ status: result.status,
953
+ kind: "hidden"
954
+ };
955
+ }
956
+ if (proposed.kind === "entity") {
957
+ return {
958
+ id: result.callId,
959
+ toolName: result.toolName,
960
+ status: result.status,
961
+ ...proposed
962
+ };
963
+ }
964
+ if (proposed.kind === "collection") {
965
+ return {
966
+ id: result.callId,
967
+ toolName: result.toolName,
968
+ status: result.status,
969
+ ...proposed
970
+ };
971
+ }
972
+ if (proposed.kind === "signature") {
973
+ return {
974
+ id: result.callId,
975
+ toolName: result.toolName,
976
+ status: result.status,
977
+ ...proposed
978
+ };
979
+ }
980
+ return finalizeSummaryPresentation(result, proposed);
981
+ }
982
+ function legacyBookingEnvelope(output) {
983
+ const card = bookingCardFromActionOutput(output);
984
+ return card ? {
985
+ schemaVersion: "webless.tool-result.v1",
986
+ output: card,
987
+ presentationKinds: [card.type]
988
+ } : null;
989
+ }
990
+
1
991
  // src/react/hooks/useAgentChat.ts
2
992
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
993
 
@@ -10,11 +1000,31 @@ import {
10
1000
  // src/runtime/capability.ts
11
1001
  import { ClientError } from "eve/client";
12
1002
  var MAX_REFRESH_SKEW_MS = 3e4;
13
- function isRecord(value) {
1003
+ var LOCAL_LOOPBACK_ORIGINS = [
1004
+ "http://127.0.0.1:3010",
1005
+ "http://127.0.0.1:3001"
1006
+ ];
1007
+ function isLoopbackRuntimeOrigin(origin) {
1008
+ try {
1009
+ const host = new URL(origin).hostname;
1010
+ return host === "127.0.0.1" || host === "localhost";
1011
+ } catch {
1012
+ return false;
1013
+ }
1014
+ }
1015
+ function localBootstrapOrigins(origin) {
1016
+ const normalized = origin.replace(/\/$/, "");
1017
+ if (!isLoopbackRuntimeOrigin(normalized)) return [normalized];
1018
+ return [
1019
+ normalized,
1020
+ ...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
1021
+ ];
1022
+ }
1023
+ function isRecord3(value) {
14
1024
  return typeof value === "object" && value !== null && !Array.isArray(value);
15
1025
  }
16
1026
  function parseBootstrapResponse(value, indexId, now) {
17
- if (!isRecord(value) || value.apiVersion !== "webless.ai/agent-runtime-bootstrap/v1" || typeof value.accessToken !== "string" || !value.accessToken || typeof value.expiresAt !== "string" || !isRecord(value.identity) || value.identity.indexId !== indexId || typeof value.identity.revision !== "string" || !value.identity.revision || typeof value.identity.tenantId !== "string" || !value.identity.tenantId || typeof value.origin !== "string" || !value.origin || value.tokenType !== "Bearer" || typeof value.visitorSubject !== "string" || !value.visitorSubject) {
1027
+ if (!isRecord3(value) || value.apiVersion !== "webless.ai/agent-runtime-bootstrap/v1" || typeof value.accessToken !== "string" || !value.accessToken || typeof value.expiresAt !== "string" || !isRecord3(value.identity) || value.identity.indexId !== indexId || typeof value.identity.revision !== "string" || !value.identity.revision || typeof value.identity.tenantId !== "string" || !value.identity.tenantId || typeof value.origin !== "string" || !value.origin || value.tokenType !== "Bearer" || typeof value.visitorSubject !== "string" || !value.visitorSubject) {
18
1028
  throw new Error("Agent Runtime returned an invalid access response.");
19
1029
  }
20
1030
  const expiresAt = Date.parse(value.expiresAt);
@@ -34,7 +1044,7 @@ async function readBootstrapError(response) {
34
1044
  const fallback = `Agent Runtime is unavailable (${response.status}).`;
35
1045
  try {
36
1046
  const value = await response.json();
37
- return isRecord(value) && typeof value.error === "string" && value.error ? value.error : fallback;
1047
+ return isRecord3(value) && typeof value.error === "string" && value.error ? value.error : fallback;
38
1048
  } catch {
39
1049
  return fallback;
40
1050
  }
@@ -56,20 +1066,31 @@ function createAgentRuntimeCapability(options) {
56
1066
  );
57
1067
  }
58
1068
  }
59
- const response = await fetchImplementation(
60
- `${options.runtimeOrigin}/webless/v1/bootstrap`,
61
- {
62
- body: JSON.stringify({
63
- clientSessionId: options.visitorSessionId,
64
- indexId: options.indexId,
65
- ...previewBuildId ? { previewBuildId } : {},
66
- ...previewGrant ? { previewGrant } : {},
67
- version: options.version
68
- }),
69
- headers: { "content-type": "application/json" },
70
- method: "POST"
1069
+ const bootstrapBody = JSON.stringify({
1070
+ clientSessionId: options.visitorSessionId,
1071
+ indexId: options.indexId,
1072
+ ...previewBuildId ? { previewBuildId } : {},
1073
+ ...previewGrant ? { previewGrant } : {},
1074
+ version: options.version
1075
+ });
1076
+ const postBootstrap = (origin) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
1077
+ body: bootstrapBody,
1078
+ headers: { "content-type": "application/json" },
1079
+ method: "POST"
1080
+ });
1081
+ let response;
1082
+ let lastError;
1083
+ for (const origin of localBootstrapOrigins(options.runtimeOrigin)) {
1084
+ try {
1085
+ response = await postBootstrap(origin);
1086
+ break;
1087
+ } catch (error) {
1088
+ lastError = error;
71
1089
  }
72
- );
1090
+ }
1091
+ if (!response) {
1092
+ throw lastError instanceof Error ? lastError : new Error("Agent Runtime is unavailable.");
1093
+ }
73
1094
  if (!response.ok) {
74
1095
  throw new Error(await readBootstrapError(response));
75
1096
  }
@@ -243,12 +1264,8 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
243
1264
  if (event.type === "message.completed") {
244
1265
  handlers.onComplete?.();
245
1266
  }
246
- if (event.type === "action.result") {
247
- const result = event.data.result;
248
- if (result && typeof result === "object" && "output" in result) {
249
- handlers.onActionResult?.(result.output);
250
- }
251
- }
1267
+ if (event.type === "action.result") emitActionResult(event, handlers);
1268
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
252
1269
  if (event.type !== "message.appended") return rendered;
253
1270
  const { messageDelta, messageSoFar } = event.data;
254
1271
  let delta = messageDelta;
@@ -259,9 +1276,45 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
259
1276
  } else if (messageDelta) {
260
1277
  next += messageDelta;
261
1278
  }
262
- if (delta) handlers.onDelta(delta);
1279
+ if (delta) handlers.onDelta?.(delta);
263
1280
  return next;
264
1281
  }
1282
+ function emitActionResult(event, handlers) {
1283
+ const result = event.data.result;
1284
+ if (result.kind === "tool-result") {
1285
+ handlers.onToolResult?.({
1286
+ callId: result.callId,
1287
+ toolName: result.toolName,
1288
+ status: event.data.status,
1289
+ output: result.output,
1290
+ ...event.data.error ? { error: event.data.error } : {}
1291
+ });
1292
+ }
1293
+ if ("output" in result) handlers.onActionResult?.(result.output);
1294
+ }
1295
+ function emitInputRequests(event, handlers) {
1296
+ handlers.onInputRequest?.(
1297
+ event.data.requests.map((request) => {
1298
+ const ui = parseAgentToolUiSurface(
1299
+ request.ui
1300
+ );
1301
+ return {
1302
+ requestId: request.requestId,
1303
+ kind: request.kind,
1304
+ prompt: request.prompt,
1305
+ ...request.display ? { display: request.display } : {},
1306
+ ...request.allowFreeform !== void 0 ? { allowFreeform: request.allowFreeform } : {},
1307
+ ...request.options ? { options: request.options } : {},
1308
+ ...ui ? { ui } : {},
1309
+ action: {
1310
+ callId: request.action.callId,
1311
+ kind: "tool-call",
1312
+ toolName: request.action.toolName
1313
+ }
1314
+ };
1315
+ })
1316
+ );
1317
+ }
265
1318
  function isResumeTurnMessage(received, candidate) {
266
1319
  if (received === candidate) return true;
267
1320
  return Boolean(candidate) && received.endsWith(`
@@ -537,9 +1590,11 @@ var AgentSession = class {
537
1590
  let streamIndex = session?.state.streamIndex ?? 0;
538
1591
  let rendered = "";
539
1592
  const workItems = /* @__PURE__ */ new Map();
1593
+ let requestedInput = false;
540
1594
  try {
541
1595
  for await (const event of response) {
542
1596
  if (signal.aborted) break;
1597
+ if (event.type === "input.requested") requestedInput = true;
543
1598
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
544
1599
  streamIndex += 1;
545
1600
  if (session) {
@@ -557,7 +1612,7 @@ var AgentSession = class {
557
1612
  this.persistSessionCursor(session);
558
1613
  }
559
1614
  }
560
- if (!rendered.trim() && !signal.aborted) {
1615
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
561
1616
  throw new Error("Empty response from runtime");
562
1617
  }
563
1618
  return rendered.trim();
@@ -577,6 +1632,9 @@ var AgentSession = class {
577
1632
  () => attached.snapshot({ signal })
578
1633
  );
579
1634
  const turnEvents = latestTurnEvents(snapshot.events);
1635
+ const hasInputRequest = turnEvents.some(
1636
+ (event) => event.type === "input.requested"
1637
+ );
580
1638
  const received = turnEvents[0];
581
1639
  const lastSent = persisted.lastMessage;
582
1640
  const inFlight = !turnEvents.some((event) => isTurnBoundary(event));
@@ -587,6 +1645,8 @@ var AgentSession = class {
587
1645
  const workItems = /* @__PURE__ */ new Map();
588
1646
  for (const event of turnEvents) {
589
1647
  applyWorkEvent(event, handlers, workItems);
1648
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
1649
+ if (event.type === "action.result") emitActionResult(event, handlers);
590
1650
  }
591
1651
  if (rendered.startsWith(initialText)) {
592
1652
  const missedText = rendered.slice(initialText.length);
@@ -616,7 +1676,9 @@ var AgentSession = class {
616
1676
  );
617
1677
  }
618
1678
  handlers.onComplete?.();
619
- if (!rendered.trim()) throw new Error("Empty response from runtime");
1679
+ if (!rendered.trim() && !hasInputRequest) {
1680
+ throw new Error("Empty response from runtime");
1681
+ }
620
1682
  return rendered.trim();
621
1683
  }
622
1684
  let streamIndex = snapshot.session.streamIndex;
@@ -635,7 +1697,55 @@ var AgentSession = class {
635
1697
  session = client.sessions.attach(session.state.sessionId, { streamIndex });
636
1698
  this.session = session;
637
1699
  this.persistSessionCursor(session);
638
- if (!rendered.trim() && !signal.aborted) {
1700
+ if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
1701
+ throw new Error("Empty response from runtime");
1702
+ }
1703
+ return rendered.trim();
1704
+ }
1705
+ async respondTurn(responses, signal, handlers) {
1706
+ const client = this.ensureClient();
1707
+ const session = this.session ?? this.attachPersistedSession(client);
1708
+ if (!session) {
1709
+ throw new Error("No active session is waiting for input.");
1710
+ }
1711
+ this.session = session;
1712
+ const inputResponses = responses.map(
1713
+ ({ requestId, optionId, text }) => ({
1714
+ requestId,
1715
+ ...optionId ? { optionId } : {},
1716
+ ...text ? { text } : {}
1717
+ })
1718
+ );
1719
+ const response = await withCapabilityRefresh(
1720
+ this.capability,
1721
+ () => session.respond(inputResponses, { signal })
1722
+ );
1723
+ this.activeResponse = response;
1724
+ let streamIndex = session.state.streamIndex;
1725
+ let rendered = "";
1726
+ let requestedInput = false;
1727
+ const workItems = /* @__PURE__ */ new Map();
1728
+ try {
1729
+ for await (const event of response) {
1730
+ if (signal.aborted) break;
1731
+ if (event.type === "input.requested") requestedInput = true;
1732
+ rendered = applyMessageEvent(event, rendered, handlers, workItems);
1733
+ streamIndex += 1;
1734
+ savePersistedAgentSession(
1735
+ this.visitorSessionId,
1736
+ session.state.sessionId,
1737
+ streamIndex,
1738
+ this.storeOptions
1739
+ );
1740
+ }
1741
+ } finally {
1742
+ this.activeResponse = void 0;
1743
+ this.session = client.sessions.attach(session.state.sessionId, {
1744
+ streamIndex
1745
+ });
1746
+ this.persistSessionCursor(this.session);
1747
+ }
1748
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
639
1749
  throw new Error("Empty response from runtime");
640
1750
  }
641
1751
  return rendered.trim();
@@ -695,297 +1805,62 @@ function createAgentClient(options) {
695
1805
  resumeOptions.handlers,
696
1806
  resumeOptions.initialText
697
1807
  ),
698
- reset: () => session.reset(),
699
- cancelActive: () => session.cancelActive(),
700
- getActiveSessionId: () => session.getActiveSessionId()
701
- };
702
- }
703
-
704
- // src/runtime/errors.ts
705
- import { ClientError as ClientError3 } from "eve/client";
706
- var TRANSIENT_AGENT_ERROR_MESSAGE = "I couldn\u2019t finish that answer. Please try again.";
707
- var PREVIEW_AUTHORIZATION_ERROR_MESSAGE = "This preview could not be authorized. Open the latest preview from Webless.";
708
- function isTransientRuntimeMessage(message) {
709
- const normalized = message.trim().toLowerCase();
710
- return normalized.includes("empty response from runtime") || normalized.includes("failed to fetch") || normalized.includes("networkerror") || normalized.includes("load failed");
711
- }
712
- function isPreviewAuthorizationMessage(message) {
713
- const normalized = message.trim().toLowerCase();
714
- return normalized.includes("unpublished preview authorization") || normalized.includes("unpublished preview grant") || normalized.includes("agent studio preview grant") || normalized.includes("preview authorization");
715
- }
716
- function formatAgentError(error) {
717
- if (error instanceof ClientError3) {
718
- if (error.status === 401 && error.code === "index_required") {
719
- return "Missing indexId \u2014 pass a published index id to createAgentClient().";
720
- }
721
- if (error.status === 403 && error.code === "agent_unavailable") {
722
- return "Agent unavailable for this index (disabled or unpublished).";
723
- }
724
- if (error.status === 409 && error.code === "session_not_active") {
725
- return "Session expired \u2014 send a new message to start again.";
726
- }
727
- if (error.message && isPreviewAuthorizationMessage(error.message)) {
728
- return PREVIEW_AUTHORIZATION_ERROR_MESSAGE;
729
- }
730
- if (error.status >= 500 || error.message && isTransientRuntimeMessage(error.message)) {
731
- return TRANSIENT_AGENT_ERROR_MESSAGE;
732
- }
733
- return error.message || "This assistant is unavailable right now.";
734
- }
735
- if (error instanceof DOMException && error.name === "AbortError") {
736
- return "";
737
- }
738
- if (error instanceof Error) {
739
- if (isPreviewAuthorizationMessage(error.message)) {
740
- return PREVIEW_AUTHORIZATION_ERROR_MESSAGE;
741
- }
742
- return isTransientRuntimeMessage(error.message) ? TRANSIENT_AGENT_ERROR_MESSAGE : error.message;
743
- }
744
- return TRANSIENT_AGENT_ERROR_MESSAGE;
745
- }
746
-
747
- // src/react/lib/tool-card.ts
748
- function bookingOfferIdentityKey(offer) {
749
- const eventTypes = offer.eventTypes.map(
750
- (item) => `${item.uri}:${item.duration ?? ""}:${item.locationKind ?? ""}:${item.location ?? ""}`
751
- ).join("|");
752
- const slots = offer.slots.map((slot) => `${slot.eventTypeUri ?? ""}:${slot.startTime}`).join("|");
753
- return `${eventTypes}::${slots}` || "offer";
754
- }
755
- var FENCE_PATTERN = /```(?:webless-tool-card|json)\s*([\s\S]*?)```/gi;
756
- function asRecord(value) {
757
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
758
- }
759
- function asString(value) {
760
- return typeof value === "string" ? value.trim() : "";
761
- }
762
- function isEventUri(value) {
763
- return /^https:\/\/api\.calendly\.com\/scheduled_events\/[^/]+$/i.test(value);
764
- }
765
- function isEventTypeUri(value) {
766
- return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
767
- }
768
- function parseToolCard(value) {
769
- const record = asRecord(value);
770
- if (!record) return null;
771
- if (record.booking_offer && asString(record.type) !== "booking_offer") {
772
- const nested = parseToolCard(record.booking_offer);
773
- if (nested) return nested;
774
- }
775
- const type = asString(record.type);
776
- if (type === "booking_offer") {
777
- const eventTypes = Array.isArray(record.eventTypes) ? record.eventTypes.flatMap((item) => {
778
- const entry = asRecord(item);
779
- const uri = asString(entry?.uri);
780
- if (!entry || !isEventTypeUri(uri)) return [];
781
- const duration = entry.duration;
782
- const locationKind = asString(entry.locationKind);
783
- const location = asString(entry.location);
784
- return [
785
- {
786
- name: asString(entry.name) || "Meeting",
787
- uri,
788
- ...typeof duration === "number" ? { duration } : {},
789
- ...locationKind ? { locationKind } : {},
790
- ...location ? { location } : {}
791
- }
792
- ];
793
- }) : [];
794
- const slots = Array.isArray(record.slots) ? record.slots.flatMap((item) => {
795
- const entry = asRecord(item);
796
- const startTime = asString(entry?.startTime);
797
- if (!entry || !startTime) return [];
798
- const eventTypeUri = asString(entry.eventTypeUri);
799
- return [
800
- {
801
- startTime,
802
- ...isEventTypeUri(eventTypeUri) ? { eventTypeUri } : {}
803
- }
804
- ];
805
- }) : [];
806
- if (slots.length === 0) return null;
807
- return { type: "booking_offer", eventTypes, slots };
808
- }
809
- if (type === "booking_confirmed") {
810
- const eventUri = asString(record.eventUri);
811
- if (!isEventUri(eventUri)) return null;
812
- const inviteeUri = asString(record.inviteeUri);
813
- const inviteeEmail = asString(record.inviteeEmail);
814
- const startTime = asString(record.startTime);
815
- return {
816
- type: "booking_confirmed",
817
- eventUri,
818
- ...inviteeUri ? { inviteeUri } : {},
819
- ...inviteeEmail ? { inviteeEmail } : {},
820
- ...startTime ? { startTime } : {}
821
- };
822
- }
823
- if (type === "booking_canceled") {
824
- const eventUri = asString(record.eventUri);
825
- if (!isEventUri(eventUri)) return null;
826
- return { type: "booking_canceled", eventUri };
827
- }
828
- return null;
829
- }
830
- function formatBookingOfferFence(offer) {
831
- return [
832
- "```webless-tool-card",
833
- JSON.stringify({
834
- type: "booking_offer",
835
- eventTypes: offer.eventTypes,
836
- slots: offer.slots
837
- }),
838
- "```"
839
- ].join("\n");
840
- }
841
- function bookingOfferFromActionOutput(output) {
842
- const record = asRecord(output);
843
- const data = asRecord(record?.data) ?? record;
844
- const card = parseToolCard(data);
845
- return card?.type === "booking_offer" ? card : null;
846
- }
847
- function ensureBookingOfferText(text, offer) {
848
- if (!offer) return text;
849
- if (extractToolCards(text).some((card) => card.type === "booking_offer")) {
850
- return text;
851
- }
852
- const visible = stripToolCards(text).trim() || text.trim();
853
- return `${visible}
854
-
855
- ${formatBookingOfferFence(offer)}`;
856
- }
857
- function hideToolCardFences(text) {
858
- 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();
859
- }
860
- function visitorTimeZone() {
861
- try {
862
- return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
863
- } catch {
864
- return "UTC";
865
- }
866
- }
867
- function extractToolCards(text) {
868
- const cards = [];
869
- for (const match of text.matchAll(FENCE_PATTERN)) {
870
- try {
871
- const card = parseToolCard(JSON.parse(match[1] ?? ""));
872
- if (card) cards.push(card);
873
- } catch {
874
- }
875
- }
876
- return cards;
877
- }
878
- function stripToolCards(text) {
879
- return text.replace(FENCE_PATTERN, "").replace(/\n{3,}/g, "\n\n").trim();
880
- }
881
- function localDateKey(date) {
882
- if (Number.isNaN(date.getTime())) return "";
883
- return [
884
- date.getFullYear(),
885
- String(date.getMonth() + 1).padStart(2, "0"),
886
- String(date.getDate()).padStart(2, "0")
887
- ].join("-");
888
- }
889
- function slotDateKey(startTime) {
890
- return localDateKey(new Date(startTime)) || startTime;
891
- }
892
- function bookingSlotsForEventType(slots, eventTypeUri) {
893
- return slots.filter(
894
- (slot) => !eventTypeUri || !slot.eventTypeUri || slot.eventTypeUri === eventTypeUri
895
- );
896
- }
897
- function firstAvailableBookingMonth(slots) {
898
- let earliest;
899
- for (const slot of slots) {
900
- const key = slotDateKey(slot.startTime);
901
- if (!earliest || key < earliest) earliest = key;
902
- }
903
- const [year, month] = (earliest ?? slotDateKey((/* @__PURE__ */ new Date()).toISOString())).split("-").map(Number);
904
- if (!year || !month) {
905
- const now = /* @__PURE__ */ new Date();
906
- return { year: now.getFullYear(), month: now.getMonth() };
907
- }
908
- return { year, month: month - 1 };
909
- }
910
- function formatMonthTitle(year, month) {
911
- return new Intl.DateTimeFormat(void 0, {
912
- month: "long",
913
- year: "numeric"
914
- }).format(new Date(year, month, 1));
915
- }
916
- function formatLongDate(startTime) {
917
- const date = new Date(startTime);
918
- if (Number.isNaN(date.getTime())) return startTime;
919
- return new Intl.DateTimeFormat(void 0, {
920
- weekday: "long",
921
- month: "long",
922
- day: "numeric"
923
- }).format(date);
924
- }
925
- function weekdayLabels() {
926
- return Array.from(
927
- { length: 7 },
928
- (_, index) => new Intl.DateTimeFormat(void 0, { weekday: "short" }).format(
929
- new Date(2026, 7, 3 + index)
930
- )
931
- );
932
- }
933
- function formatTimeChip(startTime) {
934
- const date = new Date(startTime);
935
- if (Number.isNaN(date.getTime())) return startTime;
936
- return new Intl.DateTimeFormat(void 0, {
937
- hour: "numeric",
938
- minute: "2-digit"
939
- }).format(date);
940
- }
941
- function formatSlotTimeZone(startTime) {
942
- const date = new Date(startTime);
943
- if (Number.isNaN(date.getTime())) return "";
944
- return new Intl.DateTimeFormat(void 0, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
945
- }
946
- function formatSlotLabel(startTime) {
947
- const date = new Date(startTime);
948
- if (Number.isNaN(date.getTime())) return startTime;
949
- return new Intl.DateTimeFormat(void 0, {
950
- weekday: "short",
951
- month: "short",
952
- day: "numeric",
953
- hour: "numeric",
954
- minute: "2-digit",
955
- timeZoneName: "short"
956
- }).format(date);
957
- }
958
- function formatBookingRequest(input) {
959
- return [
960
- "Book this meeting now with CALENDLY_POST_INVITEE.",
961
- "Do not open a Calendly URL and do not list other scheduled events.",
962
- "Do not invent a location kind. Use only the location fields below.",
963
- `event_type: ${input.eventTypeUri}`,
964
- `start_time: ${input.startTime}`,
965
- `invitee.name: ${input.inviteeName}`,
966
- `invitee.email: ${input.inviteeEmail}`,
967
- `invitee.timezone: ${input.timezone}`,
968
- ...input.locationKind ? [
969
- `location.kind: ${input.locationKind}`,
970
- ...input.location ? [`location.location: ${input.location}`] : []
971
- ] : ["Do not send a location field."],
972
- "After it succeeds, reply with one short confirmation and a webless-tool-card booking_confirmed block using visitor_booking."
973
- ].join("\n");
1808
+ respondTurn: (respondOptions) => session.respondTurn(
1809
+ respondOptions.responses,
1810
+ respondOptions.signal ?? new AbortController().signal,
1811
+ respondOptions.handlers
1812
+ ),
1813
+ reset: () => session.reset(),
1814
+ cancelActive: () => session.cancelActive(),
1815
+ getActiveSessionId: () => session.getActiveSessionId()
1816
+ };
974
1817
  }
975
- function visitorBookingPrefix(booking) {
976
- return [
977
- "This visitor already booked a meeting. Use only this meeting:",
978
- `- scheduled event URI: ${booking.eventUri}`,
979
- ...booking.inviteeUri ? [`- invitee URI: ${booking.inviteeUri}`] : [],
980
- ...booking.inviteeEmail ? [`- invitee email: ${booking.inviteeEmail}`] : [],
981
- "For details call CALENDLY_GET_EVENT or CALENDLY_GET_EVENT_INVITEE with those URIs.",
982
- "If you must list events, pass this invitee_email. Never describe any other scheduled event.",
983
- "start_time values from Calendly are UTC."
984
- ].join("\n");
1818
+
1819
+ // src/runtime/errors.ts
1820
+ import { ClientError as ClientError3 } from "eve/client";
1821
+ var TRANSIENT_AGENT_ERROR_MESSAGE = "I couldn\u2019t finish that answer. Please try again.";
1822
+ var PREVIEW_AUTHORIZATION_ERROR_MESSAGE = "This preview could not be authorized. Open the latest preview from Webless.";
1823
+ function isTransientRuntimeMessage(message) {
1824
+ const normalized = message.trim().toLowerCase();
1825
+ return normalized.includes("empty response from runtime") || normalized.includes("failed to fetch") || normalized.includes("networkerror") || normalized.includes("load failed");
1826
+ }
1827
+ function isPreviewAuthorizationMessage(message) {
1828
+ const normalized = message.trim().toLowerCase();
1829
+ return normalized.includes("unpublished preview authorization") || normalized.includes("unpublished preview grant") || normalized.includes("agent studio preview grant") || normalized.includes("preview authorization");
1830
+ }
1831
+ function formatAgentError(error) {
1832
+ if (error instanceof ClientError3) {
1833
+ if (error.status === 401 && error.code === "index_required") {
1834
+ return "Missing indexId \u2014 pass a published index id to createAgentClient().";
1835
+ }
1836
+ if (error.status === 403 && error.code === "agent_unavailable") {
1837
+ return "Agent unavailable for this index (disabled or unpublished).";
1838
+ }
1839
+ if (error.status === 409 && error.code === "session_not_active") {
1840
+ return "Session expired \u2014 send a new message to start again.";
1841
+ }
1842
+ if (error.message && isPreviewAuthorizationMessage(error.message)) {
1843
+ return PREVIEW_AUTHORIZATION_ERROR_MESSAGE;
1844
+ }
1845
+ if (error.status >= 500 || error.message && isTransientRuntimeMessage(error.message)) {
1846
+ return TRANSIENT_AGENT_ERROR_MESSAGE;
1847
+ }
1848
+ return error.message || "This assistant is unavailable right now.";
1849
+ }
1850
+ if (error instanceof DOMException && error.name === "AbortError") {
1851
+ return "";
1852
+ }
1853
+ if (error instanceof Error) {
1854
+ if (isPreviewAuthorizationMessage(error.message)) {
1855
+ return PREVIEW_AUTHORIZATION_ERROR_MESSAGE;
1856
+ }
1857
+ return isTransientRuntimeMessage(error.message) ? TRANSIENT_AGENT_ERROR_MESSAGE : error.message;
1858
+ }
1859
+ return TRANSIENT_AGENT_ERROR_MESSAGE;
985
1860
  }
986
1861
 
987
1862
  // src/react/persisted-conversation.ts
988
- var CONVERSATION_VERSION = 2;
1863
+ var CONVERSATION_VERSION = 3;
989
1864
  function conversationKey(storageKeyPrefix, visitorSessionId) {
990
1865
  return `${storageKeyPrefix}:conversation:${visitorSessionId}`;
991
1866
  }
@@ -1025,30 +1900,171 @@ function parseToolStep(value) {
1025
1900
  ...typeof record.detail === "string" && record.detail ? { detail: record.detail } : {}
1026
1901
  };
1027
1902
  }
1903
+ function parseInputOption(value) {
1904
+ if (typeof value !== "object" || value === null) return null;
1905
+ const record = value;
1906
+ if (typeof record.id !== "string" || typeof record.label !== "string") {
1907
+ return null;
1908
+ }
1909
+ return {
1910
+ id: record.id,
1911
+ label: record.label,
1912
+ ...typeof record.description === "string" ? { description: record.description } : {},
1913
+ ...record.style === "default" || record.style === "primary" || record.style === "danger" ? { style: record.style } : {}
1914
+ };
1915
+ }
1916
+ function parseInputRequest(value) {
1917
+ if (typeof value !== "object" || value === null) return null;
1918
+ const record = value;
1919
+ if (typeof record.requestId !== "string" || record.kind !== "question" && record.kind !== "session-limit" && record.kind !== "tool-approval" || typeof record.prompt !== "string") {
1920
+ return null;
1921
+ }
1922
+ const action = record.action;
1923
+ if (typeof action !== "object" || action === null) return null;
1924
+ const actionRecord = action;
1925
+ if (typeof actionRecord.callId !== "string" || actionRecord.kind !== "tool-call" || typeof actionRecord.toolName !== "string") {
1926
+ return null;
1927
+ }
1928
+ const options = Array.isArray(record.options) ? record.options.map(parseInputOption) : void 0;
1929
+ if (Array.isArray(record.options) && options?.some((option) => option === null)) {
1930
+ return null;
1931
+ }
1932
+ const ui = record.ui ? parseAgentToolUiSurface(record.ui) : void 0;
1933
+ if (record.ui && !ui) return null;
1934
+ return {
1935
+ requestId: record.requestId,
1936
+ kind: record.kind,
1937
+ prompt: record.prompt,
1938
+ action: {
1939
+ callId: actionRecord.callId,
1940
+ kind: "tool-call",
1941
+ toolName: actionRecord.toolName
1942
+ },
1943
+ ...record.display === "confirmation" || record.display === "select" || record.display === "text" ? { display: record.display } : {},
1944
+ ...record.allowFreeform === true ? { allowFreeform: true } : {},
1945
+ ...options && options.length > 0 ? {
1946
+ options: options.filter(
1947
+ (option) => option !== null
1948
+ )
1949
+ } : {},
1950
+ ...ui ? { ui } : {}
1951
+ };
1952
+ }
1953
+ function parseToolResult(value) {
1954
+ if (typeof value !== "object" || value === null) return null;
1955
+ const record = value;
1956
+ if (typeof record.id !== "string" || typeof record.toolName !== "string" || record.status !== "completed" && record.status !== "failed" && record.status !== "rejected") {
1957
+ return null;
1958
+ }
1959
+ if (record.kind === "input") {
1960
+ const surface = parseAgentToolUiSurface(
1961
+ record.surface
1962
+ );
1963
+ return surface ? {
1964
+ id: record.id,
1965
+ toolName: record.toolName,
1966
+ status: record.status,
1967
+ kind: "input",
1968
+ surface
1969
+ } : null;
1970
+ }
1971
+ if (record.kind === "entity" && typeof record.title === "string") {
1972
+ return {
1973
+ id: record.id,
1974
+ toolName: record.toolName,
1975
+ status: record.status,
1976
+ kind: "entity",
1977
+ title: record.title,
1978
+ ...typeof record.description === "string" ? { description: record.description } : {}
1979
+ };
1980
+ }
1981
+ if (record.kind === "collection" && typeof record.title === "string" && Array.isArray(record.items)) {
1982
+ return {
1983
+ id: record.id,
1984
+ toolName: record.toolName,
1985
+ status: record.status,
1986
+ kind: "collection",
1987
+ title: record.title,
1988
+ items: record.items.flatMap((item) => {
1989
+ if (typeof item !== "object" || item === null) return [];
1990
+ const entry = item;
1991
+ if (typeof entry.title !== "string") return [];
1992
+ return [
1993
+ {
1994
+ title: entry.title,
1995
+ ...typeof entry.description === "string" ? { description: entry.description } : {},
1996
+ ...typeof entry.href === "string" ? { href: entry.href } : {}
1997
+ }
1998
+ ];
1999
+ })
2000
+ };
2001
+ }
2002
+ if (record.kind === "signature" && typeof record.title === "string") {
2003
+ return {
2004
+ id: record.id,
2005
+ toolName: record.toolName,
2006
+ status: record.status,
2007
+ kind: "signature",
2008
+ title: record.title,
2009
+ ...typeof record.description === "string" ? { description: record.description } : {},
2010
+ ...typeof record.statusLabel === "string" ? { statusLabel: record.statusLabel } : {}
2011
+ };
2012
+ }
2013
+ if (record.kind !== "summary" || typeof record.title !== "string")
2014
+ return null;
2015
+ return {
2016
+ id: record.id,
2017
+ toolName: record.toolName,
2018
+ status: record.status,
2019
+ kind: "summary",
2020
+ title: record.title,
2021
+ ...typeof record.description === "string" ? { description: record.description } : {}
2022
+ };
2023
+ }
1028
2024
  function visitorTurnText(message) {
1029
2025
  return message.role === "visitor" && message.runtimeText ? message.runtimeText : message.text;
1030
2026
  }
1031
2027
  function loadPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
1032
2028
  if (typeof sessionStorage === "undefined") return null;
1033
- const raw = sessionStorage.getItem(conversationKey(storageKeyPrefix, visitorSessionId));
2029
+ const raw = sessionStorage.getItem(
2030
+ conversationKey(storageKeyPrefix, visitorSessionId)
2031
+ );
1034
2032
  if (!raw) return null;
1035
2033
  try {
1036
2034
  const value = JSON.parse(raw);
1037
2035
  if (typeof value !== "object" || value === null) return null;
1038
2036
  const record = value;
1039
- 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)) {
2037
+ 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)) {
1040
2038
  return null;
1041
2039
  }
1042
2040
  const messages = record.messages.map(parseMessage);
1043
2041
  if (messages.some((message) => message === null)) return null;
1044
- const storedToolSteps = record.version === CONVERSATION_VERSION && Array.isArray(record.toolSteps) ? record.toolSteps : [];
2042
+ const storedToolSteps = (record.version === 2 || record.version === CONVERSATION_VERSION) && Array.isArray(record.toolSteps) ? record.toolSteps : [];
1045
2043
  const toolSteps = storedToolSteps.map(parseToolStep);
1046
2044
  if (toolSteps.some((step) => step === null)) return null;
2045
+ const storedToolResults = record.version === CONVERSATION_VERSION && Array.isArray(record.toolResults) ? record.toolResults : [];
2046
+ const toolResults = storedToolResults.map(parseToolResult);
2047
+ if (toolResults.some((result) => result === null)) return null;
2048
+ const storedPendingInputs = Array.isArray(record.pendingInputs) ? record.pendingInputs : [];
2049
+ const pendingInputs = storedPendingInputs.map(parseInputRequest);
2050
+ if (pendingInputs.some((request) => request === null)) return null;
1047
2051
  return {
1048
- messages: messages.filter((message) => message !== null),
2052
+ messages: messages.filter(
2053
+ (message) => message !== null
2054
+ ),
1049
2055
  pending: record.pending,
1050
2056
  streamingText: record.streamingText,
1051
- toolSteps: toolSteps.filter((step) => step !== null)
2057
+ toolSteps: toolSteps.filter((step) => step !== null),
2058
+ ...Array.isArray(record.toolResults) ? {
2059
+ toolResults: toolResults.filter(
2060
+ (result) => result !== null
2061
+ )
2062
+ } : {},
2063
+ ...pendingInputs.length > 0 ? {
2064
+ pendingInputs: pendingInputs.filter(
2065
+ (request) => request !== null
2066
+ )
2067
+ } : {}
1052
2068
  };
1053
2069
  } catch {
1054
2070
  return null;
@@ -1063,7 +2079,9 @@ function savePersistedAgentConversation(storageKeyPrefix, visitorSessionId, conv
1063
2079
  }
1064
2080
  function clearPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
1065
2081
  if (typeof sessionStorage === "undefined") return;
1066
- sessionStorage.removeItem(conversationKey(storageKeyPrefix, visitorSessionId));
2082
+ sessionStorage.removeItem(
2083
+ conversationKey(storageKeyPrefix, visitorSessionId)
2084
+ );
1067
2085
  clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId);
1068
2086
  }
1069
2087
  function pendingBookingKey(storageKeyPrefix, visitorSessionId) {
@@ -1099,7 +2117,76 @@ function savePendingWidgetBooking(storageKeyPrefix, visitorSessionId, booking) {
1099
2117
  }
1100
2118
  function clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
1101
2119
  if (typeof sessionStorage === "undefined") return;
1102
- sessionStorage.removeItem(pendingBookingKey(storageKeyPrefix, visitorSessionId));
2120
+ sessionStorage.removeItem(
2121
+ pendingBookingKey(storageKeyPrefix, visitorSessionId)
2122
+ );
2123
+ }
2124
+
2125
+ // src/react/lib/visitor-input.ts
2126
+ function shouldRenderVisitorInputCard(request) {
2127
+ if (request.kind === "tool-approval" || request.kind === "session-limit") {
2128
+ return true;
2129
+ }
2130
+ if (request.kind === "question") {
2131
+ return (request.options?.length ?? 0) > 0;
2132
+ }
2133
+ return false;
2134
+ }
2135
+ function isChatCollectibleInputRequest(request) {
2136
+ return request.kind === "question" && (request.options?.length ?? 0) === 0;
2137
+ }
2138
+ function appendChatCollectiblePrompts(messages, requests) {
2139
+ const next = [...messages];
2140
+ for (const request of requests.filter(isChatCollectibleInputRequest)) {
2141
+ const prompt = request.prompt.trim();
2142
+ if (!prompt) continue;
2143
+ const last = next.at(-1);
2144
+ if (last?.role === "agent" && last.text.trim() === prompt) continue;
2145
+ next.push({
2146
+ id: `agent-input-${request.requestId}`,
2147
+ role: "agent",
2148
+ text: prompt,
2149
+ createdAt: Date.now()
2150
+ });
2151
+ }
2152
+ return next;
2153
+ }
2154
+ function chatInputResponseForText(requests, text) {
2155
+ const trimmed = text.trim();
2156
+ if (!trimmed) return null;
2157
+ const pending = requests.find(isChatCollectibleInputRequest);
2158
+ if (!pending) return null;
2159
+ return { requestId: pending.requestId, text: trimmed };
2160
+ }
2161
+ function normalizeAssistantDedupeKey(text) {
2162
+ return text.trim().replace(/\s+/g, " ").toLowerCase();
2163
+ }
2164
+ function isNearDuplicateAssistantText(left, right) {
2165
+ const a = normalizeAssistantDedupeKey(left);
2166
+ const b = normalizeAssistantDedupeKey(right);
2167
+ if (!a || !b) return false;
2168
+ if (a === b) return true;
2169
+ const shorter = a.length <= b.length ? a : b;
2170
+ const longer = a.length <= b.length ? b : a;
2171
+ if (shorter.length < 40) return false;
2172
+ return longer.startsWith(
2173
+ shorter.slice(0, Math.floor(shorter.length * 0.85))
2174
+ );
2175
+ }
2176
+ function appendAgentTurnMessage(messages, displayText) {
2177
+ const trimmed = displayText.trim();
2178
+ if (!trimmed) return [...messages];
2179
+ const agentMessage = {
2180
+ id: `agent-${Date.now()}`,
2181
+ role: "agent",
2182
+ text: trimmed,
2183
+ createdAt: Date.now()
2184
+ };
2185
+ const last = messages.at(-1);
2186
+ if (last?.role === "agent" && (last.id.startsWith("agent-input-") || isNearDuplicateAssistantText(last.text, trimmed))) {
2187
+ return [...messages.slice(0, -1), agentMessage];
2188
+ }
2189
+ return [...messages, agentMessage];
1103
2190
  }
1104
2191
 
1105
2192
  // src/react/hooks/useAgentChat.ts
@@ -1120,17 +2207,23 @@ function createInitialState(greeting = DEFAULT_GREETING) {
1120
2207
  followUps: [],
1121
2208
  streamingText: "",
1122
2209
  pendingOffer: null,
2210
+ pendingInputs: [],
2211
+ toolResults: [],
1123
2212
  error: null
1124
2213
  };
1125
2214
  }
1126
2215
  function stateFromConversation(conversation, initialState) {
1127
2216
  if (!conversation || conversation.messages.length === 0) return initialState;
2217
+ const pendingInputs = conversation.pendingInputs ?? [];
2218
+ const hasWaitingInput = pendingInputs.length > 0;
1128
2219
  return {
1129
2220
  ...initialState,
1130
2221
  messages: conversation.messages,
1131
- phase: conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
2222
+ phase: hasWaitingInput ? "waiting-input" : conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
1132
2223
  streamingText: conversation.streamingText,
1133
- toolSteps: conversation.toolSteps
2224
+ toolSteps: conversation.toolSteps,
2225
+ toolResults: conversation.toolResults,
2226
+ ...hasWaitingInput ? { pendingInputs } : {}
1134
2227
  };
1135
2228
  }
1136
2229
  function upsertToolStep(steps, item) {
@@ -1145,6 +2238,24 @@ function upsertToolStep(steps, item) {
1145
2238
  if (index < 0) return [...steps, next];
1146
2239
  return steps.map((step, stepIndex) => stepIndex === index ? next : step);
1147
2240
  }
2241
+ function applyBookingOffer(prev, card) {
2242
+ const pendingOffer = preferBookingOffer(prev.pendingOffer, card);
2243
+ const last = prev.messages.at(-1);
2244
+ if (last?.role === "agent" && prev.phase === "complete") {
2245
+ return {
2246
+ ...prev,
2247
+ pendingOffer,
2248
+ messages: [
2249
+ ...prev.messages.slice(0, -1),
2250
+ {
2251
+ ...last,
2252
+ text: ensureBookingOfferText(last.text, pendingOffer)
2253
+ }
2254
+ ]
2255
+ };
2256
+ }
2257
+ return { ...prev, pendingOffer };
2258
+ }
1148
2259
  function completeActivePlanning(steps) {
1149
2260
  return steps.map(
1150
2261
  (step) => step.kind === "planning" && step.state === "active" ? {
@@ -1154,6 +2265,9 @@ function completeActivePlanning(steps) {
1154
2265
  } : step
1155
2266
  );
1156
2267
  }
2268
+ function isJsonRecord(value) {
2269
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2270
+ }
1157
2271
  function useAgentChat({
1158
2272
  customerId,
1159
2273
  getUnpublishedPreviewGrant,
@@ -1163,7 +2277,8 @@ function useAgentChat({
1163
2277
  runtimeOrigin,
1164
2278
  visitorSessionId,
1165
2279
  storageKeyPrefix,
1166
- greeting
2280
+ greeting,
2281
+ toolResultRegistry
1167
2282
  }) {
1168
2283
  const initialState = useMemo(
1169
2284
  () => createInitialState(greeting?.trim() || DEFAULT_GREETING),
@@ -1171,6 +2286,8 @@ function useAgentChat({
1171
2286
  );
1172
2287
  const previewGrantProviderRef = useRef(getUnpublishedPreviewGrant);
1173
2288
  previewGrantProviderRef.current = getUnpublishedPreviewGrant;
2289
+ const toolResultRegistryRef = useRef(toolResultRegistry);
2290
+ toolResultRegistryRef.current = toolResultRegistry;
1174
2291
  const resolveUnpublishedPreviewGrant = () => {
1175
2292
  const provider = previewGrantProviderRef.current;
1176
2293
  if (!provider) {
@@ -1265,14 +2382,18 @@ function useAgentChat({
1265
2382
  messages: state.messages,
1266
2383
  pending: isAgentBusy(state.phase),
1267
2384
  streamingText: state.streamingText,
1268
- toolSteps: state.toolSteps
2385
+ toolSteps: state.toolSteps,
2386
+ toolResults: state.toolResults ?? [],
2387
+ ...state.pendingInputs && state.pendingInputs.length > 0 ? { pendingInputs: state.pendingInputs } : {}
1269
2388
  });
1270
2389
  }, [
1271
2390
  resolvedStorageKeyPrefix,
1272
2391
  state.messages,
2392
+ state.pendingInputs,
1273
2393
  state.phase,
1274
2394
  state.streamingText,
1275
2395
  state.toolSteps,
2396
+ state.toolResults,
1276
2397
  visitorId
1277
2398
  ]);
1278
2399
  const reset = useCallback(() => {
@@ -1285,13 +2406,20 @@ function useAgentChat({
1285
2406
  }, [initialState, resolvedStorageKeyPrefix, visitorId]);
1286
2407
  const runTurn = useCallback(
1287
2408
  async (input) => {
1288
- const { controller, initialText = "", resume, visitorText } = input;
2409
+ const {
2410
+ controller,
2411
+ initialText = "",
2412
+ responses,
2413
+ resume,
2414
+ visitorText
2415
+ } = input;
1289
2416
  const { signal } = controller;
1290
2417
  const isActiveRun = () => runRef.current === controller && !signal.aborted;
1291
2418
  try {
1292
2419
  let streamStarted = Boolean(initialText);
1293
2420
  let streamed = initialText;
1294
2421
  const capturedOffers = [];
2422
+ let capturedInputCount = 0;
1295
2423
  const handlers = {
1296
2424
  onWork: (item) => {
1297
2425
  if (!isActiveRun()) return;
@@ -1301,11 +2429,62 @@ function useAgentChat({
1301
2429
  toolSteps: upsertToolStep(prev.toolSteps, item)
1302
2430
  }));
1303
2431
  },
1304
- onActionResult: (output) => {
1305
- const offer = bookingOfferFromActionOutput(output);
1306
- if (!offer) return;
1307
- capturedOffers.push(offer);
1308
- setState((prev) => ({ ...prev, pendingOffer: offer }));
2432
+ onToolResult: (result) => {
2433
+ const presentation = presentVisitorToolResult(
2434
+ result,
2435
+ toolResultRegistryRef.current
2436
+ );
2437
+ if (presentation.kind === "booking") {
2438
+ const card = presentation.card;
2439
+ if (card.type === "booking_offer") {
2440
+ capturedOffers.push(card);
2441
+ setState((prev) => applyBookingOffer(prev, card));
2442
+ return;
2443
+ }
2444
+ if (!isActiveRun()) return;
2445
+ if (card.type === "booking_confirmed") {
2446
+ pendingBookingRef.current = card;
2447
+ savePendingWidgetBooking(
2448
+ resolvedStorageKeyPrefix,
2449
+ visitorId,
2450
+ card
2451
+ );
2452
+ } else if (card.type === "booking_canceled" && pendingBookingRef.current?.eventUri === card.eventUri) {
2453
+ pendingBookingRef.current = null;
2454
+ clearPendingWidgetBooking(resolvedStorageKeyPrefix, visitorId);
2455
+ }
2456
+ return;
2457
+ }
2458
+ if (!isActiveRun()) return;
2459
+ if (presentation.kind === "hidden") return;
2460
+ if (presentation.kind === "input") return;
2461
+ setState((prev) => ({
2462
+ ...prev,
2463
+ toolResults: [
2464
+ ...(prev.toolResults ?? []).filter(
2465
+ (item) => item.id !== presentation.id
2466
+ ),
2467
+ presentation
2468
+ ]
2469
+ }));
2470
+ },
2471
+ onInputRequest: (requests) => {
2472
+ if (!isActiveRun()) return;
2473
+ capturedInputCount += requests.length;
2474
+ const cardRequests = requests.filter(shouldRenderVisitorInputCard);
2475
+ const chatRequests = requests.filter(isChatCollectibleInputRequest);
2476
+ setState((prev) => ({
2477
+ ...prev,
2478
+ phase: cardRequests.length > 0 ? "waiting-input" : "complete",
2479
+ pendingInputs: [...requests],
2480
+ ...chatRequests.length > 0 ? {
2481
+ messages: appendChatCollectiblePrompts(
2482
+ prev.messages,
2483
+ chatRequests
2484
+ ),
2485
+ toolSteps: completeActivePlanning(prev.toolSteps)
2486
+ } : {}
2487
+ }));
1309
2488
  },
1310
2489
  onDelta: (delta) => {
1311
2490
  if (!isActiveRun()) return;
@@ -1331,7 +2510,11 @@ function useAgentChat({
1331
2510
  streamStarted = true;
1332
2511
  }
1333
2512
  };
1334
- let finalText = resume ? await clientRef.current.resumeTurn({
2513
+ let finalText = responses ? await clientRef.current.respondTurn({
2514
+ handlers,
2515
+ responses,
2516
+ signal
2517
+ }) : resume ? await clientRef.current.resumeTurn({
1335
2518
  handlers,
1336
2519
  initialText,
1337
2520
  message: visitorText,
@@ -1343,17 +2526,15 @@ function useAgentChat({
1343
2526
  signal
1344
2527
  });
1345
2528
  }
1346
- if (!isActiveRun() || finalText === null) return;
2529
+ if (!isActiveRun() || finalText === null) return null;
2530
+ if (!finalText.trim() && capturedInputCount > 0) {
2531
+ runRef.current = null;
2532
+ return null;
2533
+ }
1347
2534
  const displayText = ensureBookingOfferText(
1348
2535
  finalText,
1349
2536
  capturedOffers.at(-1) ?? null
1350
2537
  );
1351
- const agentMessage = {
1352
- id: `agent-${Date.now()}`,
1353
- role: "agent",
1354
- text: displayText,
1355
- createdAt: Date.now()
1356
- };
1357
2538
  const parsedCards = extractToolCards(displayText);
1358
2539
  for (const card of parsedCards) {
1359
2540
  if (card.type === "booking_confirmed") {
@@ -1367,21 +2548,25 @@ function useAgentChat({
1367
2548
  }
1368
2549
  setState((prev) => ({
1369
2550
  ...prev,
1370
- phase: "complete",
1371
- messages: [...prev.messages, agentMessage],
2551
+ phase: (prev.pendingInputs ?? []).some(shouldRenderVisitorInputCard) ? "waiting-input" : "complete",
2552
+ messages: appendAgentTurnMessage(prev.messages, displayText),
1372
2553
  toolSteps: completeActivePlanning(prev.toolSteps),
1373
2554
  streamingText: "",
1374
- pendingOffer: null,
2555
+ pendingOffer: capturedOffers.at(-1) ?? prev.pendingOffer,
2556
+ pendingInputs: (prev.pendingInputs ?? []).filter(
2557
+ isChatCollectibleInputRequest
2558
+ ),
1375
2559
  followUps: [],
1376
2560
  journey: null
1377
2561
  }));
1378
2562
  runRef.current = null;
2563
+ return displayText;
1379
2564
  } catch (error) {
1380
2565
  if (error instanceof DOMException && error.name === "AbortError")
1381
- return;
1382
- if (!isActiveRun()) return;
2566
+ return null;
2567
+ if (!isActiveRun()) return null;
1383
2568
  const message = formatAgentError(error);
1384
- if (!message) return;
2569
+ if (!message) return null;
1385
2570
  setState((prev) => ({
1386
2571
  ...prev,
1387
2572
  phase: "error",
@@ -1397,6 +2582,7 @@ function useAgentChat({
1397
2582
  error: message
1398
2583
  }));
1399
2584
  runRef.current = null;
2585
+ return null;
1400
2586
  }
1401
2587
  },
1402
2588
  [resolvedStorageKeyPrefix, visitorId]
@@ -1424,6 +2610,49 @@ function useAgentChat({
1424
2610
  );
1425
2611
  const submit = useCallback(
1426
2612
  async (visitorText, options) => {
2613
+ const trimmed = visitorText.trim();
2614
+ if (!trimmed) return null;
2615
+ const chatResponse = chatInputResponseForText(
2616
+ state.pendingInputs ?? [],
2617
+ trimmed
2618
+ );
2619
+ if (chatResponse) {
2620
+ const visitorMessage2 = {
2621
+ id: `visitor-${Date.now()}`,
2622
+ role: "visitor",
2623
+ text: trimmed,
2624
+ createdAt: Date.now()
2625
+ };
2626
+ if (runRef.current) {
2627
+ runRef.current.abort();
2628
+ clientRef.current.cancelActive();
2629
+ }
2630
+ const controller2 = new AbortController();
2631
+ runRef.current = controller2;
2632
+ setState((prev) => ({
2633
+ ...prev,
2634
+ phase: "running-tools",
2635
+ messages: [...prev.messages, visitorMessage2],
2636
+ pendingInputs: (prev.pendingInputs ?? []).filter(
2637
+ (request) => request.requestId !== chatResponse.requestId
2638
+ ),
2639
+ toolSteps: [
2640
+ {
2641
+ id: "planning",
2642
+ kind: "planning",
2643
+ label: "Understanding your question",
2644
+ state: "active"
2645
+ }
2646
+ ],
2647
+ error: null
2648
+ }));
2649
+ return await runTurn({
2650
+ controller: controller2,
2651
+ responses: [chatResponse],
2652
+ resume: false,
2653
+ visitorText: ""
2654
+ });
2655
+ }
1427
2656
  if (runRef.current) {
1428
2657
  runRef.current.abort();
1429
2658
  clientRef.current.cancelActive();
@@ -1458,11 +2687,17 @@ ${outgoing}` : outgoing;
1458
2687
  followUps: [],
1459
2688
  streamingText: "",
1460
2689
  pendingOffer: null,
2690
+ pendingInputs: [],
2691
+ toolResults: [],
1461
2692
  error: null
1462
2693
  }));
1463
- await runTurn({ controller, resume: false, visitorText: runtimeText });
2694
+ return await runTurn({
2695
+ controller,
2696
+ resume: false,
2697
+ visitorText: runtimeText
2698
+ });
1464
2699
  },
1465
- [runTurn]
2700
+ [runTurn, state.pendingInputs]
1466
2701
  );
1467
2702
  const retry = useCallback(async () => {
1468
2703
  const visitorMessage = [...state.messages].reverse().find((message) => message.role === "visitor");
@@ -1488,6 +2723,8 @@ ${outgoing}` : outgoing;
1488
2723
  followUps: [],
1489
2724
  streamingText: "",
1490
2725
  pendingOffer: null,
2726
+ pendingInputs: [],
2727
+ toolResults: [],
1491
2728
  error: null
1492
2729
  }));
1493
2730
  await runTurn({
@@ -1496,6 +2733,50 @@ ${outgoing}` : outgoing;
1496
2733
  visitorText: visitorTurnText(visitorMessage)
1497
2734
  });
1498
2735
  }, [runTurn, state.messages]);
2736
+ const respondToToolInput = useCallback(
2737
+ async (surface, values) => {
2738
+ const details = JSON.stringify(values);
2739
+ await submit(`${surface.title} details provided`, {
2740
+ runtimeText: [
2741
+ `Structured input provided for ${surface.toolSlug}.`,
2742
+ surface.operationId ? `Operation: ${surface.operationId}.` : "",
2743
+ `Use these exact values and retry the action: ${details}`,
2744
+ "Do not invent or alter any values."
2745
+ ].filter(Boolean).join("\n")
2746
+ });
2747
+ },
2748
+ [submit]
2749
+ );
2750
+ const respondToInput = useCallback(
2751
+ async (response) => {
2752
+ if (runRef.current) return;
2753
+ const pending = state.pendingInputs?.find(
2754
+ (request) => request.requestId === response.requestId
2755
+ );
2756
+ if (!pending) return;
2757
+ if (pending.ui && isJsonRecord(response.value)) {
2758
+ await respondToToolInput(pending.ui, response.value);
2759
+ return;
2760
+ }
2761
+ const controller = new AbortController();
2762
+ runRef.current = controller;
2763
+ setState((prev) => ({
2764
+ ...prev,
2765
+ phase: "running-tools",
2766
+ pendingInputs: (prev.pendingInputs ?? []).filter(
2767
+ (request) => request.requestId !== response.requestId
2768
+ ),
2769
+ error: null
2770
+ }));
2771
+ await runTurn({
2772
+ controller,
2773
+ responses: [response],
2774
+ resume: false,
2775
+ visitorText: ""
2776
+ });
2777
+ },
2778
+ [respondToToolInput, runTurn, state.pendingInputs]
2779
+ );
1499
2780
  useEffect(() => {
1500
2781
  const conversation = loadPersistedAgentConversation(
1501
2782
  resolvedStorageKeyPrefix,
@@ -1529,6 +2810,8 @@ ${outgoing}` : outgoing;
1529
2810
  state,
1530
2811
  reset,
1531
2812
  retry,
2813
+ respondToInput,
2814
+ respondToToolInput,
1532
2815
  submit,
1533
2816
  rememberBooking,
1534
2817
  forgetBooking,
@@ -1567,6 +2850,33 @@ function normalizeAgentPlacement(placement) {
1567
2850
  };
1568
2851
  }
1569
2852
 
2853
+ // src/react/panel-controller.ts
2854
+ var controllers = /* @__PURE__ */ new Map();
2855
+ function registerAgentPanelController(customerId, controller) {
2856
+ controllers.set(customerId, controller);
2857
+ }
2858
+ function unregisterAgentPanelController(customerId) {
2859
+ controllers.delete(customerId);
2860
+ }
2861
+ function openAgentPanel(customerId) {
2862
+ controllers.get(customerId)?.open();
2863
+ }
2864
+ function closeAgentPanel(customerId) {
2865
+ controllers.get(customerId)?.close();
2866
+ }
2867
+ function resetAgentPanel(customerId) {
2868
+ controllers.get(customerId)?.reset();
2869
+ }
2870
+ function submitAgentPanel(customerId, message, options) {
2871
+ const controller = controllers.get(customerId);
2872
+ if (!controller) {
2873
+ return Promise.reject(
2874
+ new Error(`Agent panel "${customerId}" is not mounted.`)
2875
+ );
2876
+ }
2877
+ return controller.submit(message, options);
2878
+ }
2879
+
1570
2880
  // src/react/types/conversation.ts
1571
2881
  var defaultAgentRailTheme = {
1572
2882
  railMaxWidth: "450px",
@@ -1579,8 +2889,8 @@ var defaultAgentRailTheme = {
1579
2889
  textMuted: "#5a6378",
1580
2890
  textSubtle: "#8a94a8",
1581
2891
  border: "rgb(42 51 70 / 0.1)",
1582
- visitorBubble: "#6f16ff",
1583
- visitorText: "#ffffff",
2892
+ visitorBubble: "#f3edff",
2893
+ visitorText: "#171b2a",
1584
2894
  success: "#18794e",
1585
2895
  danger: "#c94b63",
1586
2896
  fontBody: '"Mulish", "Avenir Next", "Segoe UI", sans-serif',
@@ -1597,13 +2907,14 @@ var defaultDarkAgentRailTheme = {
1597
2907
  textMuted: "#b6bfce",
1598
2908
  textSubtle: "#919cad",
1599
2909
  border: "rgb(226 232 240 / 0.16)",
1600
- visitorBubble: "#7c3aed",
2910
+ visitorBubble: "#2b2140",
2911
+ visitorText: "#f5f7fb",
1601
2912
  success: "#55cf91",
1602
2913
  danger: "#ff8da1"
1603
2914
  };
1604
2915
 
1605
2916
  // src/react/components/AgentRail/AgentRail.tsx
1606
- import { useEffect as useEffect2, useRef as useRef3, useState as useState6 } from "react";
2917
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState7 } from "react";
1607
2918
 
1608
2919
  // src/react/hooks/useAgentColorScheme.ts
1609
2920
  import { useSyncExternalStore } from "react";
@@ -1637,11 +2948,21 @@ function resolveAgentColorScheme(colorScheme = "auto", prefersDarkMode) {
1637
2948
 
1638
2949
  // src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
1639
2950
  import { useState as useState2 } from "react";
1640
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2951
+ import { jsx, jsxs } from "react/jsx-runtime";
2952
+ function joinLabels(labels) {
2953
+ if (labels.length <= 1) return labels[0] ?? "";
2954
+ if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
2955
+ return `${labels.slice(0, -1).join(", ")} and ${labels.at(-1) ?? ""}`;
2956
+ }
1641
2957
  function workSummary(steps, failed, brandLabel) {
2958
+ const activeSpecialists = steps.filter(
2959
+ (step) => step.kind === "specialist" && step.state === "active"
2960
+ );
2961
+ if (activeSpecialists.length > 0)
2962
+ return `Working with ${joinLabels(
2963
+ activeSpecialists.map((step) => step.label)
2964
+ )}`;
1642
2965
  const active = [...steps].reverse().find((step) => step.state === "active");
1643
- if (active?.kind === "specialist")
1644
- return `${active.label} is reviewing your question`;
1645
2966
  if (active?.kind === "search") return "Searching this site";
1646
2967
  if (active)
1647
2968
  return brandLabel ? `${brandLabel} is choosing the best way to help` : "Choosing the best way to help";
@@ -1659,7 +2980,7 @@ function workSummary(steps, failed, brandLabel) {
1659
2980
  if (specialists.length === 1)
1660
2981
  return `Brought in ${specialists[0]?.label}`;
1661
2982
  if (searched) return "Searched this site";
1662
- return "Answer ready";
2983
+ return brandLabel ? `Answered with ${brandLabel}` : "Answer ready";
1663
2984
  }
1664
2985
  function stepLabel(step, brandLabel) {
1665
2986
  return step.kind === "planning" ? brandLabel : step.label;
@@ -1677,36 +2998,11 @@ function stepDetail(step, steps) {
1677
2998
  return "Searched this site";
1678
2999
  return step.detail;
1679
3000
  }
1680
- function SearchIcon() {
1681
- return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
1682
- /* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "3.75", stroke: "currentColor", strokeWidth: "1.4" }),
1683
- /* @__PURE__ */ jsx(
1684
- "path",
1685
- {
1686
- d: "m10 10 3 3",
1687
- stroke: "currentColor",
1688
- strokeWidth: "1.4",
1689
- strokeLinecap: "round"
1690
- }
1691
- )
1692
- ] });
1693
- }
1694
- function PlanningIcon() {
1695
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
1696
- "path",
1697
- {
1698
- d: "M4 4.5h8M4 8h5.5M4 11.5h7",
1699
- stroke: "currentColor",
1700
- strokeWidth: "1.4",
1701
- strokeLinecap: "round"
1702
- }
1703
- ) });
1704
- }
1705
3001
  function AgentActivityBubble({
1706
3002
  brandLabel = "",
1707
- brandLogoUrl,
1708
3003
  failed = false,
1709
- steps
3004
+ steps,
3005
+ onRetryStep
1710
3006
  }) {
1711
3007
  const active = steps.some((step) => step.state === "active");
1712
3008
  const receiptId = steps.map((step) => step.id).join(":");
@@ -1714,6 +3010,10 @@ function AgentActivityBubble({
1714
3010
  null
1715
3011
  );
1716
3012
  const detailsOpen = active || expandedReceiptId === receiptId;
3013
+ const delegationCount = steps.filter(
3014
+ (step) => step.kind === "specialist"
3015
+ ).length;
3016
+ const delegated = delegationCount > 0 && steps.some((step) => step.kind === "planning");
1717
3017
  const visibleSteps = steps.filter(
1718
3018
  (step) => step.kind !== "planning" || Boolean(brandLabel)
1719
3019
  );
@@ -1729,7 +3029,7 @@ function AgentActivityBubble({
1729
3029
  workSummary(steps, failed, brandLabel)
1730
3030
  ] }),
1731
3031
  /* @__PURE__ */ jsxs("div", { className: "agent-activity-bubble__details", children: [
1732
- /* @__PURE__ */ jsx(
3032
+ /* @__PURE__ */ jsxs(
1733
3033
  "button",
1734
3034
  {
1735
3035
  type: "button",
@@ -1741,15 +3041,19 @@ function AgentActivityBubble({
1741
3041
  (current) => current === receiptId ? null : receiptId
1742
3042
  );
1743
3043
  },
1744
- children: "How this answer was made"
3044
+ children: [
3045
+ /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__summary-title", children: "How this answer was made" }),
3046
+ /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__summary-toggle", children: detailsOpen ? "Hide work" : "Show work" })
3047
+ ]
1745
3048
  }
1746
3049
  ),
1747
3050
  detailsOpen ? /* @__PURE__ */ jsx("ol", { className: "agent-activity-bubble__steps", children: visibleSteps.map((step) => {
1748
3051
  const detail = stepDetail(step, steps);
3052
+ const child = delegated && step.kind === "specialist";
1749
3053
  return /* @__PURE__ */ jsxs(
1750
3054
  "li",
1751
3055
  {
1752
- className: "agent-activity-bubble__step",
3056
+ className: `agent-activity-bubble__step${child ? " agent-activity-bubble__step--child" : ""}`,
1753
3057
  "data-kind": step.kind,
1754
3058
  "data-state": step.state,
1755
3059
  children: [
@@ -1757,25 +3061,27 @@ function AgentActivityBubble({
1757
3061
  "span",
1758
3062
  {
1759
3063
  className: "agent-activity-bubble__step-icon",
1760
- "aria-hidden": "true",
1761
- children: step.kind === "planning" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1762
- /* @__PURE__ */ jsx(PlanningIcon, {}),
1763
- brandLogoUrl ? /* @__PURE__ */ jsx(
1764
- "img",
1765
- {
1766
- src: brandLogoUrl,
1767
- alt: "",
1768
- onError: (event) => {
1769
- event.currentTarget.hidden = true;
1770
- }
1771
- }
1772
- ) : null
1773
- ] }) : step.kind === "search" ? /* @__PURE__ */ jsx(SearchIcon, {}) : step.label.slice(0, 1).toUpperCase()
3064
+ "aria-hidden": "true"
1774
3065
  }
1775
3066
  ),
1776
3067
  /* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__step-copy", children: [
1777
3068
  /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__step-heading", children: /* @__PURE__ */ jsx("strong", { children: stepLabel(step, brandLabel) }) }),
1778
- detail ? /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null
3069
+ detail ? /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null,
3070
+ delegated && step.kind === "planning" ? /* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__delegation", children: [
3071
+ "Delegated ",
3072
+ delegationCount,
3073
+ " ",
3074
+ delegationCount === 1 ? "task" : "tasks"
3075
+ ] }) : null,
3076
+ step.state === "error" && onRetryStep ? /* @__PURE__ */ jsx(
3077
+ "button",
3078
+ {
3079
+ type: "button",
3080
+ className: "agent-activity-bubble__step-retry",
3081
+ onClick: () => onRetryStep(step),
3082
+ children: "Retry"
3083
+ }
3084
+ ) : null
1779
3085
  ] })
1780
3086
  ]
1781
3087
  },
@@ -1787,62 +3093,198 @@ function AgentActivityBubble({
1787
3093
  }
1788
3094
 
1789
3095
  // src/react/components/Composer/Composer.tsx
1790
- import { useRef as useRef2, useState as useState3 } from "react";
3096
+ import {
3097
+ useEffect as useEffect2,
3098
+ useId,
3099
+ useRef as useRef2,
3100
+ useState as useState3
3101
+ } from "react";
1791
3102
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1792
3103
  function SendIcon() {
1793
- return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "M8 12V4M8 4l-3 3M8 4l3 3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
3104
+ return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
3105
+ "path",
3106
+ {
3107
+ d: "M8 12V4M8 4l-3 3M8 4l3 3",
3108
+ stroke: "currentColor",
3109
+ strokeWidth: "1.5",
3110
+ strokeLinecap: "round",
3111
+ strokeLinejoin: "round"
3112
+ }
3113
+ ) });
3114
+ }
3115
+ function emptyValues(form) {
3116
+ const values = {};
3117
+ for (const field of form?.fields ?? []) values[field.id] = "";
3118
+ return values;
1794
3119
  }
1795
3120
  function Composer({
1796
3121
  disabled = false,
1797
3122
  placeholder = "Ask anything\u2026",
1798
3123
  variant = "default",
3124
+ form = null,
1799
3125
  onSubmit
1800
3126
  }) {
1801
3127
  const [value, setValue] = useState3("");
3128
+ const [values, setValues] = useState3(
3129
+ () => emptyValues(form)
3130
+ );
3131
+ const [blurred, setBlurred] = useState3({});
1802
3132
  const inputRef = useRef2(null);
1803
- function submitCurrent() {
3133
+ const firstFieldRef = useRef2(null);
3134
+ const formId = useId();
3135
+ const activeForm = form;
3136
+ const canSendForm = activeForm ? isComposerFormComplete(activeForm, values) : Boolean(value.trim());
3137
+ useEffect2(() => {
3138
+ setValues(emptyValues(form));
3139
+ setBlurred({});
3140
+ }, [form?.id]);
3141
+ useEffect2(() => {
3142
+ if (activeForm) firstFieldRef.current?.focus();
3143
+ }, [activeForm?.id]);
3144
+ function submitChat() {
1804
3145
  const trimmed = value.trim();
1805
3146
  if (!trimmed || disabled) return;
1806
3147
  onSubmit?.(trimmed);
1807
3148
  setValue("");
1808
3149
  inputRef.current?.focus();
1809
3150
  }
3151
+ function submitForm() {
3152
+ if (!activeForm || disabled || !canSendForm) return;
3153
+ onSubmit?.(formatComposerFormMessage(activeForm, values));
3154
+ setValues(emptyValues(activeForm));
3155
+ setBlurred({});
3156
+ }
1810
3157
  function handleSubmit(event) {
1811
3158
  event.preventDefault();
1812
- submitCurrent();
3159
+ if (activeForm) submitForm();
3160
+ else submitChat();
1813
3161
  }
1814
- function handleKeyDown(event) {
3162
+ function handleChatKeyDown(event) {
1815
3163
  if (event.key === "Enter" && !event.shiftKey) {
1816
3164
  event.preventDefault();
1817
- submitCurrent();
3165
+ submitChat();
3166
+ }
3167
+ }
3168
+ function handleFormKeyDown(event) {
3169
+ const target = event.target;
3170
+ const isTextarea = Boolean(target && "tagName" in target && target.tagName === "TEXTAREA");
3171
+ if (event.key === "Enter" && !event.shiftKey && !isTextarea) {
3172
+ event.preventDefault();
3173
+ submitForm();
3174
+ }
3175
+ }
3176
+ return /* @__PURE__ */ jsx2(
3177
+ "form",
3178
+ {
3179
+ className: [
3180
+ "composer",
3181
+ variant === "dock" ? "composer--dock" : "",
3182
+ activeForm ? "composer--form" : ""
3183
+ ].filter(Boolean).join(" "),
3184
+ onSubmit: handleSubmit,
3185
+ children: activeForm ? /* @__PURE__ */ jsxs2("div", { className: "composer__sheet", role: "group", "aria-label": "Required details", children: [
3186
+ activeForm.fields.map((field, index) => {
3187
+ const fieldId = `${formId}-${field.id}`;
3188
+ const invalid = Boolean(blurred[field.id]) && !isValidComposerFieldValue(field, values[field.id] ?? "");
3189
+ const controlProps = {
3190
+ id: fieldId,
3191
+ name: field.id,
3192
+ disabled,
3193
+ required: field.required,
3194
+ autoComplete: field.autocomplete,
3195
+ placeholder: field.placeholder,
3196
+ spellCheck: false,
3197
+ value: values[field.id] ?? "",
3198
+ "aria-invalid": invalid || void 0,
3199
+ "aria-describedby": invalid ? `${fieldId}-error` : void 0,
3200
+ onBlur: () => setBlurred((current) => ({ ...current, [field.id]: true })),
3201
+ onChange: (event) => {
3202
+ const next = readComposerControlValue(event);
3203
+ setValues((current) => ({
3204
+ ...current,
3205
+ [field.id]: next
3206
+ }));
3207
+ },
3208
+ onKeyDown: handleFormKeyDown
3209
+ };
3210
+ return /* @__PURE__ */ jsxs2(
3211
+ "div",
3212
+ {
3213
+ className: [
3214
+ "composer__row",
3215
+ field.kind === "textarea" ? "composer__row--grow" : ""
3216
+ ].filter(Boolean).join(" "),
3217
+ children: [
3218
+ /* @__PURE__ */ jsxs2("label", { className: "composer__label", htmlFor: fieldId, children: [
3219
+ /* @__PURE__ */ jsx2("span", { className: "composer__sr-only", children: field.label }),
3220
+ field.kind === "textarea" ? /* @__PURE__ */ jsx2(
3221
+ "textarea",
3222
+ {
3223
+ ...controlProps,
3224
+ ref: index === 0 ? (node) => {
3225
+ firstFieldRef.current = node;
3226
+ } : void 0,
3227
+ className: "composer__control composer__control--area",
3228
+ rows: 3
3229
+ }
3230
+ ) : /* @__PURE__ */ jsx2(
3231
+ "input",
3232
+ {
3233
+ ...controlProps,
3234
+ ref: index === 0 ? (node) => {
3235
+ firstFieldRef.current = node;
3236
+ } : void 0,
3237
+ className: "composer__control",
3238
+ type: field.kind,
3239
+ inputMode: field.kind === "tel" ? "tel" : void 0
3240
+ }
3241
+ )
3242
+ ] }),
3243
+ invalid ? /* @__PURE__ */ jsx2("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
3244
+ ]
3245
+ },
3246
+ field.id
3247
+ );
3248
+ }),
3249
+ /* @__PURE__ */ jsx2("div", { className: "composer__toolbar", children: /* @__PURE__ */ jsx2(
3250
+ "button",
3251
+ {
3252
+ type: "submit",
3253
+ className: "composer__send",
3254
+ disabled: disabled || !canSendForm,
3255
+ "aria-label": "Send details",
3256
+ children: /* @__PURE__ */ jsx2(SendIcon, {})
3257
+ }
3258
+ ) })
3259
+ ] }) : /* @__PURE__ */ jsxs2("div", { className: "composer__field", children: [
3260
+ /* @__PURE__ */ jsx2(
3261
+ "textarea",
3262
+ {
3263
+ ref: inputRef,
3264
+ className: "composer__input",
3265
+ rows: 1,
3266
+ value,
3267
+ placeholder,
3268
+ disabled,
3269
+ spellCheck: false,
3270
+ "aria-label": "Message",
3271
+ onChange: (event) => setValue(event.target.value),
3272
+ onKeyDown: handleChatKeyDown
3273
+ }
3274
+ ),
3275
+ /* @__PURE__ */ jsx2(
3276
+ "button",
3277
+ {
3278
+ type: "submit",
3279
+ className: "composer__send",
3280
+ disabled: disabled || !value.trim(),
3281
+ "aria-label": "Send message",
3282
+ children: /* @__PURE__ */ jsx2(SendIcon, {})
3283
+ }
3284
+ )
3285
+ ] })
1818
3286
  }
1819
- }
1820
- return /* @__PURE__ */ jsx2("form", { className: `composer${variant === "dock" ? " composer--dock" : ""}`, onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs2("div", { className: "composer__field", children: [
1821
- /* @__PURE__ */ jsx2(
1822
- "textarea",
1823
- {
1824
- ref: inputRef,
1825
- className: "composer__input",
1826
- rows: 1,
1827
- value,
1828
- placeholder,
1829
- disabled,
1830
- "aria-label": "Message",
1831
- onChange: (event) => setValue(event.target.value),
1832
- onKeyDown: handleKeyDown
1833
- }
1834
- ),
1835
- /* @__PURE__ */ jsx2(
1836
- "button",
1837
- {
1838
- type: "submit",
1839
- className: "composer__send",
1840
- disabled: disabled || !value.trim(),
1841
- "aria-label": "Send message",
1842
- children: /* @__PURE__ */ jsx2(SendIcon, {})
1843
- }
1844
- )
1845
- ] }) });
3287
+ );
1846
3288
  }
1847
3289
 
1848
3290
  // src/react/components/FollowUpChips/FollowUpChips.tsx
@@ -1888,11 +3330,17 @@ function FollowUpChips({
1888
3330
  import { useState as useState5 } from "react";
1889
3331
 
1890
3332
  // src/react/components/BookingCard/BookingCard.tsx
1891
- import { useId, useMemo as useMemo2, useState as useState4 } from "react";
3333
+ import { useId as useId2, useMemo as useMemo2, useState as useState4 } from "react";
1892
3334
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3335
+ var BOOKING_STEPS = [
3336
+ { id: "date", label: "Date" },
3337
+ { id: "time", label: "Time" },
3338
+ { id: "details", label: "Details" }
3339
+ ];
1893
3340
  function monthFromKey(key) {
1894
3341
  const [year, month] = key.split("-").map(Number);
1895
- if (!year || !month) return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
3342
+ if (!year || !month)
3343
+ return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
1896
3344
  return { year, month: month - 1 };
1897
3345
  }
1898
3346
  function dateKeyFromParts(year, month, day) {
@@ -1917,7 +3365,7 @@ function BookingCard({
1917
3365
  offer,
1918
3366
  onBook
1919
3367
  }) {
1920
- const fieldId = useId();
3368
+ const fieldId = useId2();
1921
3369
  const defaultType = offer.eventTypes[0]?.uri ?? offer.slots[0]?.eventTypeUri ?? "";
1922
3370
  const [step, setStep] = useState4("date");
1923
3371
  const [eventTypeUri, setEventTypeUri] = useState4(defaultType);
@@ -1925,6 +3373,7 @@ function BookingCard({
1925
3373
  const [startTime, setStartTime] = useState4("");
1926
3374
  const [name, setName] = useState4("");
1927
3375
  const [email, setEmail] = useState4("");
3376
+ const stepIndex = BOOKING_STEPS.findIndex((item) => item.id === step);
1928
3377
  const slots = useMemo2(
1929
3378
  () => bookingSlotsForEventType(offer.slots, eventTypeUri),
1930
3379
  [eventTypeUri, offer.slots]
@@ -1945,14 +3394,18 @@ function BookingCard({
1945
3394
  setSelectedDate("");
1946
3395
  setStartTime("");
1947
3396
  setVisibleMonth(
1948
- firstAvailableBookingMonth(bookingSlotsForEventType(offer.slots, nextType))
3397
+ firstAvailableBookingMonth(
3398
+ bookingSlotsForEventType(offer.slots, nextType)
3399
+ )
1949
3400
  );
1950
3401
  }
1951
3402
  const daySlots = useMemo2(
1952
3403
  () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
1953
3404
  [selectedDate, slots]
1954
3405
  );
1955
- const selectedType = offer.eventTypes.find((item) => item.uri === eventTypeUri);
3406
+ const selectedType = offer.eventTypes.find(
3407
+ (item) => item.uri === eventTypeUri
3408
+ );
1956
3409
  const selectedSample = availableByDate.get(selectedDate) ?? startTime;
1957
3410
  const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
1958
3411
  const weekdays = useMemo2(() => weekdayLabels(), []);
@@ -1998,24 +3451,47 @@ function BookingCard({
1998
3451
  });
1999
3452
  }
2000
3453
  return /* @__PURE__ */ jsx4("section", { className: "booking-card", "aria-label": "Book a meeting", children: /* @__PURE__ */ jsxs4("form", { className: "booking-card__form", onSubmit: handleSubmit, children: [
3454
+ /* @__PURE__ */ jsx4("ol", { className: "booking-card__steps", "aria-label": "Booking steps", children: BOOKING_STEPS.map((item, index) => /* @__PURE__ */ jsxs4(
3455
+ "li",
3456
+ {
3457
+ className: [
3458
+ "booking-card__step-indicator",
3459
+ index === stepIndex ? "booking-card__step-indicator--active" : "",
3460
+ index < stepIndex ? "booking-card__step-indicator--complete" : ""
3461
+ ].filter(Boolean).join(" "),
3462
+ "aria-current": index === stepIndex ? "step" : void 0,
3463
+ children: [
3464
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: index + 1 }),
3465
+ /* @__PURE__ */ jsx4("span", { children: item.label })
3466
+ ]
3467
+ },
3468
+ item.id
3469
+ )) }),
2001
3470
  step === "date" ? /* @__PURE__ */ jsxs4("div", { className: "booking-card__step", children: [
2002
3471
  /* @__PURE__ */ jsx4("p", { className: "booking-card__title", children: selectedType?.name || "Pick a date" }),
2003
3472
  timeZone ? /* @__PURE__ */ jsxs4("p", { className: "booking-card__tz", children: [
2004
3473
  "Times in ",
2005
3474
  timeZone
2006
3475
  ] }) : null,
2007
- offer.eventTypes.length > 1 ? /* @__PURE__ */ jsxs4("label", { className: "booking-card__field", htmlFor: `${fieldId}-type`, children: [
2008
- /* @__PURE__ */ jsx4("span", { children: "Meeting" }),
2009
- /* @__PURE__ */ jsx4(
2010
- "select",
2011
- {
2012
- id: `${fieldId}-type`,
2013
- value: eventTypeUri,
2014
- onChange: (event) => selectEventType(event.target.value),
2015
- children: offer.eventTypes.map((item) => /* @__PURE__ */ jsx4("option", { value: item.uri, children: item.name }, item.uri))
2016
- }
2017
- )
2018
- ] }) : null,
3476
+ offer.eventTypes.length > 1 ? /* @__PURE__ */ jsxs4(
3477
+ "label",
3478
+ {
3479
+ className: "booking-card__field",
3480
+ htmlFor: `${fieldId}-type`,
3481
+ children: [
3482
+ /* @__PURE__ */ jsx4("span", { children: "Meeting" }),
3483
+ /* @__PURE__ */ jsx4(
3484
+ "select",
3485
+ {
3486
+ id: `${fieldId}-type`,
3487
+ value: eventTypeUri,
3488
+ onChange: (event) => selectEventType(event.target.value),
3489
+ children: offer.eventTypes.map((item) => /* @__PURE__ */ jsx4("option", { value: item.uri, children: item.name }, item.uri))
3490
+ }
3491
+ )
3492
+ ]
3493
+ }
3494
+ ) : null,
2019
3495
  /* @__PURE__ */ jsxs4("div", { className: "booking-card__month", children: [
2020
3496
  /* @__PURE__ */ jsx4(
2021
3497
  "button",
@@ -2042,29 +3518,44 @@ function BookingCard({
2042
3518
  )
2043
3519
  ] }),
2044
3520
  /* @__PURE__ */ jsx4("div", { className: "booking-card__weekdays", children: weekdays.map((label) => /* @__PURE__ */ jsx4("span", { children: label }, label)) }),
2045
- /* @__PURE__ */ jsx4("div", { className: "booking-card__calendar", role: "grid", "aria-label": "Available dates", children: cells.map((cell, index) => {
2046
- if (!cell) {
2047
- return /* @__PURE__ */ jsx4("span", { className: "booking-card__day" }, `empty-${index}`);
3521
+ offer.slots.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "booking-card__loading", role: "status", children: "Finding available times\u2026" }) : null,
3522
+ /* @__PURE__ */ jsx4(
3523
+ "div",
3524
+ {
3525
+ className: "booking-card__calendar",
3526
+ role: "grid",
3527
+ "aria-label": "Available dates",
3528
+ children: cells.map((cell, index) => {
3529
+ if (!cell) {
3530
+ return /* @__PURE__ */ jsx4(
3531
+ "span",
3532
+ {
3533
+ className: "booking-card__day"
3534
+ },
3535
+ `empty-${index}`
3536
+ );
3537
+ }
3538
+ const available = availableByDate.has(cell.key);
3539
+ const selected = cell.key === selectedDate;
3540
+ return /* @__PURE__ */ jsx4(
3541
+ "button",
3542
+ {
3543
+ type: "button",
3544
+ className: [
3545
+ "booking-card__day",
3546
+ available ? "booking-card__day--available" : "",
3547
+ selected ? "booking-card__day--selected" : ""
3548
+ ].filter(Boolean).join(" "),
3549
+ disabled: !available,
3550
+ "aria-pressed": selected,
3551
+ onClick: () => selectDate(cell.key),
3552
+ children: cell.day
3553
+ },
3554
+ cell.key
3555
+ );
3556
+ })
2048
3557
  }
2049
- const available = availableByDate.has(cell.key);
2050
- const selected = cell.key === selectedDate;
2051
- return /* @__PURE__ */ jsx4(
2052
- "button",
2053
- {
2054
- type: "button",
2055
- className: [
2056
- "booking-card__day",
2057
- available ? "booking-card__day--available" : "",
2058
- selected ? "booking-card__day--selected" : ""
2059
- ].filter(Boolean).join(" "),
2060
- disabled: !available,
2061
- "aria-pressed": selected,
2062
- onClick: () => selectDate(cell.key),
2063
- children: cell.day
2064
- },
2065
- cell.key
2066
- );
2067
- }) })
3558
+ )
2068
3559
  ] }, "date") : null,
2069
3560
  step === "time" ? /* @__PURE__ */ jsxs4("div", { className: "booking-card__step", children: [
2070
3561
  /* @__PURE__ */ jsxs4("div", { className: "booking-card__step-bar", children: [
@@ -2116,35 +3607,57 @@ function BookingCard({
2116
3607
  ] })
2117
3608
  ] }),
2118
3609
  /* @__PURE__ */ jsxs4("div", { className: "booking-card__identity", children: [
2119
- /* @__PURE__ */ jsxs4("label", { className: "booking-card__field", htmlFor: `${fieldId}-name`, children: [
2120
- /* @__PURE__ */ jsx4("span", { children: "Name" }),
2121
- /* @__PURE__ */ jsx4(
2122
- "input",
2123
- {
2124
- id: `${fieldId}-name`,
2125
- autoComplete: "name",
2126
- value: name,
2127
- onChange: (event) => setName(event.target.value),
2128
- required: true
2129
- }
2130
- )
2131
- ] }),
2132
- /* @__PURE__ */ jsxs4("label", { className: "booking-card__field", htmlFor: `${fieldId}-email`, children: [
2133
- /* @__PURE__ */ jsx4("span", { children: "Email" }),
2134
- /* @__PURE__ */ jsx4(
2135
- "input",
2136
- {
2137
- id: `${fieldId}-email`,
2138
- type: "email",
2139
- autoComplete: "email",
2140
- value: email,
2141
- onChange: (event) => setEmail(event.target.value),
2142
- required: true
2143
- }
2144
- )
2145
- ] })
3610
+ /* @__PURE__ */ jsxs4(
3611
+ "label",
3612
+ {
3613
+ className: "booking-card__field",
3614
+ htmlFor: `${fieldId}-name`,
3615
+ children: [
3616
+ /* @__PURE__ */ jsx4("span", { children: "Name" }),
3617
+ /* @__PURE__ */ jsx4(
3618
+ "input",
3619
+ {
3620
+ id: `${fieldId}-name`,
3621
+ autoComplete: "name",
3622
+ value: name,
3623
+ onChange: (event) => setName(event.target.value),
3624
+ required: true
3625
+ }
3626
+ )
3627
+ ]
3628
+ }
3629
+ ),
3630
+ /* @__PURE__ */ jsxs4(
3631
+ "label",
3632
+ {
3633
+ className: "booking-card__field",
3634
+ htmlFor: `${fieldId}-email`,
3635
+ children: [
3636
+ /* @__PURE__ */ jsx4("span", { children: "Email" }),
3637
+ /* @__PURE__ */ jsx4(
3638
+ "input",
3639
+ {
3640
+ id: `${fieldId}-email`,
3641
+ type: "email",
3642
+ autoComplete: "email",
3643
+ value: email,
3644
+ onChange: (event) => setEmail(event.target.value),
3645
+ required: true
3646
+ }
3647
+ )
3648
+ ]
3649
+ }
3650
+ )
2146
3651
  ] }),
2147
- /* @__PURE__ */ jsx4("button", { type: "submit", className: "booking-card__submit", children: "Book this time" })
3652
+ /* @__PURE__ */ jsx4(
3653
+ "button",
3654
+ {
3655
+ type: "submit",
3656
+ className: "booking-card__submit",
3657
+ disabled: !name.trim() || !email.trim(),
3658
+ children: "Book this time"
3659
+ }
3660
+ )
2148
3661
  ] }, "details") : null
2149
3662
  ] }) });
2150
3663
  }
@@ -2153,6 +3666,43 @@ function BookingCard({
2153
3666
  import { Streamdown } from "streamdown";
2154
3667
  import "streamdown/styles.css";
2155
3668
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3669
+ function normalizeDedupeText(text) {
3670
+ return text.trim().replace(/\s+/g, " ").toLowerCase();
3671
+ }
3672
+ function paragraphsAreNearDuplicates(first, second) {
3673
+ const left = normalizeDedupeText(first);
3674
+ const right = normalizeDedupeText(second);
3675
+ if (left.length < 40 || right.length < 40) return false;
3676
+ if (left === right) return true;
3677
+ const shorter = left.length <= right.length ? left : right;
3678
+ const longer = left.length <= right.length ? right : left;
3679
+ return longer.startsWith(
3680
+ shorter.slice(0, Math.floor(shorter.length * 0.85))
3681
+ );
3682
+ }
3683
+ function paragraphsShareOpening(first, second) {
3684
+ const opening = first.split("\n")[0]?.trim();
3685
+ if (!opening || opening.length < 20) return false;
3686
+ return second.trim().startsWith(opening);
3687
+ }
3688
+ function collapseRepeatedText(text) {
3689
+ const trimmed = text.trim();
3690
+ if (trimmed.length < 40) return trimmed;
3691
+ const paragraphs = trimmed.split(/\n{2,}/u).map((part) => part.trim()).filter(Boolean);
3692
+ if (paragraphs.length === 2 && (paragraphsAreNearDuplicates(paragraphs[0], paragraphs[1]) || paragraphsShareOpening(paragraphs[0], paragraphs[1]))) {
3693
+ return paragraphs[0];
3694
+ }
3695
+ if (paragraphs.length >= 2 && paragraphs.length % 2 === 0) {
3696
+ const mid2 = paragraphs.length / 2;
3697
+ const first = paragraphs.slice(0, mid2).join("\n\n");
3698
+ const second = paragraphs.slice(mid2).join("\n\n");
3699
+ if (first === second) return first;
3700
+ }
3701
+ const mid = Math.floor(trimmed.length / 2);
3702
+ const left = trimmed.slice(0, mid).trim();
3703
+ const right = trimmed.slice(mid).trim();
3704
+ return left.length >= 20 && left === right ? left : trimmed;
3705
+ }
2156
3706
  function MessageBubble({
2157
3707
  message,
2158
3708
  brandLogoUrl,
@@ -2169,24 +3719,41 @@ function MessageBubble({
2169
3719
  const offers = offer ? [offer] : extractedOffers;
2170
3720
  const visibleText = hideToolCardFences(message.text);
2171
3721
  const isStreaming = message.role === "agent" && Boolean(message.streaming);
2172
- const displayText = offers.length > 0 ? visibleText || "Pick a date and time that works for you." : visibleText || (isStreaming ? "" : message.text);
3722
+ const displayText = collapseRepeatedText(
3723
+ offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
3724
+ );
2173
3725
  if (message.role === "visitor") {
2174
3726
  return /* @__PURE__ */ jsx5("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ jsx5("p", { className: "message-bubble__text", children: message.text }) });
2175
3727
  }
2176
- const agentText = /* @__PURE__ */ jsx5("div", { className: "message-bubble__text", children: /* @__PURE__ */ jsx5(
2177
- Streamdown,
2178
- {
2179
- animated: true,
2180
- caret: "circle",
2181
- className: "message-bubble__markdown",
2182
- controls: false,
2183
- isAnimating: isStreaming,
2184
- linkSafety: { enabled: false },
2185
- mode: isStreaming ? "streaming" : "static",
2186
- skipHtml: true,
2187
- children: displayText
2188
- }
2189
- ) });
3728
+ const citations = message.citations ?? [];
3729
+ const agentText = /* @__PURE__ */ jsxs5("div", { className: "message-bubble__text", children: [
3730
+ /* @__PURE__ */ jsx5(
3731
+ Streamdown,
3732
+ {
3733
+ animated: isStreaming,
3734
+ caret: "circle",
3735
+ className: "message-bubble__markdown",
3736
+ controls: false,
3737
+ isAnimating: isStreaming,
3738
+ linkSafety: { enabled: false },
3739
+ mode: isStreaming ? "streaming" : "static",
3740
+ skipHtml: true,
3741
+ children: displayText
3742
+ }
3743
+ ),
3744
+ citations.length > 0 ? /* @__PURE__ */ jsx5("ul", { className: "message-bubble__sources", "aria-label": "Sources", children: citations.map((citation) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs5(
3745
+ "a",
3746
+ {
3747
+ href: citation.url,
3748
+ target: "_blank",
3749
+ rel: "noreferrer",
3750
+ children: [
3751
+ /* @__PURE__ */ jsx5("span", { className: "message-bubble__source-icon", "aria-hidden": "true", children: "\u25A6" }),
3752
+ /* @__PURE__ */ jsx5("span", { children: citation.label })
3753
+ ]
3754
+ }
3755
+ ) }, citation.id)) }) : null
3756
+ ] });
2190
3757
  return /* @__PURE__ */ jsxs5("article", { className: "message-bubble message-bubble--agent", children: [
2191
3758
  displayText ? showBrandLogo ? /* @__PURE__ */ jsxs5("div", { className: "message-bubble__agent-row", children: [
2192
3759
  /* @__PURE__ */ jsx5("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
@@ -2212,10 +3779,262 @@ function MessageBubble({
2212
3779
  ] });
2213
3780
  }
2214
3781
 
2215
- // src/react/components/AgentRail/AgentRail.tsx
3782
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3783
+ import { useState as useState6 } from "react";
3784
+
3785
+ // src/react/components/ConfirmationCard/ConfirmationCard.tsx
2216
3786
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3787
+ function ConfirmationCard({
3788
+ disabled = false,
3789
+ request,
3790
+ onRespond
3791
+ }) {
3792
+ const options = request.options ?? [];
3793
+ const heading = request.kind === "tool-approval" ? "Confirm this action" : request.prompt;
3794
+ const prompt = request.kind === "tool-approval" ? request.prompt : void 0;
3795
+ return /* @__PURE__ */ jsxs6(
3796
+ "section",
3797
+ {
3798
+ className: "confirmation-card",
3799
+ "aria-labelledby": `confirmation-${request.requestId}`,
3800
+ children: [
3801
+ /* @__PURE__ */ jsxs6("div", { className: "confirmation-card__heading", children: [
3802
+ /* @__PURE__ */ jsx6("strong", { id: `confirmation-${request.requestId}`, children: heading }),
3803
+ prompt ? /* @__PURE__ */ jsx6("p", { children: prompt }) : null
3804
+ ] }),
3805
+ /* @__PURE__ */ jsx6("div", { className: "confirmation-card__actions", children: options.map((option) => /* @__PURE__ */ jsx6(
3806
+ "button",
3807
+ {
3808
+ type: "button",
3809
+ className: `confirmation-card__action confirmation-card__action--${option.style ?? "default"}`,
3810
+ disabled,
3811
+ onClick: () => onRespond?.({
3812
+ requestId: request.requestId,
3813
+ optionId: option.id
3814
+ }),
3815
+ children: option.label
3816
+ },
3817
+ option.id
3818
+ )) })
3819
+ ]
3820
+ }
3821
+ );
3822
+ }
3823
+
3824
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3825
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3826
+ function HumanInputCard({
3827
+ disabled = false,
3828
+ request,
3829
+ onRespond
3830
+ }) {
3831
+ const [text, setText] = useState6("");
3832
+ const options = request.options ?? [];
3833
+ const showText = request.display === "text" || request.allowFreeform && options.length === 0;
3834
+ if (options.length > 0) {
3835
+ return /* @__PURE__ */ jsx7(
3836
+ ConfirmationCard,
3837
+ {
3838
+ disabled,
3839
+ request,
3840
+ onRespond
3841
+ }
3842
+ );
3843
+ }
3844
+ function submitText(event) {
3845
+ event.preventDefault();
3846
+ const value = text.trim();
3847
+ if (!value || disabled) return;
3848
+ onRespond?.({ requestId: request.requestId, text: value });
3849
+ }
3850
+ return /* @__PURE__ */ jsxs7(
3851
+ "section",
3852
+ {
3853
+ className: "human-input-card",
3854
+ "aria-labelledby": `human-input-${request.requestId}`,
3855
+ children: [
3856
+ /* @__PURE__ */ jsx7("div", { className: "human-input-card__heading", children: /* @__PURE__ */ jsx7("strong", { id: `human-input-${request.requestId}`, children: request.prompt }) }),
3857
+ showText ? /* @__PURE__ */ jsxs7("form", { onSubmit: submitText, children: [
3858
+ /* @__PURE__ */ jsx7("label", { htmlFor: `human-input-text-${request.requestId}`, children: "Response" }),
3859
+ /* @__PURE__ */ jsxs7("div", { children: [
3860
+ /* @__PURE__ */ jsx7(
3861
+ "input",
3862
+ {
3863
+ id: `human-input-text-${request.requestId}`,
3864
+ value: text,
3865
+ disabled,
3866
+ onChange: (event) => setText(event.target.value)
3867
+ }
3868
+ ),
3869
+ /* @__PURE__ */ jsx7("button", { type: "submit", disabled: disabled || !text.trim(), children: "Send" })
3870
+ ] })
3871
+ ] }) : null,
3872
+ !showText && options.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "human-input-card__unavailable", role: "status", children: "This request can\u2019t be answered here." }) : null
3873
+ ]
3874
+ }
3875
+ );
3876
+ }
3877
+
3878
+ // src/react/components/CollectionResultCard/CollectionResultCard.tsx
3879
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3880
+ function CollectionResultCard({
3881
+ result
3882
+ }) {
3883
+ const empty = result.items.length === 0;
3884
+ return /* @__PURE__ */ jsxs8(
3885
+ "section",
3886
+ {
3887
+ className: `collection-result-card tool-result-card tool-result-card--${result.status}`,
3888
+ "aria-label": result.title,
3889
+ children: [
3890
+ /* @__PURE__ */ jsxs8("div", { className: "tool-result-card__heading", children: [
3891
+ /* @__PURE__ */ jsx8("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
3892
+ /* @__PURE__ */ jsx8("strong", { children: result.title })
3893
+ ] }),
3894
+ empty ? /* @__PURE__ */ jsx8("p", { className: "collection-result-card__empty", children: "No matching record for the email you shared." }) : /* @__PURE__ */ jsx8("ul", { className: "collection-result-card__list", children: result.items.map((item) => /* @__PURE__ */ jsxs8("li", { children: [
3895
+ /* @__PURE__ */ jsx8("div", { className: "collection-result-card__item-title", children: item.title }),
3896
+ item.description ? /* @__PURE__ */ jsx8("p", { children: item.description }) : null,
3897
+ item.details?.length ? /* @__PURE__ */ jsx8("dl", { children: item.details.map((detail) => /* @__PURE__ */ jsxs8("div", { children: [
3898
+ /* @__PURE__ */ jsx8("dt", { children: detail.label }),
3899
+ /* @__PURE__ */ jsx8("dd", { children: detail.value })
3900
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
3901
+ item.href ? /* @__PURE__ */ jsx8("a", { href: item.href, target: "_blank", rel: "noreferrer", children: "Open record" }) : null
3902
+ ] }, item.title)) })
3903
+ ]
3904
+ }
3905
+ );
3906
+ }
3907
+
3908
+ // src/react/components/EntityResultCard/EntityResultCard.tsx
3909
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3910
+ function EntityResultCard({
3911
+ result
3912
+ }) {
3913
+ return /* @__PURE__ */ jsxs9(
3914
+ "section",
3915
+ {
3916
+ className: `entity-result-card tool-result-card tool-result-card--${result.status}`,
3917
+ "aria-label": result.title,
3918
+ children: [
3919
+ /* @__PURE__ */ jsxs9("div", { className: "tool-result-card__heading", children: [
3920
+ /* @__PURE__ */ jsx9("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
3921
+ /* @__PURE__ */ jsx9("strong", { children: result.title })
3922
+ ] }),
3923
+ result.description ? /* @__PURE__ */ jsx9("p", { children: result.description }) : null,
3924
+ result.details?.length ? /* @__PURE__ */ jsx9("dl", { children: result.details.map((detail) => /* @__PURE__ */ jsxs9("div", { children: [
3925
+ /* @__PURE__ */ jsx9("dt", { children: detail.label }),
3926
+ /* @__PURE__ */ jsx9("dd", { children: detail.value })
3927
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
3928
+ result.links?.length ? /* @__PURE__ */ jsx9("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ jsx9(
3929
+ "a",
3930
+ {
3931
+ href: link.href,
3932
+ target: "_blank",
3933
+ rel: "noreferrer",
3934
+ children: link.label
3935
+ },
3936
+ link.href
3937
+ )) }) : null
3938
+ ]
3939
+ }
3940
+ );
3941
+ }
3942
+
3943
+ // src/react/components/SignatureResultCard/SignatureResultCard.tsx
3944
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
3945
+ function SignatureResultCard({
3946
+ result
3947
+ }) {
3948
+ const primaryLink = result.links?.[0];
3949
+ return /* @__PURE__ */ jsxs10(
3950
+ "section",
3951
+ {
3952
+ className: `signature-result-card tool-result-card tool-result-card--${result.status}`,
3953
+ "aria-label": result.title,
3954
+ children: [
3955
+ /* @__PURE__ */ jsxs10("div", { className: "tool-result-card__heading", children: [
3956
+ /* @__PURE__ */ jsx10("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
3957
+ /* @__PURE__ */ jsx10("strong", { children: result.title })
3958
+ ] }),
3959
+ result.statusLabel ? /* @__PURE__ */ jsx10("span", { className: "signature-result-card__badge", children: result.statusLabel }) : null,
3960
+ result.description ? /* @__PURE__ */ jsx10("p", { children: result.description }) : null,
3961
+ primaryLink ? /* @__PURE__ */ jsx10(
3962
+ "a",
3963
+ {
3964
+ className: "signature-result-card__cta",
3965
+ href: primaryLink.href,
3966
+ target: "_blank",
3967
+ rel: "noreferrer",
3968
+ children: primaryLink.label
3969
+ }
3970
+ ) : null
3971
+ ]
3972
+ }
3973
+ );
3974
+ }
3975
+
3976
+ // src/react/components/ToolResultCard/ToolResultCard.tsx
3977
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3978
+ function ToolResultCard({
3979
+ result
3980
+ }) {
3981
+ return /* @__PURE__ */ jsxs11(
3982
+ "section",
3983
+ {
3984
+ className: `tool-result-card tool-result-card--${result.status}`,
3985
+ "aria-label": result.title,
3986
+ children: [
3987
+ /* @__PURE__ */ jsxs11("div", { className: "tool-result-card__heading", children: [
3988
+ /* @__PURE__ */ jsx11("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
3989
+ /* @__PURE__ */ jsx11("strong", { children: result.title })
3990
+ ] }),
3991
+ result.description ? /* @__PURE__ */ jsx11("p", { children: result.description }) : null,
3992
+ result.details?.length ? /* @__PURE__ */ jsx11("dl", { children: result.details.map((detail) => /* @__PURE__ */ jsxs11("div", { children: [
3993
+ /* @__PURE__ */ jsx11("dt", { children: detail.label }),
3994
+ /* @__PURE__ */ jsx11("dd", { children: detail.value })
3995
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
3996
+ result.links?.length ? /* @__PURE__ */ jsx11("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ jsx11(
3997
+ "a",
3998
+ {
3999
+ href: link.href,
4000
+ target: "_blank",
4001
+ rel: "noreferrer",
4002
+ children: link.label
4003
+ },
4004
+ link.href
4005
+ )) }) : null
4006
+ ]
4007
+ }
4008
+ );
4009
+ }
4010
+
4011
+ // src/react/components/VisitorToolResultView/VisitorToolResultView.tsx
4012
+ import { jsx as jsx12 } from "react/jsx-runtime";
4013
+ function VisitorToolResultView({
4014
+ result
4015
+ }) {
4016
+ if (result.kind === "entity") {
4017
+ return /* @__PURE__ */ jsx12(EntityResultCard, { result });
4018
+ }
4019
+ if (result.kind === "collection") {
4020
+ return /* @__PURE__ */ jsx12(CollectionResultCard, { result });
4021
+ }
4022
+ if (result.kind === "signature") {
4023
+ return /* @__PURE__ */ jsx12(SignatureResultCard, { result });
4024
+ }
4025
+ if (result.kind === "summary") {
4026
+ return /* @__PURE__ */ jsx12(ToolResultCard, { result });
4027
+ }
4028
+ return null;
4029
+ }
4030
+ function isRenderableVisitorToolResult(result) {
4031
+ return result.kind === "summary" || result.kind === "entity" || result.kind === "collection" || result.kind === "signature";
4032
+ }
4033
+
4034
+ // src/react/components/AgentRail/AgentRail.tsx
4035
+ import { Fragment, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2217
4036
  function MinimizeIcon() {
2218
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4037
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2219
4038
  "path",
2220
4039
  {
2221
4040
  d: "M3.5 8h9",
@@ -2226,7 +4045,7 @@ function MinimizeIcon() {
2226
4045
  ) });
2227
4046
  }
2228
4047
  function CloseIcon() {
2229
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4048
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2230
4049
  "path",
2231
4050
  {
2232
4051
  d: "M4 4l8 8M12 4l-8 8",
@@ -2237,7 +4056,7 @@ function CloseIcon() {
2237
4056
  ) });
2238
4057
  }
2239
4058
  function NewChatIcon() {
2240
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4059
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2241
4060
  "path",
2242
4061
  {
2243
4062
  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",
@@ -2249,7 +4068,7 @@ function NewChatIcon() {
2249
4068
  ) });
2250
4069
  }
2251
4070
  function ExpandIcon() {
2252
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4071
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2253
4072
  "path",
2254
4073
  {
2255
4074
  d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
@@ -2261,7 +4080,7 @@ function ExpandIcon() {
2261
4080
  ) });
2262
4081
  }
2263
4082
  function RestoreIcon() {
2264
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4083
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2265
4084
  "path",
2266
4085
  {
2267
4086
  d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
@@ -2289,28 +4108,29 @@ function AgentRail({
2289
4108
  onRetry,
2290
4109
  onSubmit,
2291
4110
  onFollowUpSelect,
2292
- onBook
4111
+ onBook,
4112
+ onInputResponse
2293
4113
  }) {
2294
4114
  const transcriptRef = useRef3(null);
2295
4115
  const resolvedBrandLabel = brandLabel.trim();
2296
4116
  const resolvedBrandLogoUrl = brandLogoUrl?.trim();
2297
- const [failedLogoUrl, setFailedLogoUrl] = useState6(null);
4117
+ const [failedLogoUrl, setFailedLogoUrl] = useState7(null);
2298
4118
  const showBrandLogo = Boolean(resolvedBrandLogoUrl) && failedLogoUrl !== resolvedBrandLogoUrl;
2299
4119
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2300
4120
  const brandedTheme = { ...defaultAgentRailTheme, ...theme };
2301
4121
  const resolvedTheme = resolvedColorScheme === "dark" ? {
2302
4122
  ...brandedTheme,
2303
4123
  brand: theme?.brand ?? defaultDarkAgentRailTheme.brand,
2304
- brandDeep: defaultDarkAgentRailTheme.brandDeep,
2305
- brandSoft: `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
2306
- border: defaultDarkAgentRailTheme.border,
2307
- danger: defaultDarkAgentRailTheme.danger,
2308
- success: defaultDarkAgentRailTheme.success,
2309
- surface: defaultDarkAgentRailTheme.surface,
2310
- surfaceMuted: defaultDarkAgentRailTheme.surfaceMuted,
2311
- text: defaultDarkAgentRailTheme.text,
2312
- textMuted: defaultDarkAgentRailTheme.textMuted,
2313
- textSubtle: defaultDarkAgentRailTheme.textSubtle,
4124
+ brandDeep: theme?.brandDeep ?? defaultDarkAgentRailTheme.brandDeep,
4125
+ brandSoft: theme?.brandSoft ?? `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
4126
+ border: theme?.border ?? defaultDarkAgentRailTheme.border,
4127
+ danger: theme?.danger ?? defaultDarkAgentRailTheme.danger,
4128
+ success: theme?.success ?? defaultDarkAgentRailTheme.success,
4129
+ surface: theme?.surface ?? defaultDarkAgentRailTheme.surface,
4130
+ surfaceMuted: theme?.surfaceMuted ?? defaultDarkAgentRailTheme.surfaceMuted,
4131
+ text: theme?.text ?? defaultDarkAgentRailTheme.text,
4132
+ textMuted: theme?.textMuted ?? defaultDarkAgentRailTheme.textMuted,
4133
+ textSubtle: theme?.textSubtle ?? defaultDarkAgentRailTheme.textSubtle,
2314
4134
  visitorBubble: theme?.visitorBubble ?? theme?.brand ?? defaultDarkAgentRailTheme.visitorBubble
2315
4135
  } : brandedTheme;
2316
4136
  const railStyle = {
@@ -2333,8 +4153,15 @@ function AgentRail({
2333
4153
  "--as-font-display": resolvedTheme.fontDisplay,
2334
4154
  colorScheme: resolvedColorScheme
2335
4155
  };
2336
- const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming";
4156
+ const pendingInputRequests = (state.pendingInputs ?? []).filter(
4157
+ (request) => shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
4158
+ );
4159
+ const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming" || state.phase === "waiting-input" && pendingInputRequests.length > 0;
4160
+ const semanticSurfaceDisabled = isBusy && state.phase !== "waiting-input";
2337
4161
  const showActivity = state.toolSteps.length > 0;
4162
+ const visitorToolResults = (state.toolResults ?? []).filter(
4163
+ isRenderableVisitorToolResult
4164
+ );
2338
4165
  const hasVisitorMessages2 = state.messages.some(
2339
4166
  (message) => message.role === "visitor"
2340
4167
  );
@@ -2344,22 +4171,44 @@ function AgentRail({
2344
4171
  );
2345
4172
  const visibleMessages = hasVisitorMessages2 ? state.messages : [];
2346
4173
  const lastMessage = visibleMessages.at(-1);
2347
- const completedAnswer = showActivity && state.phase === "complete" && lastMessage?.role === "agent" ? lastMessage : null;
2348
- const transcriptMessages = completedAnswer ? visibleMessages.slice(0, -1) : visibleMessages;
2349
- const streamingMessage = state.phase === "streaming" && state.streamingText ? {
4174
+ let lastAgentIndex = -1;
4175
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4176
+ if (visibleMessages[index]?.role === "agent") {
4177
+ lastAgentIndex = index;
4178
+ break;
4179
+ }
4180
+ }
4181
+ let lastVisitorIndex = -1;
4182
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4183
+ if (visibleMessages[index]?.role === "visitor") {
4184
+ lastVisitorIndex = index;
4185
+ break;
4186
+ }
4187
+ }
4188
+ const lastIsAgent = lastMessage?.role === "agent";
4189
+ const streamingMessage = state.phase === "streaming" && state.streamingText && !lastIsAgent ? {
2350
4190
  createdAt: 0,
2351
4191
  id: "streaming-response",
2352
4192
  role: "agent",
2353
4193
  streaming: true,
2354
4194
  text: state.streamingText
2355
- } : state.pendingOffer ? {
4195
+ } : state.pendingOffer && !lastIsAgent ? {
2356
4196
  createdAt: 0,
2357
4197
  id: "pending-booking",
2358
4198
  role: "agent",
2359
4199
  streaming: false,
2360
4200
  text: "Pick a date and time that works for you."
2361
4201
  } : null;
2362
- useEffect2(() => {
4202
+ const bookingReadyText = state.streamingText || (lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "");
4203
+ const waitingForBooking = !state.pendingOffer && looksLikeBookingReady(bookingReadyText) && (state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming");
4204
+ const lastAgentText = lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "";
4205
+ const composerForm = resolveComposerForm({
4206
+ agentText: lastAgentText,
4207
+ cards: extractToolCards(lastAgentText),
4208
+ hasBookingOffer: Boolean(state.pendingOffer) || waitingForBooking,
4209
+ enabled: lastIsAgent && !isBusy
4210
+ });
4211
+ useEffect3(() => {
2363
4212
  const node = transcriptRef.current;
2364
4213
  if (!node) return;
2365
4214
  node.scrollTop = node.scrollHeight;
@@ -2370,11 +4219,13 @@ function AgentRail({
2370
4219
  state.followUps,
2371
4220
  state.journey
2372
4221
  ]);
2373
- return /* @__PURE__ */ jsxs6(
4222
+ return /* @__PURE__ */ jsxs12(
2374
4223
  "aside",
2375
4224
  {
2376
- className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4225
+ className: `agent-rail not-typeset${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4226
+ "data-not-typeset": "",
2377
4227
  "data-color-scheme": resolvedColorScheme,
4228
+ spellCheck: false,
2378
4229
  style: railStyle,
2379
4230
  "aria-label": "Agent conversation",
2380
4231
  "aria-modal": mobileFullscreen || expanded ? true : void 0,
@@ -2382,28 +4233,28 @@ function AgentRail({
2382
4233
  role: mobileFullscreen || expanded ? "dialog" : void 0,
2383
4234
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
2384
4235
  children: [
2385
- /* @__PURE__ */ jsx6("header", { className: "agent-rail__header", children: /* @__PURE__ */ jsxs6("div", { className: "agent-rail__brand-row", children: [
2386
- onCollapse ? /* @__PURE__ */ jsx6(
4236
+ /* @__PURE__ */ jsx13("header", { className: "agent-rail__header", children: /* @__PURE__ */ jsxs12("div", { className: "agent-rail__brand-row", children: [
4237
+ onCollapse ? /* @__PURE__ */ jsx13(
2387
4238
  "button",
2388
4239
  {
2389
4240
  type: "button",
2390
4241
  className: "agent-rail__collapse",
2391
4242
  "aria-label": "Collapse assist",
2392
4243
  onClick: onCollapse,
2393
- children: /* @__PURE__ */ jsx6(MinimizeIcon, {})
4244
+ children: /* @__PURE__ */ jsx13(MinimizeIcon, {})
2394
4245
  }
2395
- ) : onClose ? /* @__PURE__ */ jsx6(
4246
+ ) : onClose ? /* @__PURE__ */ jsx13(
2396
4247
  "button",
2397
4248
  {
2398
4249
  type: "button",
2399
4250
  className: "agent-rail__close",
2400
4251
  "aria-label": "Close agent",
2401
4252
  onClick: onClose,
2402
- children: /* @__PURE__ */ jsx6(CloseIcon, {})
4253
+ children: /* @__PURE__ */ jsx13(CloseIcon, {})
2403
4254
  }
2404
- ) : /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
2405
- resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ jsxs6("span", { className: "agent-rail__identity", children: [
2406
- showBrandLogo ? /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4255
+ ) : /* @__PURE__ */ jsx13("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
4256
+ resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ jsxs12("span", { className: "agent-rail__identity", children: [
4257
+ showBrandLogo ? /* @__PURE__ */ jsx13("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2407
4258
  "img",
2408
4259
  {
2409
4260
  className: "agent-rail__brand-logo",
@@ -2414,10 +4265,10 @@ function AgentRail({
2414
4265
  }
2415
4266
  }
2416
4267
  ) }) : null,
2417
- resolvedBrandLabel ? /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
4268
+ resolvedBrandLabel ? /* @__PURE__ */ jsx13("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
2418
4269
  ] }) : null,
2419
- /* @__PURE__ */ jsxs6("span", { className: "agent-rail__actions", children: [
2420
- onReset ? /* @__PURE__ */ jsx6(
4270
+ /* @__PURE__ */ jsxs12("span", { className: "agent-rail__actions", children: [
4271
+ onReset ? /* @__PURE__ */ jsx13(
2421
4272
  "button",
2422
4273
  {
2423
4274
  type: "button",
@@ -2425,24 +4276,24 @@ function AgentRail({
2425
4276
  "aria-label": "Start a new conversation",
2426
4277
  disabled: !hasVisitorMessages2,
2427
4278
  onClick: onReset,
2428
- children: /* @__PURE__ */ jsx6(NewChatIcon, {})
4279
+ children: /* @__PURE__ */ jsx13(NewChatIcon, {})
2429
4280
  }
2430
4281
  ) : null,
2431
- onExpandToggle ? /* @__PURE__ */ jsx6(
4282
+ onExpandToggle ? /* @__PURE__ */ jsx13(
2432
4283
  "button",
2433
4284
  {
2434
4285
  type: "button",
2435
4286
  className: "agent-rail__expand",
2436
4287
  "aria-label": expanded ? "Exit full screen" : "Open full screen",
2437
4288
  onClick: onExpandToggle,
2438
- children: expanded ? /* @__PURE__ */ jsx6(RestoreIcon, {}) : /* @__PURE__ */ jsx6(ExpandIcon, {})
4289
+ children: expanded ? /* @__PURE__ */ jsx13(RestoreIcon, {}) : /* @__PURE__ */ jsx13(ExpandIcon, {})
2439
4290
  }
2440
4291
  ) : null
2441
4292
  ] })
2442
4293
  ] }) }),
2443
- /* @__PURE__ */ jsx6("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ jsxs6("div", { className: "agent-rail__thread", children: [
2444
- !hasVisitorMessages2 ? /* @__PURE__ */ jsxs6("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
2445
- greeting?.role === "agent" ? /* @__PURE__ */ jsx6(
4294
+ /* @__PURE__ */ jsx13("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ jsxs12("div", { className: "agent-rail__thread", children: [
4295
+ !hasVisitorMessages2 ? /* @__PURE__ */ jsxs12("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
4296
+ greeting?.role === "agent" ? /* @__PURE__ */ jsx13(
2446
4297
  MessageBubble,
2447
4298
  {
2448
4299
  message: greeting,
@@ -2450,7 +4301,7 @@ function AgentRail({
2450
4301
  onBook
2451
4302
  }
2452
4303
  ) : null,
2453
- showIdleFollowUps ? /* @__PURE__ */ jsx6("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx6(
4304
+ showIdleFollowUps ? /* @__PURE__ */ jsx13("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx13(
2454
4305
  FollowUpChips,
2455
4306
  {
2456
4307
  suggestions: state.followUps,
@@ -2458,64 +4309,94 @@ function AgentRail({
2458
4309
  label: "Start here",
2459
4310
  onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
2460
4311
  }
2461
- ) }) : null
4312
+ ) }) : null,
4313
+ showActivity ? /* @__PURE__ */ jsx13(
4314
+ AgentActivityBubble,
4315
+ {
4316
+ brandLabel: resolvedBrandLabel,
4317
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4318
+ failed: state.phase === "error",
4319
+ steps: state.toolSteps
4320
+ }
4321
+ ) : null,
4322
+ visitorToolResults.map((result) => /* @__PURE__ */ jsx13(VisitorToolResultView, { result }, result.id)),
4323
+ pendingInputRequests.map((request) => /* @__PURE__ */ jsx13(
4324
+ HumanInputCard,
4325
+ {
4326
+ request,
4327
+ onRespond: onInputResponse
4328
+ },
4329
+ request.requestId
4330
+ ))
2462
4331
  ] }) : null,
2463
- transcriptMessages.map((message) => /* @__PURE__ */ jsx6(
2464
- MessageBubble,
2465
- {
2466
- message,
2467
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2468
- onBook
2469
- },
2470
- message.id
2471
- )),
2472
- showActivity ? /* @__PURE__ */ jsx6(
2473
- AgentActivityBubble,
2474
- {
2475
- brandLabel: resolvedBrandLabel,
2476
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2477
- failed: state.phase === "error",
2478
- steps: state.toolSteps
2479
- }
2480
- ) : null,
2481
- completedAnswer ? /* @__PURE__ */ jsx6(
4332
+ visibleMessages.map((message, index) => /* @__PURE__ */ jsxs12("div", { className: "agent-rail__turn-block", children: [
4333
+ /* @__PURE__ */ jsx13(
4334
+ MessageBubble,
4335
+ {
4336
+ message,
4337
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4338
+ offer: index === lastAgentIndex ? state.pendingOffer : void 0,
4339
+ onBook
4340
+ }
4341
+ ),
4342
+ index === lastVisitorIndex ? /* @__PURE__ */ jsxs12(Fragment, { children: [
4343
+ showActivity ? /* @__PURE__ */ jsx13(
4344
+ AgentActivityBubble,
4345
+ {
4346
+ brandLabel: resolvedBrandLabel,
4347
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4348
+ failed: state.phase === "error",
4349
+ steps: state.toolSteps
4350
+ }
4351
+ ) : null,
4352
+ visitorToolResults.map((result) => /* @__PURE__ */ jsx13(VisitorToolResultView, { result }, result.id)),
4353
+ pendingInputRequests.map((request) => /* @__PURE__ */ jsx13(
4354
+ HumanInputCard,
4355
+ {
4356
+ request,
4357
+ onRespond: onInputResponse
4358
+ },
4359
+ request.requestId
4360
+ ))
4361
+ ] }) : null
4362
+ ] }, message.id)),
4363
+ streamingMessage ? /* @__PURE__ */ jsx13(
2482
4364
  MessageBubble,
2483
4365
  {
2484
- message: completedAnswer,
4366
+ message: streamingMessage,
2485
4367
  brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4368
+ offer: state.pendingOffer,
2486
4369
  onBook
2487
4370
  }
2488
4371
  ) : null,
2489
- streamingMessage ? /* @__PURE__ */ jsx6(
2490
- MessageBubble,
4372
+ waitingForBooking ? /* @__PURE__ */ jsx13(
4373
+ BookingCard,
2491
4374
  {
2492
- message: streamingMessage,
2493
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2494
- offer: state.pendingOffer,
2495
- onBook
4375
+ offer: { type: "booking_offer", eventTypes: [], slots: [] }
2496
4376
  }
2497
4377
  ) : null,
2498
- state.error ? /* @__PURE__ */ jsxs6("section", { className: "agent-rail__error", role: "alert", children: [
2499
- /* @__PURE__ */ jsxs6("div", { children: [
2500
- /* @__PURE__ */ jsx6("strong", { children: "Something went wrong" }),
2501
- /* @__PURE__ */ jsx6("p", { children: state.error })
4378
+ state.error ? /* @__PURE__ */ jsxs12("section", { className: "agent-rail__error", role: "alert", children: [
4379
+ /* @__PURE__ */ jsxs12("div", { children: [
4380
+ /* @__PURE__ */ jsx13("strong", { children: "Something went wrong" }),
4381
+ /* @__PURE__ */ jsx13("p", { children: state.error })
2502
4382
  ] }),
2503
- onRetry ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
4383
+ onRetry ? /* @__PURE__ */ jsx13("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
2504
4384
  ] }) : null
2505
4385
  ] }) }),
2506
- /* @__PURE__ */ jsxs6("div", { className: "agent-rail__composer-wrap", children: [
2507
- /* @__PURE__ */ jsx6(
4386
+ /* @__PURE__ */ jsxs12("div", { className: "agent-rail__composer-wrap", children: [
4387
+ /* @__PURE__ */ jsx13(
2508
4388
  Composer,
2509
4389
  {
2510
4390
  variant: expanded || mobileFullscreen ? "dock" : "default",
2511
4391
  disabled: isBusy,
4392
+ form: composerForm,
2512
4393
  placeholder: composerPlaceholder,
2513
4394
  onSubmit
2514
4395
  }
2515
4396
  ),
2516
- /* @__PURE__ */ jsx6("div", { className: "agent-rail__footer", children: /* @__PURE__ */ jsxs6("p", { children: [
2517
- /* @__PURE__ */ jsx6("span", { children: "AI can make mistakes. Check important info." }),
2518
- /* @__PURE__ */ jsx6("span", { children: poweredByLabel })
4397
+ /* @__PURE__ */ jsx13("div", { className: "agent-rail__footer", children: /* @__PURE__ */ jsxs12("p", { children: [
4398
+ /* @__PURE__ */ jsx13("span", { children: "AI can make mistakes. Check important info." }),
4399
+ /* @__PURE__ */ jsx13("span", { children: poweredByLabel })
2519
4400
  ] }) })
2520
4401
  ] })
2521
4402
  ]
@@ -2524,9 +4405,9 @@ function AgentRail({
2524
4405
  }
2525
4406
 
2526
4407
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
2527
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
4408
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2528
4409
  function SparklesIcon() {
2529
- return /* @__PURE__ */ jsxs7(
4410
+ return /* @__PURE__ */ jsxs13(
2530
4411
  "svg",
2531
4412
  {
2532
4413
  className: "assist-edge-tab__sparkles",
@@ -2534,21 +4415,21 @@ function SparklesIcon() {
2534
4415
  fill: "none",
2535
4416
  "aria-hidden": "true",
2536
4417
  children: [
2537
- /* @__PURE__ */ jsx7(
4418
+ /* @__PURE__ */ jsx14(
2538
4419
  "path",
2539
4420
  {
2540
4421
  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",
2541
4422
  fill: "currentColor"
2542
4423
  }
2543
4424
  ),
2544
- /* @__PURE__ */ jsx7(
4425
+ /* @__PURE__ */ jsx14(
2545
4426
  "path",
2546
4427
  {
2547
4428
  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",
2548
4429
  fill: "currentColor"
2549
4430
  }
2550
4431
  ),
2551
- /* @__PURE__ */ jsx7(
4432
+ /* @__PURE__ */ jsx14(
2552
4433
  "path",
2553
4434
  {
2554
4435
  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",
@@ -2562,7 +4443,7 @@ function SparklesIcon() {
2562
4443
  function TabMarkIcon({ customIconUrl }) {
2563
4444
  const url = customIconUrl?.trim();
2564
4445
  if (url) {
2565
- return /* @__PURE__ */ jsx7(
4446
+ return /* @__PURE__ */ jsx14(
2566
4447
  "img",
2567
4448
  {
2568
4449
  alt: "",
@@ -2572,10 +4453,10 @@ function TabMarkIcon({ customIconUrl }) {
2572
4453
  }
2573
4454
  );
2574
4455
  }
2575
- return /* @__PURE__ */ jsx7(SparklesIcon, {});
4456
+ return /* @__PURE__ */ jsx14(SparklesIcon, {});
2576
4457
  }
2577
4458
  function ChevronLeftIcon() {
2578
- return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
4459
+ return /* @__PURE__ */ jsx14("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx14(
2579
4460
  "path",
2580
4461
  {
2581
4462
  d: "M10 4L6 8l4 4",
@@ -2587,7 +4468,7 @@ function ChevronLeftIcon() {
2587
4468
  ) });
2588
4469
  }
2589
4470
  function ChevronDownIcon() {
2590
- return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
4471
+ return /* @__PURE__ */ jsx14("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx14(
2591
4472
  "path",
2592
4473
  {
2593
4474
  d: "M4 6l4 4 4-4",
@@ -2599,7 +4480,7 @@ function ChevronDownIcon() {
2599
4480
  ) });
2600
4481
  }
2601
4482
  function DragDots() {
2602
- return /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ jsx7("i", {}, index)) });
4483
+ return /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ jsx14("i", {}, index)) });
2603
4484
  }
2604
4485
  var VARIANT_COPY = {
2605
4486
  outline: { label: "Ask anything", aria: "Ask anything" },
@@ -2628,6 +4509,7 @@ function AssistEdgeTab({
2628
4509
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2629
4510
  const copy = VARIANT_COPY[variant];
2630
4511
  const visibleLabel = label?.trim() || copy.label;
4512
+ const alignment = along < 50 ? "start" : along > 50 ? "end" : "center";
2631
4513
  const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
2632
4514
  const resolvedBrandColor = brandColor ?? (resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
2633
4515
  const resolvedBorderColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.border : borderColor;
@@ -2644,11 +4526,11 @@ function AssistEdgeTab({
2644
4526
  ...resolvedTextColor ? { "--as-text": resolvedTextColor } : {},
2645
4527
  colorScheme: resolvedColorScheme
2646
4528
  };
2647
- return /* @__PURE__ */ jsxs7(
4529
+ return /* @__PURE__ */ jsxs13(
2648
4530
  "button",
2649
4531
  {
2650
4532
  type: "button",
2651
- className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
4533
+ 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" : ""}`,
2652
4534
  "data-color-scheme": resolvedColorScheme,
2653
4535
  style,
2654
4536
  "aria-label": `Open ${visibleLabel}`,
@@ -2656,15 +4538,15 @@ function AssistEdgeTab({
2656
4538
  tabIndex: visible ? 0 : -1,
2657
4539
  onClick: onOpen,
2658
4540
  children: [
2659
- mobile ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2660
- /* @__PURE__ */ jsxs7(
4541
+ mobile ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
4542
+ /* @__PURE__ */ jsxs13(
2661
4543
  "span",
2662
4544
  {
2663
4545
  className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
2664
4546
  "aria-hidden": "true",
2665
4547
  children: [
2666
- /* @__PURE__ */ jsx7(TabMarkIcon, { customIconUrl }),
2667
- showLogo ? /* @__PURE__ */ jsx7(
4548
+ /* @__PURE__ */ jsx14(TabMarkIcon, { customIconUrl }),
4549
+ showLogo ? /* @__PURE__ */ jsx14(
2668
4550
  "img",
2669
4551
  {
2670
4552
  className: "assist-edge-tab__logo",
@@ -2678,11 +4560,11 @@ function AssistEdgeTab({
2678
4560
  ]
2679
4561
  }
2680
4562
  ),
2681
- /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: visibleLabel })
2682
- ] }) : variant === "outline" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2683
- /* @__PURE__ */ jsxs7("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2684
- /* @__PURE__ */ jsx7(TabMarkIcon, { customIconUrl }),
2685
- showLogo ? /* @__PURE__ */ jsx7(
4563
+ /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel })
4564
+ ] }) : variant === "outline" ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
4565
+ /* @__PURE__ */ jsxs13("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4566
+ /* @__PURE__ */ jsx14(TabMarkIcon, { customIconUrl }),
4567
+ showLogo ? /* @__PURE__ */ jsx14(
2686
4568
  "img",
2687
4569
  {
2688
4570
  className: "assist-edge-tab__logo",
@@ -2694,18 +4576,18 @@ function AssistEdgeTab({
2694
4576
  }
2695
4577
  ) : null
2696
4578
  ] }),
2697
- /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2698
- /* @__PURE__ */ jsx7(ChevronDownIcon, {})
4579
+ /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4580
+ /* @__PURE__ */ jsx14(ChevronDownIcon, {})
2699
4581
  ] }) : null,
2700
- variant === "ask" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2701
- /* @__PURE__ */ jsx7(ChevronLeftIcon, {}),
2702
- /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2703
- /* @__PURE__ */ jsx7(DragDots, {})
4582
+ variant === "ask" ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
4583
+ /* @__PURE__ */ jsx14(ChevronLeftIcon, {}),
4584
+ /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4585
+ /* @__PURE__ */ jsx14(DragDots, {})
2704
4586
  ] }) : null,
2705
- variant === "fill" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2706
- /* @__PURE__ */ jsxs7("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
2707
- /* @__PURE__ */ jsx7(TabMarkIcon, { customIconUrl }),
2708
- showLogo ? /* @__PURE__ */ jsx7(
4587
+ variant === "fill" ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
4588
+ /* @__PURE__ */ jsxs13("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4589
+ /* @__PURE__ */ jsx14(TabMarkIcon, { customIconUrl }),
4590
+ showLogo ? /* @__PURE__ */ jsx14(
2709
4591
  "img",
2710
4592
  {
2711
4593
  className: "assist-edge-tab__logo",
@@ -2717,8 +4599,8 @@ function AssistEdgeTab({
2717
4599
  }
2718
4600
  ) : null
2719
4601
  ] }),
2720
- /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2721
- /* @__PURE__ */ jsx7(ChevronLeftIcon, {})
4602
+ /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4603
+ /* @__PURE__ */ jsx14(ChevronLeftIcon, {})
2722
4604
  ] }) : null
2723
4605
  ]
2724
4606
  }
@@ -2726,10 +4608,10 @@ function AssistEdgeTab({
2726
4608
  }
2727
4609
 
2728
4610
  // src/react/components/AgentWidget/AgentWidget.tsx
2729
- import { useEffect as useEffect5, useRef as useRef4, useState as useState8 } from "react";
4611
+ import { useEffect as useEffect6, useRef as useRef4, useState as useState9 } from "react";
2730
4612
 
2731
4613
  // src/react/page-shift.ts
2732
- import { useEffect as useEffect3 } from "react";
4614
+ import { useEffect as useEffect4 } from "react";
2733
4615
  var PAGE_SHIFT_CLASS = "webless-agent-page-shift";
2734
4616
  var DEFAULT_RAIL_WIDTH_PX = 450;
2735
4617
  function shouldApplyPageShift(input) {
@@ -2781,7 +4663,7 @@ function clearPageMargin() {
2781
4663
  }
2782
4664
  function usePageShift(input) {
2783
4665
  const { active, railSlotRef } = input;
2784
- useEffect3(() => {
4666
+ useEffect4(() => {
2785
4667
  if (typeof document === "undefined") {
2786
4668
  return;
2787
4669
  }
@@ -2809,12 +4691,12 @@ function usePageShift(input) {
2809
4691
  }
2810
4692
 
2811
4693
  // src/react/hooks/useIsMobile.ts
2812
- import { useEffect as useEffect4, useState as useState7 } from "react";
4694
+ import { useEffect as useEffect5, useState as useState8 } from "react";
2813
4695
  function useIsMobile(breakpoint = 767) {
2814
- const [isMobile, setIsMobile] = useState7(
4696
+ const [isMobile, setIsMobile] = useState8(
2815
4697
  () => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
2816
4698
  );
2817
- useEffect4(() => {
4699
+ useEffect5(() => {
2818
4700
  const media = window.matchMedia(`(max-width: ${breakpoint}px)`);
2819
4701
  const onChange = () => setIsMobile(media.matches);
2820
4702
  onChange();
@@ -2824,23 +4706,8 @@ function useIsMobile(breakpoint = 767) {
2824
4706
  return isMobile;
2825
4707
  }
2826
4708
 
2827
- // src/react/panel-controller.ts
2828
- var controllers = /* @__PURE__ */ new Map();
2829
- function registerAgentPanelController(customerId, controller) {
2830
- controllers.set(customerId, controller);
2831
- }
2832
- function unregisterAgentPanelController(customerId) {
2833
- controllers.delete(customerId);
2834
- }
2835
- function openAgentPanel(customerId) {
2836
- controllers.get(customerId)?.open();
2837
- }
2838
- function closeAgentPanel(customerId) {
2839
- controllers.get(customerId)?.close();
2840
- }
2841
-
2842
4709
  // src/react/components/AgentWidget/AgentWidget.tsx
2843
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
4710
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2844
4711
  function AgentWidget({
2845
4712
  indexId,
2846
4713
  customerId,
@@ -2853,13 +4720,14 @@ function AgentWidget({
2853
4720
  pageShift = true,
2854
4721
  registerPanelController = false,
2855
4722
  colorScheme = "auto",
2856
- branding
4723
+ branding,
4724
+ toolResultRegistry
2857
4725
  }) {
2858
4726
  const isMobile = useIsMobile();
2859
4727
  const placement = normalizeAgentPlacement(placementInput);
2860
4728
  const railSlotRef = useRef4(null);
2861
- const [railCollapsed, setRailCollapsed] = useState8(defaultCollapsed);
2862
- const [railExpanded, setRailExpanded] = useState8(false);
4729
+ const [railCollapsed, setRailCollapsed] = useState9(defaultCollapsed);
4730
+ const [railExpanded, setRailExpanded] = useState9(false);
2863
4731
  const pageShiftActive = shouldApplyPageShift({
2864
4732
  pageShift,
2865
4733
  isMobile,
@@ -2870,14 +4738,15 @@ function AgentWidget({
2870
4738
  active: pageShiftActive,
2871
4739
  railSlotRef
2872
4740
  });
2873
- const { state, reset, retry, submit } = useAgentChat({
4741
+ const { state, reset, retry, respondToInput, respondToToolInput, submit } = useAgentChat({
2874
4742
  customerId,
2875
4743
  getUnpublishedPreviewGrant,
2876
4744
  indexId,
2877
4745
  previewBuildId,
2878
4746
  version,
2879
4747
  runtimeOrigin,
2880
- greeting: branding?.greeting
4748
+ greeting: branding?.greeting,
4749
+ toolResultRegistry
2881
4750
  });
2882
4751
  const agentName = branding?.agentName ?? "";
2883
4752
  const tabLabel = branding?.tabLabel ?? agentName;
@@ -2898,22 +4767,24 @@ function AgentWidget({
2898
4767
  } : {},
2899
4768
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
2900
4769
  };
2901
- useEffect5(() => {
4770
+ useEffect6(() => {
2902
4771
  if (!registerPanelController) return;
2903
4772
  registerAgentPanelController(customerId, {
2904
4773
  open: () => setRailCollapsed(false),
2905
4774
  close: () => {
2906
4775
  setRailCollapsed(true);
2907
4776
  setRailExpanded(false);
2908
- }
4777
+ },
4778
+ reset,
4779
+ submit
2909
4780
  });
2910
4781
  return () => unregisterAgentPanelController(customerId);
2911
- }, [customerId, registerPanelController]);
4782
+ }, [customerId, registerPanelController, reset, submit]);
2912
4783
  async function handleSubmit(message) {
2913
4784
  if (isMobile) setRailCollapsed(false);
2914
4785
  await submit(message);
2915
4786
  }
2916
- useEffect5(() => {
4787
+ useEffect6(() => {
2917
4788
  if (railCollapsed) return;
2918
4789
  const handleKeyDown = (event) => {
2919
4790
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -2947,19 +4818,19 @@ function AgentWidget({
2947
4818
  window.addEventListener("keydown", handleKeyDown);
2948
4819
  return () => window.removeEventListener("keydown", handleKeyDown);
2949
4820
  }, [isMobile, railCollapsed, railExpanded]);
2950
- return /* @__PURE__ */ jsxs8("div", { className: "webless-agent-root", children: [
2951
- /* @__PURE__ */ jsx8(
4821
+ return /* @__PURE__ */ jsxs14("div", { className: "webless-agent-root", children: [
4822
+ /* @__PURE__ */ jsx15(
2952
4823
  "div",
2953
4824
  {
2954
4825
  className: `webless-agent-root__shell${railCollapsed ? " webless-agent-root__shell--collapsed" : ""}${railExpanded ? " webless-agent-root__shell--expanded" : ""}`,
2955
- children: /* @__PURE__ */ jsx8(
4826
+ children: /* @__PURE__ */ jsx15(
2956
4827
  "div",
2957
4828
  {
2958
4829
  ref: railSlotRef,
2959
4830
  className: "webless-agent-root__rail-slot",
2960
4831
  inert: railCollapsed || void 0,
2961
4832
  "aria-hidden": railCollapsed,
2962
- children: /* @__PURE__ */ jsx8(
4833
+ children: /* @__PURE__ */ jsx15(
2963
4834
  AgentRail,
2964
4835
  {
2965
4836
  theme,
@@ -2977,6 +4848,8 @@ function AgentWidget({
2977
4848
  onSubmit: handleSubmit,
2978
4849
  onReset: reset,
2979
4850
  onRetry: () => void retry(),
4851
+ onInputResponse: (response) => void respondToInput(response),
4852
+ onToolInput: (surface, values) => void respondToToolInput(surface, values),
2980
4853
  onFollowUpSelect: (label) => void handleSubmit(label),
2981
4854
  onBook: (input) => void submit(input.displayText, { runtimeText: input.runtimeText })
2982
4855
  }
@@ -2985,7 +4858,7 @@ function AgentWidget({
2985
4858
  )
2986
4859
  }
2987
4860
  ),
2988
- railCollapsed ? /* @__PURE__ */ jsx8(
4861
+ railCollapsed ? /* @__PURE__ */ jsx15(
2989
4862
  AssistEdgeTab,
2990
4863
  {
2991
4864
  variant: placement.variant,
@@ -3013,6 +4886,8 @@ function AgentWidget({
3013
4886
  export {
3014
4887
  DEFAULT_RUNTIME_ORIGIN,
3015
4888
  resolveAgentRuntimeConfig,
4889
+ builtInVisitorToolResultRegistry,
4890
+ presentVisitorToolResult,
3016
4891
  useAgentChat,
3017
4892
  hasVisitorMessages,
3018
4893
  createIdleSuggestions,
@@ -3021,10 +4896,12 @@ export {
3021
4896
  normalizeAgentPlacement,
3022
4897
  openAgentPanel,
3023
4898
  closeAgentPanel,
4899
+ resetAgentPanel,
4900
+ submitAgentPanel,
3024
4901
  defaultAgentRailTheme,
3025
4902
  defaultDarkAgentRailTheme,
3026
4903
  AgentRail,
3027
4904
  AssistEdgeTab,
3028
4905
  AgentWidget
3029
4906
  };
3030
- //# sourceMappingURL=chunk-HXKD46I6.js.map
4907
+ //# sourceMappingURL=chunk-Y4DCEJDC.js.map