@webless/agent 0.6.3 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -632,10 +1694,58 @@ var AgentSession = class {
632
1694
  );
633
1695
  if (isTurnBoundary(event)) break;
634
1696
  }
635
- session = client.sessions.attach(session.state.sessionId, { streamIndex });
636
- this.session = session;
637
- this.persistSessionCursor(session);
638
- if (!rendered.trim() && !signal.aborted) {
1697
+ session = client.sessions.attach(session.state.sessionId, { streamIndex });
1698
+ this.session = session;
1699
+ this.persistSessionCursor(session);
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
  );
@@ -1746,10 +3046,11 @@ function AgentActivityBubble({
1746
3046
  ),
1747
3047
  detailsOpen ? /* @__PURE__ */ jsx("ol", { className: "agent-activity-bubble__steps", children: visibleSteps.map((step) => {
1748
3048
  const detail = stepDetail(step, steps);
3049
+ const child = delegated && step.kind === "specialist";
1749
3050
  return /* @__PURE__ */ jsxs(
1750
3051
  "li",
1751
3052
  {
1752
- className: "agent-activity-bubble__step",
3053
+ className: `agent-activity-bubble__step${child ? " agent-activity-bubble__step--child" : ""}`,
1753
3054
  "data-kind": step.kind,
1754
3055
  "data-state": step.state,
1755
3056
  children: [
@@ -1757,25 +3058,27 @@ function AgentActivityBubble({
1757
3058
  "span",
1758
3059
  {
1759
3060
  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()
3061
+ "aria-hidden": "true"
1774
3062
  }
1775
3063
  ),
1776
3064
  /* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__step-copy", children: [
1777
3065
  /* @__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
3066
+ detail ? /* @__PURE__ */ jsx("span", { className: "agent-activity-bubble__step-detail", children: detail }) : null,
3067
+ delegated && step.kind === "planning" ? /* @__PURE__ */ jsxs("span", { className: "agent-activity-bubble__delegation", children: [
3068
+ "Delegated ",
3069
+ delegationCount,
3070
+ " ",
3071
+ delegationCount === 1 ? "task" : "tasks"
3072
+ ] }) : null,
3073
+ step.state === "error" && onRetryStep ? /* @__PURE__ */ jsx(
3074
+ "button",
3075
+ {
3076
+ type: "button",
3077
+ className: "agent-activity-bubble__step-retry",
3078
+ onClick: () => onRetryStep(step),
3079
+ children: "Retry"
3080
+ }
3081
+ ) : null
1779
3082
  ] })
1780
3083
  ]
1781
3084
  },
@@ -1787,62 +3090,198 @@ function AgentActivityBubble({
1787
3090
  }
1788
3091
 
1789
3092
  // src/react/components/Composer/Composer.tsx
1790
- import { useRef as useRef2, useState as useState3 } from "react";
3093
+ import {
3094
+ useEffect as useEffect2,
3095
+ useId,
3096
+ useRef as useRef2,
3097
+ useState as useState3
3098
+ } from "react";
1791
3099
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1792
3100
  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" }) });
3101
+ return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
3102
+ "path",
3103
+ {
3104
+ d: "M8 12V4M8 4l-3 3M8 4l3 3",
3105
+ stroke: "currentColor",
3106
+ strokeWidth: "1.5",
3107
+ strokeLinecap: "round",
3108
+ strokeLinejoin: "round"
3109
+ }
3110
+ ) });
3111
+ }
3112
+ function emptyValues(form) {
3113
+ const values = {};
3114
+ for (const field of form?.fields ?? []) values[field.id] = "";
3115
+ return values;
1794
3116
  }
1795
3117
  function Composer({
1796
3118
  disabled = false,
1797
3119
  placeholder = "Ask anything\u2026",
1798
3120
  variant = "default",
3121
+ form = null,
1799
3122
  onSubmit
1800
3123
  }) {
1801
3124
  const [value, setValue] = useState3("");
3125
+ const [values, setValues] = useState3(
3126
+ () => emptyValues(form)
3127
+ );
3128
+ const [blurred, setBlurred] = useState3({});
1802
3129
  const inputRef = useRef2(null);
1803
- function submitCurrent() {
3130
+ const firstFieldRef = useRef2(null);
3131
+ const formId = useId();
3132
+ const activeForm = form;
3133
+ const canSendForm = activeForm ? isComposerFormComplete(activeForm, values) : Boolean(value.trim());
3134
+ useEffect2(() => {
3135
+ setValues(emptyValues(form));
3136
+ setBlurred({});
3137
+ }, [form?.id]);
3138
+ useEffect2(() => {
3139
+ if (activeForm) firstFieldRef.current?.focus();
3140
+ }, [activeForm?.id]);
3141
+ function submitChat() {
1804
3142
  const trimmed = value.trim();
1805
3143
  if (!trimmed || disabled) return;
1806
3144
  onSubmit?.(trimmed);
1807
3145
  setValue("");
1808
3146
  inputRef.current?.focus();
1809
3147
  }
3148
+ function submitForm() {
3149
+ if (!activeForm || disabled || !canSendForm) return;
3150
+ onSubmit?.(formatComposerFormMessage(activeForm, values));
3151
+ setValues(emptyValues(activeForm));
3152
+ setBlurred({});
3153
+ }
1810
3154
  function handleSubmit(event) {
1811
3155
  event.preventDefault();
1812
- submitCurrent();
3156
+ if (activeForm) submitForm();
3157
+ else submitChat();
1813
3158
  }
1814
- function handleKeyDown(event) {
3159
+ function handleChatKeyDown(event) {
1815
3160
  if (event.key === "Enter" && !event.shiftKey) {
1816
3161
  event.preventDefault();
1817
- submitCurrent();
3162
+ submitChat();
3163
+ }
3164
+ }
3165
+ function handleFormKeyDown(event) {
3166
+ const target = event.target;
3167
+ const isTextarea = Boolean(target && "tagName" in target && target.tagName === "TEXTAREA");
3168
+ if (event.key === "Enter" && !event.shiftKey && !isTextarea) {
3169
+ event.preventDefault();
3170
+ submitForm();
3171
+ }
3172
+ }
3173
+ return /* @__PURE__ */ jsx2(
3174
+ "form",
3175
+ {
3176
+ className: [
3177
+ "composer",
3178
+ variant === "dock" ? "composer--dock" : "",
3179
+ activeForm ? "composer--form" : ""
3180
+ ].filter(Boolean).join(" "),
3181
+ onSubmit: handleSubmit,
3182
+ children: activeForm ? /* @__PURE__ */ jsxs2("div", { className: "composer__sheet", role: "group", "aria-label": "Required details", children: [
3183
+ activeForm.fields.map((field, index) => {
3184
+ const fieldId = `${formId}-${field.id}`;
3185
+ const invalid = Boolean(blurred[field.id]) && !isValidComposerFieldValue(field, values[field.id] ?? "");
3186
+ const controlProps = {
3187
+ id: fieldId,
3188
+ name: field.id,
3189
+ disabled,
3190
+ required: field.required,
3191
+ autoComplete: field.autocomplete,
3192
+ placeholder: field.placeholder,
3193
+ spellCheck: false,
3194
+ value: values[field.id] ?? "",
3195
+ "aria-invalid": invalid || void 0,
3196
+ "aria-describedby": invalid ? `${fieldId}-error` : void 0,
3197
+ onBlur: () => setBlurred((current) => ({ ...current, [field.id]: true })),
3198
+ onChange: (event) => {
3199
+ const next = readComposerControlValue(event);
3200
+ setValues((current) => ({
3201
+ ...current,
3202
+ [field.id]: next
3203
+ }));
3204
+ },
3205
+ onKeyDown: handleFormKeyDown
3206
+ };
3207
+ return /* @__PURE__ */ jsxs2(
3208
+ "div",
3209
+ {
3210
+ className: [
3211
+ "composer__row",
3212
+ field.kind === "textarea" ? "composer__row--grow" : ""
3213
+ ].filter(Boolean).join(" "),
3214
+ children: [
3215
+ /* @__PURE__ */ jsxs2("label", { className: "composer__label", htmlFor: fieldId, children: [
3216
+ /* @__PURE__ */ jsx2("span", { className: "composer__sr-only", children: field.label }),
3217
+ field.kind === "textarea" ? /* @__PURE__ */ jsx2(
3218
+ "textarea",
3219
+ {
3220
+ ...controlProps,
3221
+ ref: index === 0 ? (node) => {
3222
+ firstFieldRef.current = node;
3223
+ } : void 0,
3224
+ className: "composer__control composer__control--area",
3225
+ rows: 3
3226
+ }
3227
+ ) : /* @__PURE__ */ jsx2(
3228
+ "input",
3229
+ {
3230
+ ...controlProps,
3231
+ ref: index === 0 ? (node) => {
3232
+ firstFieldRef.current = node;
3233
+ } : void 0,
3234
+ className: "composer__control",
3235
+ type: field.kind,
3236
+ inputMode: field.kind === "tel" ? "tel" : void 0
3237
+ }
3238
+ )
3239
+ ] }),
3240
+ 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
3241
+ ]
3242
+ },
3243
+ field.id
3244
+ );
3245
+ }),
3246
+ /* @__PURE__ */ jsx2("div", { className: "composer__toolbar", children: /* @__PURE__ */ jsx2(
3247
+ "button",
3248
+ {
3249
+ type: "submit",
3250
+ className: "composer__send",
3251
+ disabled: disabled || !canSendForm,
3252
+ "aria-label": "Send details",
3253
+ children: /* @__PURE__ */ jsx2(SendIcon, {})
3254
+ }
3255
+ ) })
3256
+ ] }) : /* @__PURE__ */ jsxs2("div", { className: "composer__field", children: [
3257
+ /* @__PURE__ */ jsx2(
3258
+ "textarea",
3259
+ {
3260
+ ref: inputRef,
3261
+ className: "composer__input",
3262
+ rows: 1,
3263
+ value,
3264
+ placeholder,
3265
+ disabled,
3266
+ spellCheck: false,
3267
+ "aria-label": "Message",
3268
+ onChange: (event) => setValue(event.target.value),
3269
+ onKeyDown: handleChatKeyDown
3270
+ }
3271
+ ),
3272
+ /* @__PURE__ */ jsx2(
3273
+ "button",
3274
+ {
3275
+ type: "submit",
3276
+ className: "composer__send",
3277
+ disabled: disabled || !value.trim(),
3278
+ "aria-label": "Send message",
3279
+ children: /* @__PURE__ */ jsx2(SendIcon, {})
3280
+ }
3281
+ )
3282
+ ] })
1818
3283
  }
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
- ] }) });
3284
+ );
1846
3285
  }
1847
3286
 
1848
3287
  // src/react/components/FollowUpChips/FollowUpChips.tsx
@@ -1888,11 +3327,17 @@ function FollowUpChips({
1888
3327
  import { useState as useState5 } from "react";
1889
3328
 
1890
3329
  // src/react/components/BookingCard/BookingCard.tsx
1891
- import { useId, useMemo as useMemo2, useState as useState4 } from "react";
3330
+ import { useId as useId2, useMemo as useMemo2, useState as useState4 } from "react";
1892
3331
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3332
+ var BOOKING_STEPS = [
3333
+ { id: "date", label: "Date" },
3334
+ { id: "time", label: "Time" },
3335
+ { id: "details", label: "Details" }
3336
+ ];
1893
3337
  function monthFromKey(key) {
1894
3338
  const [year, month] = key.split("-").map(Number);
1895
- if (!year || !month) return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
3339
+ if (!year || !month)
3340
+ return { year: (/* @__PURE__ */ new Date()).getFullYear(), month: (/* @__PURE__ */ new Date()).getMonth() };
1896
3341
  return { year, month: month - 1 };
1897
3342
  }
1898
3343
  function dateKeyFromParts(year, month, day) {
@@ -1917,7 +3362,7 @@ function BookingCard({
1917
3362
  offer,
1918
3363
  onBook
1919
3364
  }) {
1920
- const fieldId = useId();
3365
+ const fieldId = useId2();
1921
3366
  const defaultType = offer.eventTypes[0]?.uri ?? offer.slots[0]?.eventTypeUri ?? "";
1922
3367
  const [step, setStep] = useState4("date");
1923
3368
  const [eventTypeUri, setEventTypeUri] = useState4(defaultType);
@@ -1925,6 +3370,7 @@ function BookingCard({
1925
3370
  const [startTime, setStartTime] = useState4("");
1926
3371
  const [name, setName] = useState4("");
1927
3372
  const [email, setEmail] = useState4("");
3373
+ const stepIndex = BOOKING_STEPS.findIndex((item) => item.id === step);
1928
3374
  const slots = useMemo2(
1929
3375
  () => bookingSlotsForEventType(offer.slots, eventTypeUri),
1930
3376
  [eventTypeUri, offer.slots]
@@ -1945,14 +3391,18 @@ function BookingCard({
1945
3391
  setSelectedDate("");
1946
3392
  setStartTime("");
1947
3393
  setVisibleMonth(
1948
- firstAvailableBookingMonth(bookingSlotsForEventType(offer.slots, nextType))
3394
+ firstAvailableBookingMonth(
3395
+ bookingSlotsForEventType(offer.slots, nextType)
3396
+ )
1949
3397
  );
1950
3398
  }
1951
3399
  const daySlots = useMemo2(
1952
3400
  () => slots.filter((slot) => slotDateKey(slot.startTime) === selectedDate),
1953
3401
  [selectedDate, slots]
1954
3402
  );
1955
- const selectedType = offer.eventTypes.find((item) => item.uri === eventTypeUri);
3403
+ const selectedType = offer.eventTypes.find(
3404
+ (item) => item.uri === eventTypeUri
3405
+ );
1956
3406
  const selectedSample = availableByDate.get(selectedDate) ?? startTime;
1957
3407
  const timeZone = formatSlotTimeZone(slots[0]?.startTime ?? selectedSample);
1958
3408
  const weekdays = useMemo2(() => weekdayLabels(), []);
@@ -1998,24 +3448,47 @@ function BookingCard({
1998
3448
  });
1999
3449
  }
2000
3450
  return /* @__PURE__ */ jsx4("section", { className: "booking-card", "aria-label": "Book a meeting", children: /* @__PURE__ */ jsxs4("form", { className: "booking-card__form", onSubmit: handleSubmit, children: [
3451
+ /* @__PURE__ */ jsx4("ol", { className: "booking-card__steps", "aria-label": "Booking steps", children: BOOKING_STEPS.map((item, index) => /* @__PURE__ */ jsxs4(
3452
+ "li",
3453
+ {
3454
+ className: [
3455
+ "booking-card__step-indicator",
3456
+ index === stepIndex ? "booking-card__step-indicator--active" : "",
3457
+ index < stepIndex ? "booking-card__step-indicator--complete" : ""
3458
+ ].filter(Boolean).join(" "),
3459
+ "aria-current": index === stepIndex ? "step" : void 0,
3460
+ children: [
3461
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: index + 1 }),
3462
+ /* @__PURE__ */ jsx4("span", { children: item.label })
3463
+ ]
3464
+ },
3465
+ item.id
3466
+ )) }),
2001
3467
  step === "date" ? /* @__PURE__ */ jsxs4("div", { className: "booking-card__step", children: [
2002
3468
  /* @__PURE__ */ jsx4("p", { className: "booking-card__title", children: selectedType?.name || "Pick a date" }),
2003
3469
  timeZone ? /* @__PURE__ */ jsxs4("p", { className: "booking-card__tz", children: [
2004
3470
  "Times in ",
2005
3471
  timeZone
2006
3472
  ] }) : 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,
3473
+ offer.eventTypes.length > 1 ? /* @__PURE__ */ jsxs4(
3474
+ "label",
3475
+ {
3476
+ className: "booking-card__field",
3477
+ htmlFor: `${fieldId}-type`,
3478
+ children: [
3479
+ /* @__PURE__ */ jsx4("span", { children: "Meeting" }),
3480
+ /* @__PURE__ */ jsx4(
3481
+ "select",
3482
+ {
3483
+ id: `${fieldId}-type`,
3484
+ value: eventTypeUri,
3485
+ onChange: (event) => selectEventType(event.target.value),
3486
+ children: offer.eventTypes.map((item) => /* @__PURE__ */ jsx4("option", { value: item.uri, children: item.name }, item.uri))
3487
+ }
3488
+ )
3489
+ ]
3490
+ }
3491
+ ) : null,
2019
3492
  /* @__PURE__ */ jsxs4("div", { className: "booking-card__month", children: [
2020
3493
  /* @__PURE__ */ jsx4(
2021
3494
  "button",
@@ -2042,29 +3515,44 @@ function BookingCard({
2042
3515
  )
2043
3516
  ] }),
2044
3517
  /* @__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}`);
3518
+ offer.slots.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "booking-card__loading", role: "status", children: "Finding available times\u2026" }) : null,
3519
+ /* @__PURE__ */ jsx4(
3520
+ "div",
3521
+ {
3522
+ className: "booking-card__calendar",
3523
+ role: "grid",
3524
+ "aria-label": "Available dates",
3525
+ children: cells.map((cell, index) => {
3526
+ if (!cell) {
3527
+ return /* @__PURE__ */ jsx4(
3528
+ "span",
3529
+ {
3530
+ className: "booking-card__day"
3531
+ },
3532
+ `empty-${index}`
3533
+ );
3534
+ }
3535
+ const available = availableByDate.has(cell.key);
3536
+ const selected = cell.key === selectedDate;
3537
+ return /* @__PURE__ */ jsx4(
3538
+ "button",
3539
+ {
3540
+ type: "button",
3541
+ className: [
3542
+ "booking-card__day",
3543
+ available ? "booking-card__day--available" : "",
3544
+ selected ? "booking-card__day--selected" : ""
3545
+ ].filter(Boolean).join(" "),
3546
+ disabled: !available,
3547
+ "aria-pressed": selected,
3548
+ onClick: () => selectDate(cell.key),
3549
+ children: cell.day
3550
+ },
3551
+ cell.key
3552
+ );
3553
+ })
2048
3554
  }
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
- }) })
3555
+ )
2068
3556
  ] }, "date") : null,
2069
3557
  step === "time" ? /* @__PURE__ */ jsxs4("div", { className: "booking-card__step", children: [
2070
3558
  /* @__PURE__ */ jsxs4("div", { className: "booking-card__step-bar", children: [
@@ -2116,35 +3604,57 @@ function BookingCard({
2116
3604
  ] })
2117
3605
  ] }),
2118
3606
  /* @__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
- ] })
3607
+ /* @__PURE__ */ jsxs4(
3608
+ "label",
3609
+ {
3610
+ className: "booking-card__field",
3611
+ htmlFor: `${fieldId}-name`,
3612
+ children: [
3613
+ /* @__PURE__ */ jsx4("span", { children: "Name" }),
3614
+ /* @__PURE__ */ jsx4(
3615
+ "input",
3616
+ {
3617
+ id: `${fieldId}-name`,
3618
+ autoComplete: "name",
3619
+ value: name,
3620
+ onChange: (event) => setName(event.target.value),
3621
+ required: true
3622
+ }
3623
+ )
3624
+ ]
3625
+ }
3626
+ ),
3627
+ /* @__PURE__ */ jsxs4(
3628
+ "label",
3629
+ {
3630
+ className: "booking-card__field",
3631
+ htmlFor: `${fieldId}-email`,
3632
+ children: [
3633
+ /* @__PURE__ */ jsx4("span", { children: "Email" }),
3634
+ /* @__PURE__ */ jsx4(
3635
+ "input",
3636
+ {
3637
+ id: `${fieldId}-email`,
3638
+ type: "email",
3639
+ autoComplete: "email",
3640
+ value: email,
3641
+ onChange: (event) => setEmail(event.target.value),
3642
+ required: true
3643
+ }
3644
+ )
3645
+ ]
3646
+ }
3647
+ )
2146
3648
  ] }),
2147
- /* @__PURE__ */ jsx4("button", { type: "submit", className: "booking-card__submit", children: "Book this time" })
3649
+ /* @__PURE__ */ jsx4(
3650
+ "button",
3651
+ {
3652
+ type: "submit",
3653
+ className: "booking-card__submit",
3654
+ disabled: !name.trim() || !email.trim(),
3655
+ children: "Book this time"
3656
+ }
3657
+ )
2148
3658
  ] }, "details") : null
2149
3659
  ] }) });
2150
3660
  }
@@ -2153,6 +3663,43 @@ function BookingCard({
2153
3663
  import { Streamdown } from "streamdown";
2154
3664
  import "streamdown/styles.css";
2155
3665
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3666
+ function normalizeDedupeText(text) {
3667
+ return text.trim().replace(/\s+/g, " ").toLowerCase();
3668
+ }
3669
+ function paragraphsAreNearDuplicates(first, second) {
3670
+ const left = normalizeDedupeText(first);
3671
+ const right = normalizeDedupeText(second);
3672
+ if (left.length < 40 || right.length < 40) return false;
3673
+ if (left === right) return true;
3674
+ const shorter = left.length <= right.length ? left : right;
3675
+ const longer = left.length <= right.length ? right : left;
3676
+ return longer.startsWith(
3677
+ shorter.slice(0, Math.floor(shorter.length * 0.85))
3678
+ );
3679
+ }
3680
+ function paragraphsShareOpening(first, second) {
3681
+ const opening = first.split("\n")[0]?.trim();
3682
+ if (!opening || opening.length < 20) return false;
3683
+ return second.trim().startsWith(opening);
3684
+ }
3685
+ function collapseRepeatedText(text) {
3686
+ const trimmed = text.trim();
3687
+ if (trimmed.length < 40) return trimmed;
3688
+ const paragraphs = trimmed.split(/\n{2,}/u).map((part) => part.trim()).filter(Boolean);
3689
+ if (paragraphs.length === 2 && (paragraphsAreNearDuplicates(paragraphs[0], paragraphs[1]) || paragraphsShareOpening(paragraphs[0], paragraphs[1]))) {
3690
+ return paragraphs[0];
3691
+ }
3692
+ if (paragraphs.length >= 2 && paragraphs.length % 2 === 0) {
3693
+ const mid2 = paragraphs.length / 2;
3694
+ const first = paragraphs.slice(0, mid2).join("\n\n");
3695
+ const second = paragraphs.slice(mid2).join("\n\n");
3696
+ if (first === second) return first;
3697
+ }
3698
+ const mid = Math.floor(trimmed.length / 2);
3699
+ const left = trimmed.slice(0, mid).trim();
3700
+ const right = trimmed.slice(mid).trim();
3701
+ return left.length >= 20 && left === right ? left : trimmed;
3702
+ }
2156
3703
  function MessageBubble({
2157
3704
  message,
2158
3705
  brandLogoUrl,
@@ -2169,24 +3716,41 @@ function MessageBubble({
2169
3716
  const offers = offer ? [offer] : extractedOffers;
2170
3717
  const visibleText = hideToolCardFences(message.text);
2171
3718
  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);
3719
+ const displayText = collapseRepeatedText(
3720
+ offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
3721
+ );
2173
3722
  if (message.role === "visitor") {
2174
3723
  return /* @__PURE__ */ jsx5("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ jsx5("p", { className: "message-bubble__text", children: message.text }) });
2175
3724
  }
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
- ) });
3725
+ const citations = message.citations ?? [];
3726
+ const agentText = /* @__PURE__ */ jsxs5("div", { className: "message-bubble__text", children: [
3727
+ /* @__PURE__ */ jsx5(
3728
+ Streamdown,
3729
+ {
3730
+ animated: isStreaming,
3731
+ caret: "circle",
3732
+ className: "message-bubble__markdown",
3733
+ controls: false,
3734
+ isAnimating: isStreaming,
3735
+ linkSafety: { enabled: false },
3736
+ mode: isStreaming ? "streaming" : "static",
3737
+ skipHtml: true,
3738
+ children: displayText
3739
+ }
3740
+ ),
3741
+ citations.length > 0 ? /* @__PURE__ */ jsx5("ul", { className: "message-bubble__sources", "aria-label": "Sources", children: citations.map((citation) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs5(
3742
+ "a",
3743
+ {
3744
+ href: citation.url,
3745
+ target: "_blank",
3746
+ rel: "noreferrer",
3747
+ children: [
3748
+ /* @__PURE__ */ jsx5("span", { className: "message-bubble__source-icon", "aria-hidden": "true", children: "\u25A6" }),
3749
+ /* @__PURE__ */ jsx5("span", { children: citation.label })
3750
+ ]
3751
+ }
3752
+ ) }, citation.id)) }) : null
3753
+ ] });
2190
3754
  return /* @__PURE__ */ jsxs5("article", { className: "message-bubble message-bubble--agent", children: [
2191
3755
  displayText ? showBrandLogo ? /* @__PURE__ */ jsxs5("div", { className: "message-bubble__agent-row", children: [
2192
3756
  /* @__PURE__ */ jsx5("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
@@ -2212,10 +3776,262 @@ function MessageBubble({
2212
3776
  ] });
2213
3777
  }
2214
3778
 
2215
- // src/react/components/AgentRail/AgentRail.tsx
3779
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3780
+ import { useState as useState6 } from "react";
3781
+
3782
+ // src/react/components/ConfirmationCard/ConfirmationCard.tsx
2216
3783
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3784
+ function ConfirmationCard({
3785
+ disabled = false,
3786
+ request,
3787
+ onRespond
3788
+ }) {
3789
+ const options = request.options ?? [];
3790
+ const heading = request.kind === "tool-approval" ? "Confirm this action" : request.prompt;
3791
+ const prompt = request.kind === "tool-approval" ? request.prompt : void 0;
3792
+ return /* @__PURE__ */ jsxs6(
3793
+ "section",
3794
+ {
3795
+ className: "confirmation-card",
3796
+ "aria-labelledby": `confirmation-${request.requestId}`,
3797
+ children: [
3798
+ /* @__PURE__ */ jsxs6("div", { className: "confirmation-card__heading", children: [
3799
+ /* @__PURE__ */ jsx6("strong", { id: `confirmation-${request.requestId}`, children: heading }),
3800
+ prompt ? /* @__PURE__ */ jsx6("p", { children: prompt }) : null
3801
+ ] }),
3802
+ /* @__PURE__ */ jsx6("div", { className: "confirmation-card__actions", children: options.map((option) => /* @__PURE__ */ jsx6(
3803
+ "button",
3804
+ {
3805
+ type: "button",
3806
+ className: `confirmation-card__action confirmation-card__action--${option.style ?? "default"}`,
3807
+ disabled,
3808
+ onClick: () => onRespond?.({
3809
+ requestId: request.requestId,
3810
+ optionId: option.id
3811
+ }),
3812
+ children: option.label
3813
+ },
3814
+ option.id
3815
+ )) })
3816
+ ]
3817
+ }
3818
+ );
3819
+ }
3820
+
3821
+ // src/react/components/HumanInputCard/HumanInputCard.tsx
3822
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3823
+ function HumanInputCard({
3824
+ disabled = false,
3825
+ request,
3826
+ onRespond
3827
+ }) {
3828
+ const [text, setText] = useState6("");
3829
+ const options = request.options ?? [];
3830
+ const showText = request.display === "text" || request.allowFreeform && options.length === 0;
3831
+ if (options.length > 0) {
3832
+ return /* @__PURE__ */ jsx7(
3833
+ ConfirmationCard,
3834
+ {
3835
+ disabled,
3836
+ request,
3837
+ onRespond
3838
+ }
3839
+ );
3840
+ }
3841
+ function submitText(event) {
3842
+ event.preventDefault();
3843
+ const value = text.trim();
3844
+ if (!value || disabled) return;
3845
+ onRespond?.({ requestId: request.requestId, text: value });
3846
+ }
3847
+ return /* @__PURE__ */ jsxs7(
3848
+ "section",
3849
+ {
3850
+ className: "human-input-card",
3851
+ "aria-labelledby": `human-input-${request.requestId}`,
3852
+ children: [
3853
+ /* @__PURE__ */ jsx7("div", { className: "human-input-card__heading", children: /* @__PURE__ */ jsx7("strong", { id: `human-input-${request.requestId}`, children: request.prompt }) }),
3854
+ showText ? /* @__PURE__ */ jsxs7("form", { onSubmit: submitText, children: [
3855
+ /* @__PURE__ */ jsx7("label", { htmlFor: `human-input-text-${request.requestId}`, children: "Response" }),
3856
+ /* @__PURE__ */ jsxs7("div", { children: [
3857
+ /* @__PURE__ */ jsx7(
3858
+ "input",
3859
+ {
3860
+ id: `human-input-text-${request.requestId}`,
3861
+ value: text,
3862
+ disabled,
3863
+ onChange: (event) => setText(event.target.value)
3864
+ }
3865
+ ),
3866
+ /* @__PURE__ */ jsx7("button", { type: "submit", disabled: disabled || !text.trim(), children: "Send" })
3867
+ ] })
3868
+ ] }) : null,
3869
+ !showText && options.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "human-input-card__unavailable", role: "status", children: "This request can\u2019t be answered here." }) : null
3870
+ ]
3871
+ }
3872
+ );
3873
+ }
3874
+
3875
+ // src/react/components/CollectionResultCard/CollectionResultCard.tsx
3876
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3877
+ function CollectionResultCard({
3878
+ result
3879
+ }) {
3880
+ const empty = result.items.length === 0;
3881
+ return /* @__PURE__ */ jsxs8(
3882
+ "section",
3883
+ {
3884
+ className: `collection-result-card tool-result-card tool-result-card--${result.status}`,
3885
+ "aria-label": result.title,
3886
+ children: [
3887
+ /* @__PURE__ */ jsxs8("div", { className: "tool-result-card__heading", children: [
3888
+ /* @__PURE__ */ jsx8("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
3889
+ /* @__PURE__ */ jsx8("strong", { children: result.title })
3890
+ ] }),
3891
+ 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: [
3892
+ /* @__PURE__ */ jsx8("div", { className: "collection-result-card__item-title", children: item.title }),
3893
+ item.description ? /* @__PURE__ */ jsx8("p", { children: item.description }) : null,
3894
+ item.details?.length ? /* @__PURE__ */ jsx8("dl", { children: item.details.map((detail) => /* @__PURE__ */ jsxs8("div", { children: [
3895
+ /* @__PURE__ */ jsx8("dt", { children: detail.label }),
3896
+ /* @__PURE__ */ jsx8("dd", { children: detail.value })
3897
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
3898
+ item.href ? /* @__PURE__ */ jsx8("a", { href: item.href, target: "_blank", rel: "noreferrer", children: "Open record" }) : null
3899
+ ] }, item.title)) })
3900
+ ]
3901
+ }
3902
+ );
3903
+ }
3904
+
3905
+ // src/react/components/EntityResultCard/EntityResultCard.tsx
3906
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3907
+ function EntityResultCard({
3908
+ result
3909
+ }) {
3910
+ return /* @__PURE__ */ jsxs9(
3911
+ "section",
3912
+ {
3913
+ className: `entity-result-card tool-result-card tool-result-card--${result.status}`,
3914
+ "aria-label": result.title,
3915
+ children: [
3916
+ /* @__PURE__ */ jsxs9("div", { className: "tool-result-card__heading", children: [
3917
+ /* @__PURE__ */ jsx9("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
3918
+ /* @__PURE__ */ jsx9("strong", { children: result.title })
3919
+ ] }),
3920
+ result.description ? /* @__PURE__ */ jsx9("p", { children: result.description }) : null,
3921
+ result.details?.length ? /* @__PURE__ */ jsx9("dl", { children: result.details.map((detail) => /* @__PURE__ */ jsxs9("div", { children: [
3922
+ /* @__PURE__ */ jsx9("dt", { children: detail.label }),
3923
+ /* @__PURE__ */ jsx9("dd", { children: detail.value })
3924
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
3925
+ result.links?.length ? /* @__PURE__ */ jsx9("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ jsx9(
3926
+ "a",
3927
+ {
3928
+ href: link.href,
3929
+ target: "_blank",
3930
+ rel: "noreferrer",
3931
+ children: link.label
3932
+ },
3933
+ link.href
3934
+ )) }) : null
3935
+ ]
3936
+ }
3937
+ );
3938
+ }
3939
+
3940
+ // src/react/components/SignatureResultCard/SignatureResultCard.tsx
3941
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
3942
+ function SignatureResultCard({
3943
+ result
3944
+ }) {
3945
+ const primaryLink = result.links?.[0];
3946
+ return /* @__PURE__ */ jsxs10(
3947
+ "section",
3948
+ {
3949
+ className: `signature-result-card tool-result-card tool-result-card--${result.status}`,
3950
+ "aria-label": result.title,
3951
+ children: [
3952
+ /* @__PURE__ */ jsxs10("div", { className: "tool-result-card__heading", children: [
3953
+ /* @__PURE__ */ jsx10("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
3954
+ /* @__PURE__ */ jsx10("strong", { children: result.title })
3955
+ ] }),
3956
+ result.statusLabel ? /* @__PURE__ */ jsx10("span", { className: "signature-result-card__badge", children: result.statusLabel }) : null,
3957
+ result.description ? /* @__PURE__ */ jsx10("p", { children: result.description }) : null,
3958
+ primaryLink ? /* @__PURE__ */ jsx10(
3959
+ "a",
3960
+ {
3961
+ className: "signature-result-card__cta",
3962
+ href: primaryLink.href,
3963
+ target: "_blank",
3964
+ rel: "noreferrer",
3965
+ children: primaryLink.label
3966
+ }
3967
+ ) : null
3968
+ ]
3969
+ }
3970
+ );
3971
+ }
3972
+
3973
+ // src/react/components/ToolResultCard/ToolResultCard.tsx
3974
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3975
+ function ToolResultCard({
3976
+ result
3977
+ }) {
3978
+ return /* @__PURE__ */ jsxs11(
3979
+ "section",
3980
+ {
3981
+ className: `tool-result-card tool-result-card--${result.status}`,
3982
+ "aria-label": result.title,
3983
+ children: [
3984
+ /* @__PURE__ */ jsxs11("div", { className: "tool-result-card__heading", children: [
3985
+ /* @__PURE__ */ jsx11("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
3986
+ /* @__PURE__ */ jsx11("strong", { children: result.title })
3987
+ ] }),
3988
+ result.description ? /* @__PURE__ */ jsx11("p", { children: result.description }) : null,
3989
+ result.details?.length ? /* @__PURE__ */ jsx11("dl", { children: result.details.map((detail) => /* @__PURE__ */ jsxs11("div", { children: [
3990
+ /* @__PURE__ */ jsx11("dt", { children: detail.label }),
3991
+ /* @__PURE__ */ jsx11("dd", { children: detail.value })
3992
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
3993
+ result.links?.length ? /* @__PURE__ */ jsx11("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ jsx11(
3994
+ "a",
3995
+ {
3996
+ href: link.href,
3997
+ target: "_blank",
3998
+ rel: "noreferrer",
3999
+ children: link.label
4000
+ },
4001
+ link.href
4002
+ )) }) : null
4003
+ ]
4004
+ }
4005
+ );
4006
+ }
4007
+
4008
+ // src/react/components/VisitorToolResultView/VisitorToolResultView.tsx
4009
+ import { jsx as jsx12 } from "react/jsx-runtime";
4010
+ function VisitorToolResultView({
4011
+ result
4012
+ }) {
4013
+ if (result.kind === "entity") {
4014
+ return /* @__PURE__ */ jsx12(EntityResultCard, { result });
4015
+ }
4016
+ if (result.kind === "collection") {
4017
+ return /* @__PURE__ */ jsx12(CollectionResultCard, { result });
4018
+ }
4019
+ if (result.kind === "signature") {
4020
+ return /* @__PURE__ */ jsx12(SignatureResultCard, { result });
4021
+ }
4022
+ if (result.kind === "summary") {
4023
+ return /* @__PURE__ */ jsx12(ToolResultCard, { result });
4024
+ }
4025
+ return null;
4026
+ }
4027
+ function isRenderableVisitorToolResult(result) {
4028
+ return result.kind === "summary" || result.kind === "entity" || result.kind === "collection" || result.kind === "signature";
4029
+ }
4030
+
4031
+ // src/react/components/AgentRail/AgentRail.tsx
4032
+ import { Fragment, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2217
4033
  function MinimizeIcon() {
2218
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4034
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2219
4035
  "path",
2220
4036
  {
2221
4037
  d: "M3.5 8h9",
@@ -2226,7 +4042,7 @@ function MinimizeIcon() {
2226
4042
  ) });
2227
4043
  }
2228
4044
  function CloseIcon() {
2229
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4045
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2230
4046
  "path",
2231
4047
  {
2232
4048
  d: "M4 4l8 8M12 4l-8 8",
@@ -2237,7 +4053,7 @@ function CloseIcon() {
2237
4053
  ) });
2238
4054
  }
2239
4055
  function NewChatIcon() {
2240
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4056
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2241
4057
  "path",
2242
4058
  {
2243
4059
  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 +4065,7 @@ function NewChatIcon() {
2249
4065
  ) });
2250
4066
  }
2251
4067
  function ExpandIcon() {
2252
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4068
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2253
4069
  "path",
2254
4070
  {
2255
4071
  d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
@@ -2261,7 +4077,7 @@ function ExpandIcon() {
2261
4077
  ) });
2262
4078
  }
2263
4079
  function RestoreIcon() {
2264
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
4080
+ return /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2265
4081
  "path",
2266
4082
  {
2267
4083
  d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
@@ -2289,28 +4105,29 @@ function AgentRail({
2289
4105
  onRetry,
2290
4106
  onSubmit,
2291
4107
  onFollowUpSelect,
2292
- onBook
4108
+ onBook,
4109
+ onInputResponse
2293
4110
  }) {
2294
4111
  const transcriptRef = useRef3(null);
2295
4112
  const resolvedBrandLabel = brandLabel.trim();
2296
4113
  const resolvedBrandLogoUrl = brandLogoUrl?.trim();
2297
- const [failedLogoUrl, setFailedLogoUrl] = useState6(null);
4114
+ const [failedLogoUrl, setFailedLogoUrl] = useState7(null);
2298
4115
  const showBrandLogo = Boolean(resolvedBrandLogoUrl) && failedLogoUrl !== resolvedBrandLogoUrl;
2299
4116
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2300
4117
  const brandedTheme = { ...defaultAgentRailTheme, ...theme };
2301
4118
  const resolvedTheme = resolvedColorScheme === "dark" ? {
2302
4119
  ...brandedTheme,
2303
4120
  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,
4121
+ brandDeep: theme?.brandDeep ?? defaultDarkAgentRailTheme.brandDeep,
4122
+ brandSoft: theme?.brandSoft ?? `color-mix(in srgb, ${theme?.brand ?? defaultDarkAgentRailTheme.brand} 18%, ${defaultDarkAgentRailTheme.surface})`,
4123
+ border: theme?.border ?? defaultDarkAgentRailTheme.border,
4124
+ danger: theme?.danger ?? defaultDarkAgentRailTheme.danger,
4125
+ success: theme?.success ?? defaultDarkAgentRailTheme.success,
4126
+ surface: theme?.surface ?? defaultDarkAgentRailTheme.surface,
4127
+ surfaceMuted: theme?.surfaceMuted ?? defaultDarkAgentRailTheme.surfaceMuted,
4128
+ text: theme?.text ?? defaultDarkAgentRailTheme.text,
4129
+ textMuted: theme?.textMuted ?? defaultDarkAgentRailTheme.textMuted,
4130
+ textSubtle: theme?.textSubtle ?? defaultDarkAgentRailTheme.textSubtle,
2314
4131
  visitorBubble: theme?.visitorBubble ?? theme?.brand ?? defaultDarkAgentRailTheme.visitorBubble
2315
4132
  } : brandedTheme;
2316
4133
  const railStyle = {
@@ -2333,8 +4150,15 @@ function AgentRail({
2333
4150
  "--as-font-display": resolvedTheme.fontDisplay,
2334
4151
  colorScheme: resolvedColorScheme
2335
4152
  };
2336
- const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming";
4153
+ const pendingInputRequests = (state.pendingInputs ?? []).filter(
4154
+ (request) => shouldRenderVisitorInputCard(request) && (!state.pendingOffer || request.kind === "tool-approval")
4155
+ );
4156
+ const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming" || state.phase === "waiting-input" && pendingInputRequests.length > 0;
4157
+ const semanticSurfaceDisabled = isBusy && state.phase !== "waiting-input";
2337
4158
  const showActivity = state.toolSteps.length > 0;
4159
+ const visitorToolResults = (state.toolResults ?? []).filter(
4160
+ isRenderableVisitorToolResult
4161
+ );
2338
4162
  const hasVisitorMessages2 = state.messages.some(
2339
4163
  (message) => message.role === "visitor"
2340
4164
  );
@@ -2344,22 +4168,44 @@ function AgentRail({
2344
4168
  );
2345
4169
  const visibleMessages = hasVisitorMessages2 ? state.messages : [];
2346
4170
  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 ? {
4171
+ let lastAgentIndex = -1;
4172
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4173
+ if (visibleMessages[index]?.role === "agent") {
4174
+ lastAgentIndex = index;
4175
+ break;
4176
+ }
4177
+ }
4178
+ let lastVisitorIndex = -1;
4179
+ for (let index = visibleMessages.length - 1; index >= 0; index -= 1) {
4180
+ if (visibleMessages[index]?.role === "visitor") {
4181
+ lastVisitorIndex = index;
4182
+ break;
4183
+ }
4184
+ }
4185
+ const lastIsAgent = lastMessage?.role === "agent";
4186
+ const streamingMessage = state.phase === "streaming" && state.streamingText && !lastIsAgent ? {
2350
4187
  createdAt: 0,
2351
4188
  id: "streaming-response",
2352
4189
  role: "agent",
2353
4190
  streaming: true,
2354
4191
  text: state.streamingText
2355
- } : state.pendingOffer ? {
4192
+ } : state.pendingOffer && !lastIsAgent ? {
2356
4193
  createdAt: 0,
2357
4194
  id: "pending-booking",
2358
4195
  role: "agent",
2359
4196
  streaming: false,
2360
4197
  text: "Pick a date and time that works for you."
2361
4198
  } : null;
2362
- useEffect2(() => {
4199
+ const bookingReadyText = state.streamingText || (lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "");
4200
+ const waitingForBooking = !state.pendingOffer && looksLikeBookingReady(bookingReadyText) && (state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming");
4201
+ const lastAgentText = lastIsAgent && lastMessage?.role === "agent" ? lastMessage.text : "";
4202
+ const composerForm = resolveComposerForm({
4203
+ agentText: lastAgentText,
4204
+ cards: extractToolCards(lastAgentText),
4205
+ hasBookingOffer: Boolean(state.pendingOffer) || waitingForBooking,
4206
+ enabled: lastIsAgent && !isBusy
4207
+ });
4208
+ useEffect3(() => {
2363
4209
  const node = transcriptRef.current;
2364
4210
  if (!node) return;
2365
4211
  node.scrollTop = node.scrollHeight;
@@ -2370,11 +4216,13 @@ function AgentRail({
2370
4216
  state.followUps,
2371
4217
  state.journey
2372
4218
  ]);
2373
- return /* @__PURE__ */ jsxs6(
4219
+ return /* @__PURE__ */ jsxs12(
2374
4220
  "aside",
2375
4221
  {
2376
- className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4222
+ className: `agent-rail not-typeset${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
4223
+ "data-not-typeset": "",
2377
4224
  "data-color-scheme": resolvedColorScheme,
4225
+ spellCheck: false,
2378
4226
  style: railStyle,
2379
4227
  "aria-label": "Agent conversation",
2380
4228
  "aria-modal": mobileFullscreen || expanded ? true : void 0,
@@ -2382,28 +4230,28 @@ function AgentRail({
2382
4230
  role: mobileFullscreen || expanded ? "dialog" : void 0,
2383
4231
  tabIndex: mobileFullscreen || expanded ? -1 : void 0,
2384
4232
  children: [
2385
- /* @__PURE__ */ jsx6("header", { className: "agent-rail__header", children: /* @__PURE__ */ jsxs6("div", { className: "agent-rail__brand-row", children: [
2386
- onCollapse ? /* @__PURE__ */ jsx6(
4233
+ /* @__PURE__ */ jsx13("header", { className: "agent-rail__header", children: /* @__PURE__ */ jsxs12("div", { className: "agent-rail__brand-row", children: [
4234
+ onCollapse ? /* @__PURE__ */ jsx13(
2387
4235
  "button",
2388
4236
  {
2389
4237
  type: "button",
2390
4238
  className: "agent-rail__collapse",
2391
4239
  "aria-label": "Collapse assist",
2392
4240
  onClick: onCollapse,
2393
- children: /* @__PURE__ */ jsx6(MinimizeIcon, {})
4241
+ children: /* @__PURE__ */ jsx13(MinimizeIcon, {})
2394
4242
  }
2395
- ) : onClose ? /* @__PURE__ */ jsx6(
4243
+ ) : onClose ? /* @__PURE__ */ jsx13(
2396
4244
  "button",
2397
4245
  {
2398
4246
  type: "button",
2399
4247
  className: "agent-rail__close",
2400
4248
  "aria-label": "Close agent",
2401
4249
  onClick: onClose,
2402
- children: /* @__PURE__ */ jsx6(CloseIcon, {})
4250
+ children: /* @__PURE__ */ jsx13(CloseIcon, {})
2403
4251
  }
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(
4252
+ ) : /* @__PURE__ */ jsx13("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
4253
+ resolvedBrandLabel || showBrandLogo ? /* @__PURE__ */ jsxs12("span", { className: "agent-rail__identity", children: [
4254
+ showBrandLogo ? /* @__PURE__ */ jsx13("span", { className: "agent-rail__brand-mark", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(
2407
4255
  "img",
2408
4256
  {
2409
4257
  className: "agent-rail__brand-logo",
@@ -2414,10 +4262,10 @@ function AgentRail({
2414
4262
  }
2415
4263
  }
2416
4264
  ) }) : null,
2417
- resolvedBrandLabel ? /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
4265
+ resolvedBrandLabel ? /* @__PURE__ */ jsx13("span", { className: "agent-rail__brand-label", children: resolvedBrandLabel }) : null
2418
4266
  ] }) : null,
2419
- /* @__PURE__ */ jsxs6("span", { className: "agent-rail__actions", children: [
2420
- onReset ? /* @__PURE__ */ jsx6(
4267
+ /* @__PURE__ */ jsxs12("span", { className: "agent-rail__actions", children: [
4268
+ onReset ? /* @__PURE__ */ jsx13(
2421
4269
  "button",
2422
4270
  {
2423
4271
  type: "button",
@@ -2425,24 +4273,24 @@ function AgentRail({
2425
4273
  "aria-label": "Start a new conversation",
2426
4274
  disabled: !hasVisitorMessages2,
2427
4275
  onClick: onReset,
2428
- children: /* @__PURE__ */ jsx6(NewChatIcon, {})
4276
+ children: /* @__PURE__ */ jsx13(NewChatIcon, {})
2429
4277
  }
2430
4278
  ) : null,
2431
- onExpandToggle ? /* @__PURE__ */ jsx6(
4279
+ onExpandToggle ? /* @__PURE__ */ jsx13(
2432
4280
  "button",
2433
4281
  {
2434
4282
  type: "button",
2435
4283
  className: "agent-rail__expand",
2436
4284
  "aria-label": expanded ? "Exit full screen" : "Open full screen",
2437
4285
  onClick: onExpandToggle,
2438
- children: expanded ? /* @__PURE__ */ jsx6(RestoreIcon, {}) : /* @__PURE__ */ jsx6(ExpandIcon, {})
4286
+ children: expanded ? /* @__PURE__ */ jsx13(RestoreIcon, {}) : /* @__PURE__ */ jsx13(ExpandIcon, {})
2439
4287
  }
2440
4288
  ) : null
2441
4289
  ] })
2442
4290
  ] }) }),
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(
4291
+ /* @__PURE__ */ jsx13("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ jsxs12("div", { className: "agent-rail__thread", children: [
4292
+ !hasVisitorMessages2 ? /* @__PURE__ */ jsxs12("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
4293
+ greeting?.role === "agent" ? /* @__PURE__ */ jsx13(
2446
4294
  MessageBubble,
2447
4295
  {
2448
4296
  message: greeting,
@@ -2450,7 +4298,7 @@ function AgentRail({
2450
4298
  onBook
2451
4299
  }
2452
4300
  ) : null,
2453
- showIdleFollowUps ? /* @__PURE__ */ jsx6("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx6(
4301
+ showIdleFollowUps ? /* @__PURE__ */ jsx13("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx13(
2454
4302
  FollowUpChips,
2455
4303
  {
2456
4304
  suggestions: state.followUps,
@@ -2458,64 +4306,94 @@ function AgentRail({
2458
4306
  label: "Start here",
2459
4307
  onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
2460
4308
  }
2461
- ) }) : null
4309
+ ) }) : null,
4310
+ showActivity ? /* @__PURE__ */ jsx13(
4311
+ AgentActivityBubble,
4312
+ {
4313
+ brandLabel: resolvedBrandLabel,
4314
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4315
+ failed: state.phase === "error",
4316
+ steps: state.toolSteps
4317
+ }
4318
+ ) : null,
4319
+ visitorToolResults.map((result) => /* @__PURE__ */ jsx13(VisitorToolResultView, { result }, result.id)),
4320
+ pendingInputRequests.map((request) => /* @__PURE__ */ jsx13(
4321
+ HumanInputCard,
4322
+ {
4323
+ request,
4324
+ onRespond: onInputResponse
4325
+ },
4326
+ request.requestId
4327
+ ))
2462
4328
  ] }) : 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(
4329
+ visibleMessages.map((message, index) => /* @__PURE__ */ jsxs12("div", { className: "agent-rail__turn-block", children: [
4330
+ /* @__PURE__ */ jsx13(
4331
+ MessageBubble,
4332
+ {
4333
+ message,
4334
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4335
+ offer: index === lastAgentIndex ? state.pendingOffer : void 0,
4336
+ onBook
4337
+ }
4338
+ ),
4339
+ index === lastVisitorIndex ? /* @__PURE__ */ jsxs12(Fragment, { children: [
4340
+ showActivity ? /* @__PURE__ */ jsx13(
4341
+ AgentActivityBubble,
4342
+ {
4343
+ brandLabel: resolvedBrandLabel,
4344
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4345
+ failed: state.phase === "error",
4346
+ steps: state.toolSteps
4347
+ }
4348
+ ) : null,
4349
+ visitorToolResults.map((result) => /* @__PURE__ */ jsx13(VisitorToolResultView, { result }, result.id)),
4350
+ pendingInputRequests.map((request) => /* @__PURE__ */ jsx13(
4351
+ HumanInputCard,
4352
+ {
4353
+ request,
4354
+ onRespond: onInputResponse
4355
+ },
4356
+ request.requestId
4357
+ ))
4358
+ ] }) : null
4359
+ ] }, message.id)),
4360
+ streamingMessage ? /* @__PURE__ */ jsx13(
2482
4361
  MessageBubble,
2483
4362
  {
2484
- message: completedAnswer,
4363
+ message: streamingMessage,
2485
4364
  brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
4365
+ offer: state.pendingOffer,
2486
4366
  onBook
2487
4367
  }
2488
4368
  ) : null,
2489
- streamingMessage ? /* @__PURE__ */ jsx6(
2490
- MessageBubble,
4369
+ waitingForBooking ? /* @__PURE__ */ jsx13(
4370
+ BookingCard,
2491
4371
  {
2492
- message: streamingMessage,
2493
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
2494
- offer: state.pendingOffer,
2495
- onBook
4372
+ offer: { type: "booking_offer", eventTypes: [], slots: [] }
2496
4373
  }
2497
4374
  ) : 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 })
4375
+ state.error ? /* @__PURE__ */ jsxs12("section", { className: "agent-rail__error", role: "alert", children: [
4376
+ /* @__PURE__ */ jsxs12("div", { children: [
4377
+ /* @__PURE__ */ jsx13("strong", { children: "Something went wrong" }),
4378
+ /* @__PURE__ */ jsx13("p", { children: state.error })
2502
4379
  ] }),
2503
- onRetry ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
4380
+ onRetry ? /* @__PURE__ */ jsx13("button", { type: "button", onClick: onRetry, children: "Try again" }) : null
2504
4381
  ] }) : null
2505
4382
  ] }) }),
2506
- /* @__PURE__ */ jsxs6("div", { className: "agent-rail__composer-wrap", children: [
2507
- /* @__PURE__ */ jsx6(
4383
+ /* @__PURE__ */ jsxs12("div", { className: "agent-rail__composer-wrap", children: [
4384
+ /* @__PURE__ */ jsx13(
2508
4385
  Composer,
2509
4386
  {
2510
4387
  variant: expanded || mobileFullscreen ? "dock" : "default",
2511
4388
  disabled: isBusy,
4389
+ form: composerForm,
2512
4390
  placeholder: composerPlaceholder,
2513
4391
  onSubmit
2514
4392
  }
2515
4393
  ),
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 })
4394
+ /* @__PURE__ */ jsx13("div", { className: "agent-rail__footer", children: /* @__PURE__ */ jsxs12("p", { children: [
4395
+ /* @__PURE__ */ jsx13("span", { children: "AI can make mistakes. Check important info." }),
4396
+ /* @__PURE__ */ jsx13("span", { children: poweredByLabel })
2519
4397
  ] }) })
2520
4398
  ] })
2521
4399
  ]
@@ -2524,9 +4402,9 @@ function AgentRail({
2524
4402
  }
2525
4403
 
2526
4404
  // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
2527
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
4405
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2528
4406
  function SparklesIcon() {
2529
- return /* @__PURE__ */ jsxs7(
4407
+ return /* @__PURE__ */ jsxs13(
2530
4408
  "svg",
2531
4409
  {
2532
4410
  className: "assist-edge-tab__sparkles",
@@ -2534,21 +4412,21 @@ function SparklesIcon() {
2534
4412
  fill: "none",
2535
4413
  "aria-hidden": "true",
2536
4414
  children: [
2537
- /* @__PURE__ */ jsx7(
4415
+ /* @__PURE__ */ jsx14(
2538
4416
  "path",
2539
4417
  {
2540
4418
  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
4419
  fill: "currentColor"
2542
4420
  }
2543
4421
  ),
2544
- /* @__PURE__ */ jsx7(
4422
+ /* @__PURE__ */ jsx14(
2545
4423
  "path",
2546
4424
  {
2547
4425
  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
4426
  fill: "currentColor"
2549
4427
  }
2550
4428
  ),
2551
- /* @__PURE__ */ jsx7(
4429
+ /* @__PURE__ */ jsx14(
2552
4430
  "path",
2553
4431
  {
2554
4432
  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 +4440,7 @@ function SparklesIcon() {
2562
4440
  function TabMarkIcon({ customIconUrl }) {
2563
4441
  const url = customIconUrl?.trim();
2564
4442
  if (url) {
2565
- return /* @__PURE__ */ jsx7(
4443
+ return /* @__PURE__ */ jsx14(
2566
4444
  "img",
2567
4445
  {
2568
4446
  alt: "",
@@ -2572,10 +4450,10 @@ function TabMarkIcon({ customIconUrl }) {
2572
4450
  }
2573
4451
  );
2574
4452
  }
2575
- return /* @__PURE__ */ jsx7(SparklesIcon, {});
4453
+ return /* @__PURE__ */ jsx14(SparklesIcon, {});
2576
4454
  }
2577
4455
  function ChevronLeftIcon() {
2578
- return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
4456
+ return /* @__PURE__ */ jsx14("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx14(
2579
4457
  "path",
2580
4458
  {
2581
4459
  d: "M10 4L6 8l4 4",
@@ -2587,7 +4465,7 @@ function ChevronLeftIcon() {
2587
4465
  ) });
2588
4466
  }
2589
4467
  function ChevronDownIcon() {
2590
- return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
4468
+ return /* @__PURE__ */ jsx14("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx14(
2591
4469
  "path",
2592
4470
  {
2593
4471
  d: "M4 6l4 4 4-4",
@@ -2599,7 +4477,7 @@ function ChevronDownIcon() {
2599
4477
  ) });
2600
4478
  }
2601
4479
  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)) });
4480
+ return /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ jsx14("i", {}, index)) });
2603
4481
  }
2604
4482
  var VARIANT_COPY = {
2605
4483
  outline: { label: "Ask anything", aria: "Ask anything" },
@@ -2628,6 +4506,7 @@ function AssistEdgeTab({
2628
4506
  const resolvedColorScheme = useAgentColorScheme(colorScheme);
2629
4507
  const copy = VARIANT_COPY[variant];
2630
4508
  const visibleLabel = label?.trim() || copy.label;
4509
+ const alignment = along < 50 ? "start" : along > 50 ? "end" : "center";
2631
4510
  const showLogo = Boolean(logoUrl?.trim()) && !customIconUrl?.trim();
2632
4511
  const resolvedBrandColor = brandColor ?? (resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.brand : void 0);
2633
4512
  const resolvedBorderColor = resolvedColorScheme === "dark" ? defaultDarkAgentRailTheme.border : borderColor;
@@ -2644,11 +4523,11 @@ function AssistEdgeTab({
2644
4523
  ...resolvedTextColor ? { "--as-text": resolvedTextColor } : {},
2645
4524
  colorScheme: resolvedColorScheme
2646
4525
  };
2647
- return /* @__PURE__ */ jsxs7(
4526
+ return /* @__PURE__ */ jsxs13(
2648
4527
  "button",
2649
4528
  {
2650
4529
  type: "button",
2651
- className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side}${mobile ? " assist-edge-tab--mobile" : ""}${visible ? " is-visible" : ""}`,
4530
+ 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
4531
  "data-color-scheme": resolvedColorScheme,
2653
4532
  style,
2654
4533
  "aria-label": `Open ${visibleLabel}`,
@@ -2656,15 +4535,15 @@ function AssistEdgeTab({
2656
4535
  tabIndex: visible ? 0 : -1,
2657
4536
  onClick: onOpen,
2658
4537
  children: [
2659
- mobile ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2660
- /* @__PURE__ */ jsxs7(
4538
+ mobile ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
4539
+ /* @__PURE__ */ jsxs13(
2661
4540
  "span",
2662
4541
  {
2663
4542
  className: "assist-edge-tab__mark assist-edge-tab__mark--mobile",
2664
4543
  "aria-hidden": "true",
2665
4544
  children: [
2666
- /* @__PURE__ */ jsx7(TabMarkIcon, { customIconUrl }),
2667
- showLogo ? /* @__PURE__ */ jsx7(
4545
+ /* @__PURE__ */ jsx14(TabMarkIcon, { customIconUrl }),
4546
+ showLogo ? /* @__PURE__ */ jsx14(
2668
4547
  "img",
2669
4548
  {
2670
4549
  className: "assist-edge-tab__logo",
@@ -2678,11 +4557,11 @@ function AssistEdgeTab({
2678
4557
  ]
2679
4558
  }
2680
4559
  ),
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(
4560
+ /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel })
4561
+ ] }) : variant === "outline" ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
4562
+ /* @__PURE__ */ jsxs13("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4563
+ /* @__PURE__ */ jsx14(TabMarkIcon, { customIconUrl }),
4564
+ showLogo ? /* @__PURE__ */ jsx14(
2686
4565
  "img",
2687
4566
  {
2688
4567
  className: "assist-edge-tab__logo",
@@ -2694,18 +4573,18 @@ function AssistEdgeTab({
2694
4573
  }
2695
4574
  ) : null
2696
4575
  ] }),
2697
- /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2698
- /* @__PURE__ */ jsx7(ChevronDownIcon, {})
4576
+ /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4577
+ /* @__PURE__ */ jsx14(ChevronDownIcon, {})
2699
4578
  ] }) : 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, {})
4579
+ variant === "ask" ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
4580
+ /* @__PURE__ */ jsx14(ChevronLeftIcon, {}),
4581
+ /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4582
+ /* @__PURE__ */ jsx14(DragDots, {})
2704
4583
  ] }) : 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(
4584
+ variant === "fill" ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
4585
+ /* @__PURE__ */ jsxs13("span", { className: "assist-edge-tab__mark", "aria-hidden": "true", children: [
4586
+ /* @__PURE__ */ jsx14(TabMarkIcon, { customIconUrl }),
4587
+ showLogo ? /* @__PURE__ */ jsx14(
2709
4588
  "img",
2710
4589
  {
2711
4590
  className: "assist-edge-tab__logo",
@@ -2717,8 +4596,8 @@ function AssistEdgeTab({
2717
4596
  }
2718
4597
  ) : null
2719
4598
  ] }),
2720
- /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: visibleLabel }),
2721
- /* @__PURE__ */ jsx7(ChevronLeftIcon, {})
4599
+ /* @__PURE__ */ jsx14("span", { className: "assist-edge-tab__label", children: visibleLabel }),
4600
+ /* @__PURE__ */ jsx14(ChevronLeftIcon, {})
2722
4601
  ] }) : null
2723
4602
  ]
2724
4603
  }
@@ -2726,10 +4605,10 @@ function AssistEdgeTab({
2726
4605
  }
2727
4606
 
2728
4607
  // src/react/components/AgentWidget/AgentWidget.tsx
2729
- import { useEffect as useEffect5, useRef as useRef4, useState as useState8 } from "react";
4608
+ import { useEffect as useEffect6, useRef as useRef4, useState as useState9 } from "react";
2730
4609
 
2731
4610
  // src/react/page-shift.ts
2732
- import { useEffect as useEffect3 } from "react";
4611
+ import { useEffect as useEffect4 } from "react";
2733
4612
  var PAGE_SHIFT_CLASS = "webless-agent-page-shift";
2734
4613
  var DEFAULT_RAIL_WIDTH_PX = 450;
2735
4614
  function shouldApplyPageShift(input) {
@@ -2781,7 +4660,7 @@ function clearPageMargin() {
2781
4660
  }
2782
4661
  function usePageShift(input) {
2783
4662
  const { active, railSlotRef } = input;
2784
- useEffect3(() => {
4663
+ useEffect4(() => {
2785
4664
  if (typeof document === "undefined") {
2786
4665
  return;
2787
4666
  }
@@ -2809,12 +4688,12 @@ function usePageShift(input) {
2809
4688
  }
2810
4689
 
2811
4690
  // src/react/hooks/useIsMobile.ts
2812
- import { useEffect as useEffect4, useState as useState7 } from "react";
4691
+ import { useEffect as useEffect5, useState as useState8 } from "react";
2813
4692
  function useIsMobile(breakpoint = 767) {
2814
- const [isMobile, setIsMobile] = useState7(
4693
+ const [isMobile, setIsMobile] = useState8(
2815
4694
  () => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
2816
4695
  );
2817
- useEffect4(() => {
4696
+ useEffect5(() => {
2818
4697
  const media = window.matchMedia(`(max-width: ${breakpoint}px)`);
2819
4698
  const onChange = () => setIsMobile(media.matches);
2820
4699
  onChange();
@@ -2824,23 +4703,8 @@ function useIsMobile(breakpoint = 767) {
2824
4703
  return isMobile;
2825
4704
  }
2826
4705
 
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
4706
  // src/react/components/AgentWidget/AgentWidget.tsx
2843
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
4707
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2844
4708
  function AgentWidget({
2845
4709
  indexId,
2846
4710
  customerId,
@@ -2853,13 +4717,14 @@ function AgentWidget({
2853
4717
  pageShift = true,
2854
4718
  registerPanelController = false,
2855
4719
  colorScheme = "auto",
2856
- branding
4720
+ branding,
4721
+ toolResultRegistry
2857
4722
  }) {
2858
4723
  const isMobile = useIsMobile();
2859
4724
  const placement = normalizeAgentPlacement(placementInput);
2860
4725
  const railSlotRef = useRef4(null);
2861
- const [railCollapsed, setRailCollapsed] = useState8(defaultCollapsed);
2862
- const [railExpanded, setRailExpanded] = useState8(false);
4726
+ const [railCollapsed, setRailCollapsed] = useState9(defaultCollapsed);
4727
+ const [railExpanded, setRailExpanded] = useState9(false);
2863
4728
  const pageShiftActive = shouldApplyPageShift({
2864
4729
  pageShift,
2865
4730
  isMobile,
@@ -2870,14 +4735,15 @@ function AgentWidget({
2870
4735
  active: pageShiftActive,
2871
4736
  railSlotRef
2872
4737
  });
2873
- const { state, reset, retry, submit } = useAgentChat({
4738
+ const { state, reset, retry, respondToInput, respondToToolInput, submit } = useAgentChat({
2874
4739
  customerId,
2875
4740
  getUnpublishedPreviewGrant,
2876
4741
  indexId,
2877
4742
  previewBuildId,
2878
4743
  version,
2879
4744
  runtimeOrigin,
2880
- greeting: branding?.greeting
4745
+ greeting: branding?.greeting,
4746
+ toolResultRegistry
2881
4747
  });
2882
4748
  const agentName = branding?.agentName ?? "";
2883
4749
  const tabLabel = branding?.tabLabel ?? agentName;
@@ -2898,22 +4764,24 @@ function AgentWidget({
2898
4764
  } : {},
2899
4765
  ...branding?.colors?.border ? { border: branding.colors.border } : {}
2900
4766
  };
2901
- useEffect5(() => {
4767
+ useEffect6(() => {
2902
4768
  if (!registerPanelController) return;
2903
4769
  registerAgentPanelController(customerId, {
2904
4770
  open: () => setRailCollapsed(false),
2905
4771
  close: () => {
2906
4772
  setRailCollapsed(true);
2907
4773
  setRailExpanded(false);
2908
- }
4774
+ },
4775
+ reset,
4776
+ submit
2909
4777
  });
2910
4778
  return () => unregisterAgentPanelController(customerId);
2911
- }, [customerId, registerPanelController]);
4779
+ }, [customerId, registerPanelController, reset, submit]);
2912
4780
  async function handleSubmit(message) {
2913
4781
  if (isMobile) setRailCollapsed(false);
2914
4782
  await submit(message);
2915
4783
  }
2916
- useEffect5(() => {
4784
+ useEffect6(() => {
2917
4785
  if (railCollapsed) return;
2918
4786
  const handleKeyDown = (event) => {
2919
4787
  if (event.key === "Tab" && (isMobile || railExpanded)) {
@@ -2947,19 +4815,19 @@ function AgentWidget({
2947
4815
  window.addEventListener("keydown", handleKeyDown);
2948
4816
  return () => window.removeEventListener("keydown", handleKeyDown);
2949
4817
  }, [isMobile, railCollapsed, railExpanded]);
2950
- return /* @__PURE__ */ jsxs8("div", { className: "webless-agent-root", children: [
2951
- /* @__PURE__ */ jsx8(
4818
+ return /* @__PURE__ */ jsxs14("div", { className: "webless-agent-root", children: [
4819
+ /* @__PURE__ */ jsx15(
2952
4820
  "div",
2953
4821
  {
2954
4822
  className: `webless-agent-root__shell${railCollapsed ? " webless-agent-root__shell--collapsed" : ""}${railExpanded ? " webless-agent-root__shell--expanded" : ""}`,
2955
- children: /* @__PURE__ */ jsx8(
4823
+ children: /* @__PURE__ */ jsx15(
2956
4824
  "div",
2957
4825
  {
2958
4826
  ref: railSlotRef,
2959
4827
  className: "webless-agent-root__rail-slot",
2960
4828
  inert: railCollapsed || void 0,
2961
4829
  "aria-hidden": railCollapsed,
2962
- children: /* @__PURE__ */ jsx8(
4830
+ children: /* @__PURE__ */ jsx15(
2963
4831
  AgentRail,
2964
4832
  {
2965
4833
  theme,
@@ -2977,6 +4845,8 @@ function AgentWidget({
2977
4845
  onSubmit: handleSubmit,
2978
4846
  onReset: reset,
2979
4847
  onRetry: () => void retry(),
4848
+ onInputResponse: (response) => void respondToInput(response),
4849
+ onToolInput: (surface, values) => void respondToToolInput(surface, values),
2980
4850
  onFollowUpSelect: (label) => void handleSubmit(label),
2981
4851
  onBook: (input) => void submit(input.displayText, { runtimeText: input.runtimeText })
2982
4852
  }
@@ -2985,7 +4855,7 @@ function AgentWidget({
2985
4855
  )
2986
4856
  }
2987
4857
  ),
2988
- railCollapsed ? /* @__PURE__ */ jsx8(
4858
+ railCollapsed ? /* @__PURE__ */ jsx15(
2989
4859
  AssistEdgeTab,
2990
4860
  {
2991
4861
  variant: placement.variant,
@@ -3013,6 +4883,8 @@ function AgentWidget({
3013
4883
  export {
3014
4884
  DEFAULT_RUNTIME_ORIGIN,
3015
4885
  resolveAgentRuntimeConfig,
4886
+ builtInVisitorToolResultRegistry,
4887
+ presentVisitorToolResult,
3016
4888
  useAgentChat,
3017
4889
  hasVisitorMessages,
3018
4890
  createIdleSuggestions,
@@ -3021,10 +4893,12 @@ export {
3021
4893
  normalizeAgentPlacement,
3022
4894
  openAgentPanel,
3023
4895
  closeAgentPanel,
4896
+ resetAgentPanel,
4897
+ submitAgentPanel,
3024
4898
  defaultAgentRailTheme,
3025
4899
  defaultDarkAgentRailTheme,
3026
4900
  AgentRail,
3027
4901
  AssistEdgeTab,
3028
4902
  AgentWidget
3029
4903
  };
3030
- //# sourceMappingURL=chunk-HXKD46I6.js.map
4904
+ //# sourceMappingURL=chunk-U2TIUZJM.js.map