@webless/agent 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -131,6 +131,329 @@ function usePageShift(input) {
131
131
  // src/react/hooks/useAgentChat.ts
132
132
  var import_react2 = require("react");
133
133
 
134
+ // src/runtime/tool-ui.ts
135
+ var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
136
+ var AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION = "webless.structured-tool-input.v1";
137
+ var AGENT_STRUCTURED_TOOL_INPUT_HEADER = "Webless-Structured-Tool-Input-JSON:";
138
+ function formatAgentStructuredToolInput(surface, values) {
139
+ const payload = {
140
+ schemaVersion: AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION,
141
+ toolSlug: surface.toolSlug,
142
+ ...surface.operationId ? { operationId: surface.operationId } : {},
143
+ values
144
+ };
145
+ return `${AGENT_STRUCTURED_TOOL_INPUT_HEADER} ${JSON.stringify(payload)}`;
146
+ }
147
+ function isRecord(value) {
148
+ return value !== null && typeof value === "object" && !Array.isArray(value);
149
+ }
150
+ function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
151
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
152
+ return true;
153
+ }
154
+ if (typeof value === "number") return Number.isFinite(value);
155
+ if (typeof value !== "object") return false;
156
+ if (seen.has(value)) return false;
157
+ seen.add(value);
158
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
159
+ ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
160
+ );
161
+ seen.delete(value);
162
+ return valid;
163
+ }
164
+ function boundedString(value, max) {
165
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
166
+ return void 0;
167
+ }
168
+ return value.trim();
169
+ }
170
+ function numberValue(value) {
171
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
172
+ }
173
+ function integerValue(value) {
174
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
175
+ }
176
+ function isFieldKind(value) {
177
+ return typeof value === "string" && [
178
+ "text",
179
+ "textarea",
180
+ "email",
181
+ "number",
182
+ "select",
183
+ "multi-select",
184
+ "checkbox",
185
+ "confirmation",
186
+ "radio",
187
+ "date",
188
+ "time",
189
+ "date-time",
190
+ "calendar",
191
+ "range",
192
+ "json"
193
+ ].includes(value);
194
+ }
195
+ function parseField(value) {
196
+ if (!isRecord(value)) return null;
197
+ if (!hasOnlyKeys(value, [
198
+ "description",
199
+ "kind",
200
+ "label",
201
+ "max",
202
+ "maxItems",
203
+ "maxLength",
204
+ "min",
205
+ "minLength",
206
+ "options",
207
+ "path",
208
+ "placeholder",
209
+ "required",
210
+ "step",
211
+ "defaultValue"
212
+ ])) {
213
+ return null;
214
+ }
215
+ if (!isFieldKind(value.kind)) return null;
216
+ const path = boundedString(value.path, 160);
217
+ const label = boundedString(value.label, 160);
218
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
219
+ const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
220
+ const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
221
+ const min = value.min === void 0 ? void 0 : numberValue(value.min);
222
+ const max = value.max === void 0 ? void 0 : numberValue(value.max);
223
+ const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
224
+ const step = value.step === void 0 ? void 0 : numberValue(value.step);
225
+ const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
226
+ const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
227
+ const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
228
+ if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
229
+ return null;
230
+ }
231
+ if (value.description !== void 0 && !description) return null;
232
+ if (value.placeholder !== void 0 && placeholder === void 0) return null;
233
+ if (value.required !== void 0 && required === void 0) return null;
234
+ if (value.min !== void 0 && min === void 0) return null;
235
+ if (value.max !== void 0 && max === void 0) return null;
236
+ if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
237
+ return null;
238
+ if (value.step !== void 0 && step === void 0) return null;
239
+ if (value.minLength !== void 0 && minLength === void 0) return null;
240
+ if (value.maxLength !== void 0 && maxLength === void 0) return null;
241
+ if (value.defaultValue !== void 0 && defaultValue === void 0)
242
+ return null;
243
+ if (value.options !== void 0) {
244
+ if (!Array.isArray(value.options) || value.options.length > 100)
245
+ return null;
246
+ for (const option of value.options) {
247
+ if (!isRecord(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
248
+ return null;
249
+ }
250
+ }
251
+ }
252
+ return {
253
+ kind: value.kind,
254
+ path,
255
+ label,
256
+ ...description ? { description } : {},
257
+ ...placeholder !== void 0 ? { placeholder } : {},
258
+ ...required !== void 0 ? { required } : {},
259
+ ...defaultValue !== void 0 ? { defaultValue } : {},
260
+ ...value.options !== void 0 ? { options: value.options } : {},
261
+ ...min !== void 0 ? { min } : {},
262
+ ...max !== void 0 ? { max } : {},
263
+ ...maxItems !== void 0 ? { maxItems } : {},
264
+ ...step !== void 0 ? { step } : {},
265
+ ...minLength !== void 0 ? { minLength } : {},
266
+ ...maxLength !== void 0 ? { maxLength } : {}
267
+ };
268
+ }
269
+ function parseStep(value) {
270
+ if (!isRecord(value)) return null;
271
+ if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
272
+ return null;
273
+ }
274
+ const id = boundedString(value.id, 80);
275
+ const label = boundedString(value.label, 160);
276
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
277
+ 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(
278
+ (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
279
+ )) {
280
+ return null;
281
+ }
282
+ return {
283
+ id,
284
+ label,
285
+ fieldPaths: value.fieldPaths,
286
+ ...description ? { description } : {}
287
+ };
288
+ }
289
+ function parseAction(value) {
290
+ if (!isRecord(value)) return null;
291
+ if (!hasOnlyKeys(value, ["id", "label"])) return null;
292
+ const label = boundedString(value.label, 80);
293
+ if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
294
+ return null;
295
+ }
296
+ return {
297
+ id: value.id,
298
+ label
299
+ };
300
+ }
301
+ function parseAgentToolUiSurface(value) {
302
+ if (!isRecord(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
303
+ return null;
304
+ if (!hasOnlyKeys(value, [
305
+ "actions",
306
+ "description",
307
+ "fields",
308
+ "id",
309
+ "operationId",
310
+ "requestId",
311
+ "schemaVersion",
312
+ "steps",
313
+ "submitLabel",
314
+ "title",
315
+ "toolSlug",
316
+ "values"
317
+ ])) {
318
+ return null;
319
+ }
320
+ const id = boundedString(value.id, 200);
321
+ const title = boundedString(value.title, 200);
322
+ const toolSlug = boundedString(value.toolSlug, 200);
323
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
324
+ const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
325
+ const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
326
+ const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
327
+ if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
328
+ return null;
329
+ }
330
+ const fields = value.fields.map(parseField);
331
+ if (fields.some((field) => field === null)) return null;
332
+ const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
333
+ if (steps?.some((step) => step === null)) return null;
334
+ const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
335
+ if (actions?.some((action) => action === null)) return null;
336
+ if (value.description !== void 0 && !description) return null;
337
+ if (value.operationId !== void 0 && !operationId) return null;
338
+ if (value.requestId !== void 0 && !requestId) return null;
339
+ if (value.submitLabel !== void 0 && !submitLabel) return null;
340
+ const values = value.values !== void 0 && isRecord(value.values) ? value.values : void 0;
341
+ if (value.values !== void 0) {
342
+ if (!values || !Object.values(values).every((item) => isJsonValue(item)))
343
+ return null;
344
+ }
345
+ return {
346
+ schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
347
+ id,
348
+ title,
349
+ toolSlug,
350
+ fields,
351
+ ...actions ? { actions } : {},
352
+ ...description ? { description } : {},
353
+ ...operationId ? { operationId } : {},
354
+ ...requestId ? { requestId } : {},
355
+ ...submitLabel ? { submitLabel } : {},
356
+ ...steps ? { steps } : {},
357
+ ...values ? { values } : {}
358
+ };
359
+ }
360
+ function hasOnlyKeys(value, allowed) {
361
+ const allowedKeys = new Set(allowed);
362
+ return Object.keys(value).every((key) => allowedKeys.has(key));
363
+ }
364
+
365
+ // src/runtime/tool-result-envelope.ts
366
+ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
367
+ "schemaVersion",
368
+ "output",
369
+ "presentationKinds",
370
+ "ui"
371
+ ]);
372
+ function isRecord2(value) {
373
+ return value !== null && typeof value === "object" && !Array.isArray(value);
374
+ }
375
+ function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
376
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
377
+ return true;
378
+ }
379
+ if (typeof value === "number") return Number.isFinite(value);
380
+ if (typeof value !== "object") return false;
381
+ if (seen.has(value)) return false;
382
+ seen.add(value);
383
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
384
+ ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
385
+ );
386
+ seen.delete(value);
387
+ return valid;
388
+ }
389
+ function decodeEnvelope(value) {
390
+ if (typeof value !== "string") return value;
391
+ try {
392
+ return JSON.parse(value);
393
+ } catch {
394
+ return null;
395
+ }
396
+ }
397
+ function parseAgentToolResultEnvelope(value) {
398
+ const decoded = decodeEnvelope(value);
399
+ if (!isRecord2(decoded)) return null;
400
+ const keys = Object.keys(decoded);
401
+ 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) {
402
+ return null;
403
+ }
404
+ const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
405
+ if (typeof kind !== "string") return [];
406
+ const normalized = kind.trim();
407
+ return normalized && normalized.length <= 128 ? [normalized] : [];
408
+ });
409
+ if (presentationKinds.length !== decoded.presentationKinds.length) {
410
+ return null;
411
+ }
412
+ const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
413
+ if (decoded.ui !== void 0 && !ui) return null;
414
+ return {
415
+ schemaVersion: "webless.tool-result.v1",
416
+ output: decoded.output,
417
+ presentationKinds,
418
+ ...ui ? { ui } : {}
419
+ };
420
+ }
421
+
422
+ // src/runtime/connected-tool-work.ts
423
+ var HUBSPOT_ACTION_LABELS = {
424
+ HUBSPOT_LIST_CONTACTS: "Checking your details",
425
+ HUBSPOT_CREATE_CONTACT: "Saving your details",
426
+ HUBSPOT_UPDATE_CONTACT: "Updating your details",
427
+ HUBSPOT_CREATE_COMPANY: "Saving your company details"
428
+ };
429
+ function connectedToolWork(action) {
430
+ const slug = action.toolName === "COMPOSIO_MULTI_EXECUTE_TOOL" ? action.input.toolSlug : action.toolName;
431
+ if (typeof slug !== "string") return null;
432
+ const detail = HUBSPOT_ACTION_LABELS[slug];
433
+ return detail ? {
434
+ id: action.callId,
435
+ kind: "tool",
436
+ label: "Contact details",
437
+ detail,
438
+ state: "active"
439
+ } : null;
440
+ }
441
+ function toolResultFailed(result) {
442
+ if (result.status !== "completed") return true;
443
+ const output = parseAgentToolResultEnvelope(result.output)?.output ?? result.output;
444
+ if (typeof output !== "object" || output === null || Array.isArray(output))
445
+ return false;
446
+ return "error" in output && Boolean(output.error) || "providerError" in output && Boolean(output.providerError);
447
+ }
448
+ function completeConnectedToolWork(item, result) {
449
+ const failed = toolResultFailed(result);
450
+ return {
451
+ ...item,
452
+ state: failed ? "error" : "completed",
453
+ detail: failed ? "Action could not be confirmed" : "Action completed"
454
+ };
455
+ }
456
+
134
457
  // src/runtime/client.ts
135
458
  var import_client2 = require("eve/client");
136
459
 
@@ -157,11 +480,11 @@ function localBootstrapOrigins(origin) {
157
480
  ...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
158
481
  ];
159
482
  }
160
- function isRecord(value) {
483
+ function isRecord3(value) {
161
484
  return typeof value === "object" && value !== null && !Array.isArray(value);
162
485
  }
163
486
  function parseBootstrapResponse(value, indexId, now) {
164
- 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) {
487
+ 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) {
165
488
  throw new Error("Agent Runtime returned an invalid access response.");
166
489
  }
167
490
  const expiresAt = Date.parse(value.expiresAt);
@@ -181,7 +504,7 @@ async function readBootstrapError(response) {
181
504
  const fallback = `Agent Runtime is unavailable (${response.status}).`;
182
505
  try {
183
506
  const value = await response.json();
184
- return isRecord(value) && typeof value.error === "string" && value.error ? value.error : fallback;
507
+ return isRecord3(value) && typeof value.error === "string" && value.error ? value.error : fallback;
185
508
  } catch {
186
509
  return fallback;
187
510
  }
@@ -393,11 +716,11 @@ function clearPersistedAgentSession(visitorSessionId, options) {
393
716
  var INITIAL_RETRY_DELAY_MS = 100;
394
717
  var MAX_RETRY_DELAY_MS = 2e3;
395
718
  var MAX_CONSECUTIVE_RETRIES = 6;
396
- function isRecord2(value) {
719
+ function isRecord4(value) {
397
720
  return typeof value === "object" && value !== null && !Array.isArray(value);
398
721
  }
399
722
  function parseError(value) {
400
- if (!isRecord2(value)) return void 0;
723
+ if (!isRecord4(value)) return void 0;
401
724
  const { code, message } = value;
402
725
  if (typeof code !== "string" || typeof message !== "string") {
403
726
  return void 0;
@@ -405,29 +728,41 @@ function parseError(value) {
405
728
  return { code, message };
406
729
  }
407
730
  function parseChildStreamEvent(value) {
408
- if (!isRecord2(value) || typeof value.type !== "string") {
731
+ if (!isRecord4(value) || typeof value.type !== "string") {
409
732
  return { type: "other" };
410
733
  }
411
734
  if (value.type === "session.waiting" || value.type === "session.completed" || value.type === "session.failed") {
412
735
  return { type: "session.boundary" };
413
736
  }
414
- if (value.type === "subagent.event" && isRecord2(value.data) && Object.hasOwn(value.data, "event")) {
737
+ if (value.type === "subagent.event" && isRecord4(value.data) && Object.hasOwn(value.data, "event")) {
415
738
  return parseChildStreamEvent(value.data.event);
416
739
  }
417
- if (value.type === "subagent.called" && isRecord2(value.data)) {
740
+ if (value.type === "subagent.called" && isRecord4(value.data)) {
418
741
  const { childStreamPath } = value.data;
419
742
  if (typeof childStreamPath === "string") {
420
743
  return { childStreamPath, type: "subagent.called" };
421
744
  }
422
745
  }
423
- if (value.type !== "action.result" || !isRecord2(value.data)) {
746
+ if (value.type === "actions.requested" && isRecord4(value.data) && Array.isArray(value.data.actions)) {
747
+ const items = value.data.actions.flatMap((action) => {
748
+ if (!isRecord4(action) || action.kind !== "tool-call" || typeof action.callId !== "string" || typeof action.toolName !== "string" || !isRecord4(action.input)) return [];
749
+ const item = connectedToolWork({
750
+ callId: action.callId,
751
+ toolName: action.toolName,
752
+ input: action.input
753
+ });
754
+ return item ? [item] : [];
755
+ });
756
+ return { type: "actions.requested", items };
757
+ }
758
+ if (value.type !== "action.result" || !isRecord4(value.data)) {
424
759
  return { type: "other" };
425
760
  }
426
761
  const { data } = value;
427
762
  if (data.status !== "completed" && data.status !== "failed" && data.status !== "rejected") {
428
763
  return { type: "other" };
429
764
  }
430
- if (!isRecord2(data.result) || data.result.kind !== "tool-result") {
765
+ if (!isRecord4(data.result) || data.result.kind !== "tool-result") {
431
766
  return { type: "other" };
432
767
  }
433
768
  const result = data.result;
@@ -441,7 +776,7 @@ function parseChildStreamEvent(value) {
441
776
  result: {
442
777
  callId: result.callId,
443
778
  toolName: result.toolName,
444
- status: data.status,
779
+ status: result.isError === true ? "failed" : data.status,
445
780
  ...Object.hasOwn(result, "output") ? { output: result.output } : {},
446
781
  ...error ? { error } : {}
447
782
  }
@@ -522,300 +857,81 @@ var SubagentChildStreamCoordinator = class {
522
857
  this.controllers.clear();
523
858
  }
524
859
  beginPath(childStreamPath) {
525
- if (this.tasks.has(childStreamPath)) return;
526
- const controller = new AbortController();
527
- const abort = () => controller.abort();
528
- if (this.parentSignal.aborted) {
529
- controller.abort();
530
- } else {
531
- this.parentSignal.addEventListener("abort", abort, { once: true });
532
- }
533
- this.controllers.set(childStreamPath, controller);
534
- const task = this.consume(childStreamPath, controller.signal).finally(
535
- () => {
536
- this.parentSignal.removeEventListener("abort", abort);
537
- if (this.controllers.get(childStreamPath) === controller) {
538
- this.controllers.delete(childStreamPath);
539
- }
540
- }
541
- );
542
- this.tasks.set(childStreamPath, task);
543
- }
544
- async consume(path, signal) {
545
- let streamIndex = 0;
546
- let consecutiveRetries = 0;
547
- let retryDelayMs = INITIAL_RETRY_DELAY_MS;
548
- while (!signal.aborted && consecutiveRetries < MAX_CONSECUTIVE_RETRIES) {
549
- let receivedEvent = false;
550
- try {
551
- const response = await this.client.fetch(
552
- streamPathAt(path, streamIndex),
553
- {
554
- cache: "no-store",
555
- signal
556
- }
557
- );
558
- if (!response.ok || response.body === null) {
559
- await response.body?.cancel().catch(() => {
560
- });
561
- throw new Error(`Child stream returned ${response.status}.`);
562
- }
563
- for await (const rawEvent of readNdjsonStream(response.body)) {
564
- if (signal.aborted) return;
565
- receivedEvent = true;
566
- streamIndex += 1;
567
- const event = parseChildStreamEvent(rawEvent);
568
- if (event.type === "session.boundary") return;
569
- if (event.type === "subagent.called") {
570
- this.beginPath(event.childStreamPath);
571
- continue;
572
- }
573
- if (event.type !== "action.result") continue;
574
- this.handlers.onToolResult?.(event.result);
575
- if (event.hasOutput) {
576
- this.handlers.onActionResult?.(event.result.output);
577
- }
578
- }
579
- } catch {
580
- if (signal.aborted) return;
581
- }
582
- consecutiveRetries = receivedEvent ? 0 : consecutiveRetries + 1;
583
- retryDelayMs = receivedEvent ? INITIAL_RETRY_DELAY_MS : Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
584
- await abortableDelay(retryDelayMs, signal);
585
- }
586
- }
587
- };
588
-
589
- // src/runtime/tool-ui.ts
590
- var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
591
- var AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION = "webless.structured-tool-input.v1";
592
- var AGENT_STRUCTURED_TOOL_INPUT_HEADER = "Webless-Structured-Tool-Input-JSON:";
593
- function formatAgentStructuredToolInput(surface, values) {
594
- const payload = {
595
- schemaVersion: AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION,
596
- toolSlug: surface.toolSlug,
597
- ...surface.operationId ? { operationId: surface.operationId } : {},
598
- values
599
- };
600
- return `${AGENT_STRUCTURED_TOOL_INPUT_HEADER} ${JSON.stringify(payload)}`;
601
- }
602
- function isRecord3(value) {
603
- return value !== null && typeof value === "object" && !Array.isArray(value);
604
- }
605
- function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
606
- if (value === null || typeof value === "string" || typeof value === "boolean") {
607
- return true;
608
- }
609
- if (typeof value === "number") return Number.isFinite(value);
610
- if (typeof value !== "object") return false;
611
- if (seen.has(value)) return false;
612
- seen.add(value);
613
- const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
614
- ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
615
- );
616
- seen.delete(value);
617
- return valid;
618
- }
619
- function boundedString(value, max) {
620
- if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
621
- return void 0;
622
- }
623
- return value.trim();
624
- }
625
- function numberValue(value) {
626
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
627
- }
628
- function integerValue(value) {
629
- return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
630
- }
631
- function isFieldKind(value) {
632
- return typeof value === "string" && [
633
- "text",
634
- "textarea",
635
- "email",
636
- "number",
637
- "select",
638
- "multi-select",
639
- "checkbox",
640
- "confirmation",
641
- "radio",
642
- "date",
643
- "time",
644
- "date-time",
645
- "calendar",
646
- "range",
647
- "json"
648
- ].includes(value);
649
- }
650
- function parseField(value) {
651
- if (!isRecord3(value)) return null;
652
- if (!hasOnlyKeys(value, [
653
- "description",
654
- "kind",
655
- "label",
656
- "max",
657
- "maxItems",
658
- "maxLength",
659
- "min",
660
- "minLength",
661
- "options",
662
- "path",
663
- "placeholder",
664
- "required",
665
- "step",
666
- "defaultValue"
667
- ])) {
668
- return null;
669
- }
670
- if (!isFieldKind(value.kind)) return null;
671
- const path = boundedString(value.path, 160);
672
- const label = boundedString(value.label, 160);
673
- const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
674
- const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
675
- const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
676
- const min = value.min === void 0 ? void 0 : numberValue(value.min);
677
- const max = value.max === void 0 ? void 0 : numberValue(value.max);
678
- const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
679
- const step = value.step === void 0 ? void 0 : numberValue(value.step);
680
- const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
681
- const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
682
- const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
683
- if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
684
- return null;
685
- }
686
- if (value.description !== void 0 && !description) return null;
687
- if (value.placeholder !== void 0 && placeholder === void 0) return null;
688
- if (value.required !== void 0 && required === void 0) return null;
689
- if (value.min !== void 0 && min === void 0) return null;
690
- if (value.max !== void 0 && max === void 0) return null;
691
- if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
692
- return null;
693
- if (value.step !== void 0 && step === void 0) return null;
694
- if (value.minLength !== void 0 && minLength === void 0) return null;
695
- if (value.maxLength !== void 0 && maxLength === void 0) return null;
696
- if (value.defaultValue !== void 0 && defaultValue === void 0)
697
- return null;
698
- if (value.options !== void 0) {
699
- if (!Array.isArray(value.options) || value.options.length > 100)
700
- return null;
701
- for (const option of value.options) {
702
- if (!isRecord3(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
703
- return null;
704
- }
705
- }
706
- }
707
- return {
708
- kind: value.kind,
709
- path,
710
- label,
711
- ...description ? { description } : {},
712
- ...placeholder !== void 0 ? { placeholder } : {},
713
- ...required !== void 0 ? { required } : {},
714
- ...defaultValue !== void 0 ? { defaultValue } : {},
715
- ...value.options !== void 0 ? { options: value.options } : {},
716
- ...min !== void 0 ? { min } : {},
717
- ...max !== void 0 ? { max } : {},
718
- ...maxItems !== void 0 ? { maxItems } : {},
719
- ...step !== void 0 ? { step } : {},
720
- ...minLength !== void 0 ? { minLength } : {},
721
- ...maxLength !== void 0 ? { maxLength } : {}
722
- };
723
- }
724
- function parseStep(value) {
725
- if (!isRecord3(value)) return null;
726
- if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
727
- return null;
728
- }
729
- const id = boundedString(value.id, 80);
730
- const label = boundedString(value.label, 160);
731
- const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
732
- 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(
733
- (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
734
- )) {
735
- return null;
736
- }
737
- return {
738
- id,
739
- label,
740
- fieldPaths: value.fieldPaths,
741
- ...description ? { description } : {}
742
- };
743
- }
744
- function parseAction(value) {
745
- if (!isRecord3(value)) return null;
746
- if (!hasOnlyKeys(value, ["id", "label"])) return null;
747
- const label = boundedString(value.label, 80);
748
- if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
749
- return null;
750
- }
751
- return {
752
- id: value.id,
753
- label
754
- };
755
- }
756
- function parseAgentToolUiSurface(value) {
757
- if (!isRecord3(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
758
- return null;
759
- if (!hasOnlyKeys(value, [
760
- "actions",
761
- "description",
762
- "fields",
763
- "id",
764
- "operationId",
765
- "requestId",
766
- "schemaVersion",
767
- "steps",
768
- "submitLabel",
769
- "title",
770
- "toolSlug",
771
- "values"
772
- ])) {
773
- return null;
774
- }
775
- const id = boundedString(value.id, 200);
776
- const title = boundedString(value.title, 200);
777
- const toolSlug = boundedString(value.toolSlug, 200);
778
- const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
779
- const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
780
- const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
781
- const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
782
- if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
783
- return null;
860
+ if (this.tasks.has(childStreamPath)) return;
861
+ const controller = new AbortController();
862
+ const abort = () => controller.abort();
863
+ if (this.parentSignal.aborted) {
864
+ controller.abort();
865
+ } else {
866
+ this.parentSignal.addEventListener("abort", abort, { once: true });
867
+ }
868
+ this.controllers.set(childStreamPath, controller);
869
+ const task = this.consume(childStreamPath, controller.signal).finally(
870
+ () => {
871
+ this.parentSignal.removeEventListener("abort", abort);
872
+ if (this.controllers.get(childStreamPath) === controller) {
873
+ this.controllers.delete(childStreamPath);
874
+ }
875
+ }
876
+ );
877
+ this.tasks.set(childStreamPath, task);
784
878
  }
785
- const fields = value.fields.map(parseField);
786
- if (fields.some((field) => field === null)) return null;
787
- const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
788
- if (steps?.some((step) => step === null)) return null;
789
- const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
790
- if (actions?.some((action) => action === null)) return null;
791
- if (value.description !== void 0 && !description) return null;
792
- if (value.operationId !== void 0 && !operationId) return null;
793
- if (value.requestId !== void 0 && !requestId) return null;
794
- if (value.submitLabel !== void 0 && !submitLabel) return null;
795
- const values = value.values !== void 0 && isRecord3(value.values) ? value.values : void 0;
796
- if (value.values !== void 0) {
797
- if (!values || !Object.values(values).every((item) => isJsonValue(item)))
798
- return null;
879
+ async consume(path, signal) {
880
+ const workItems = /* @__PURE__ */ new Map();
881
+ let streamIndex = 0;
882
+ let consecutiveRetries = 0;
883
+ let retryDelayMs = INITIAL_RETRY_DELAY_MS;
884
+ while (!signal.aborted && consecutiveRetries < MAX_CONSECUTIVE_RETRIES) {
885
+ let receivedEvent = false;
886
+ try {
887
+ const response = await this.client.fetch(
888
+ streamPathAt(path, streamIndex),
889
+ {
890
+ cache: "no-store",
891
+ signal
892
+ }
893
+ );
894
+ if (!response.ok || response.body === null) {
895
+ await response.body?.cancel().catch(() => {
896
+ });
897
+ throw new Error(`Child stream returned ${response.status}.`);
898
+ }
899
+ for await (const rawEvent of readNdjsonStream(response.body)) {
900
+ if (signal.aborted) return;
901
+ receivedEvent = true;
902
+ streamIndex += 1;
903
+ const event = parseChildStreamEvent(rawEvent);
904
+ if (event.type === "session.boundary") return;
905
+ if (event.type === "subagent.called") {
906
+ this.beginPath(event.childStreamPath);
907
+ continue;
908
+ }
909
+ if (event.type === "actions.requested") {
910
+ for (const item2 of event.items) {
911
+ workItems.set(item2.id, item2);
912
+ this.handlers.onWork?.(item2);
913
+ }
914
+ continue;
915
+ }
916
+ if (event.type !== "action.result") continue;
917
+ const item = workItems.get(event.result.callId);
918
+ if (item) {
919
+ this.handlers.onWork?.(completeConnectedToolWork(item, event.result));
920
+ }
921
+ this.handlers.onToolResult?.(event.result);
922
+ if (event.hasOutput) {
923
+ this.handlers.onActionResult?.(event.result.output);
924
+ }
925
+ }
926
+ } catch {
927
+ if (signal.aborted) return;
928
+ }
929
+ consecutiveRetries = receivedEvent ? 0 : consecutiveRetries + 1;
930
+ retryDelayMs = receivedEvent ? INITIAL_RETRY_DELAY_MS : Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
931
+ await abortableDelay(retryDelayMs, signal);
932
+ }
799
933
  }
800
- return {
801
- schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
802
- id,
803
- title,
804
- toolSlug,
805
- fields,
806
- ...actions ? { actions } : {},
807
- ...description ? { description } : {},
808
- ...operationId ? { operationId } : {},
809
- ...requestId ? { requestId } : {},
810
- ...submitLabel ? { submitLabel } : {},
811
- ...steps ? { steps } : {},
812
- ...values ? { values } : {}
813
- };
814
- }
815
- function hasOnlyKeys(value, allowed) {
816
- const allowedKeys = new Set(allowed);
817
- return Object.keys(value).every((key) => allowedKeys.has(key));
818
- }
934
+ };
819
935
 
820
936
  // src/runtime/client.ts
821
937
  function isTurnBoundary(event) {
@@ -849,7 +965,7 @@ function emitActionResult(event, handlers) {
849
965
  handlers.onToolResult?.({
850
966
  callId: result.callId,
851
967
  toolName: result.toolName,
852
- status: event.data.status,
968
+ status: result.isError ? "failed" : event.data.status,
853
969
  output: result.output,
854
970
  ...event.data.error ? { error: event.data.error } : {}
855
971
  });
@@ -975,9 +1091,13 @@ function requestedWorkItem(action) {
975
1091
  state: "active"
976
1092
  };
977
1093
  }
978
- return null;
1094
+ return action.kind === "tool-call" ? connectedToolWork(action) : null;
979
1095
  }
980
1096
  function applyWorkEvent(event, handlers, workItems) {
1097
+ if (event.type === "subagent.event") {
1098
+ applyWorkEvent(event.data.event, handlers, workItems);
1099
+ return;
1100
+ }
981
1101
  if (event.type === "step.started" && workItems.size === 0) {
982
1102
  emitWorkItem(
983
1103
  {
@@ -1036,6 +1156,15 @@ function applyWorkEvent(event, handlers, workItems) {
1036
1156
  const { result, status } = event.data;
1037
1157
  const current = workItems.get(result.callId);
1038
1158
  if (!current) return;
1159
+ if (current.kind === "tool" && result.kind === "tool-result") {
1160
+ emitWorkItem(completeConnectedToolWork(current, {
1161
+ callId: result.callId,
1162
+ toolName: result.toolName,
1163
+ status: result.isError ? "failed" : status,
1164
+ output: result.output
1165
+ }), handlers, workItems);
1166
+ return;
1167
+ }
1039
1168
  const failed = status !== "completed" || result.isError === true;
1040
1169
  emitWorkItem(
1041
1170
  {
@@ -2073,10 +2202,15 @@ function parseMessage(value) {
2073
2202
  const result = parseToolResult(value2);
2074
2203
  return result?.kind === "search" ? [result] : [];
2075
2204
  }) : [];
2205
+ const toolResults = Array.isArray(record2.toolResults) ? record2.toolResults.flatMap((value2) => {
2206
+ const result = parseToolResult(value2);
2207
+ return result && result.kind !== "search" && result.kind !== "input" ? [result] : [];
2208
+ }) : [];
2076
2209
  return {
2077
2210
  id: record2.id,
2078
2211
  role: "agent",
2079
2212
  ...searchResults.length ? { searchResults } : {},
2213
+ ...toolResults.length ? { toolResults } : {},
2080
2214
  text: record2.text,
2081
2215
  createdAt: record2.createdAt
2082
2216
  };
@@ -2084,7 +2218,7 @@ function parseMessage(value) {
2084
2218
  function parseToolStep(value) {
2085
2219
  if (typeof value !== "object" || value === null) return null;
2086
2220
  const record2 = value;
2087
- if (typeof record2.id !== "string" || record2.kind !== "planning" && record2.kind !== "search" && record2.kind !== "specialist" || typeof record2.label !== "string" || record2.state !== "completed" && record2.state !== "active" && record2.state !== "pending" && record2.state !== "error") {
2221
+ if (typeof record2.id !== "string" || record2.kind !== "planning" && record2.kind !== "search" && record2.kind !== "specialist" && record2.kind !== "tool" || typeof record2.label !== "string" || record2.state !== "completed" && record2.state !== "active" && record2.state !== "pending" && record2.state !== "error") {
2088
2222
  return null;
2089
2223
  }
2090
2224
  return {
@@ -2145,6 +2279,15 @@ function parseInputRequest(value) {
2145
2279
  ...ui ? { ui } : {}
2146
2280
  };
2147
2281
  }
2282
+ function parseResultDetails(value) {
2283
+ if (!Array.isArray(value)) return [];
2284
+ return value.slice(0, 6).flatMap((item) => {
2285
+ if (typeof item !== "object" || item === null) return [];
2286
+ const detail = item;
2287
+ if (typeof detail.label !== "string" || typeof detail.value !== "string") return [];
2288
+ return [{ label: detail.label.slice(0, 240), value: detail.value.slice(0, 240) }];
2289
+ });
2290
+ }
2148
2291
  function parseToolResult(value) {
2149
2292
  if (typeof value !== "object" || value === null) return null;
2150
2293
  const record2 = value;
@@ -2180,6 +2323,7 @@ function parseToolResult(value) {
2180
2323
  status: record2.status,
2181
2324
  kind: "entity",
2182
2325
  title: record2.title,
2326
+ details: parseResultDetails(record2.details),
2183
2327
  ...typeof record2.description === "string" ? { description: record2.description } : {}
2184
2328
  };
2185
2329
  }
@@ -2223,6 +2367,7 @@ function parseToolResult(value) {
2223
2367
  status: record2.status,
2224
2368
  kind: "summary",
2225
2369
  title: record2.title,
2370
+ details: parseResultDetails(record2.details),
2226
2371
  ...typeof record2.description === "string" ? { description: record2.description } : {}
2227
2372
  };
2228
2373
  }
@@ -2327,63 +2472,6 @@ function clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
2327
2472
  );
2328
2473
  }
2329
2474
 
2330
- // src/runtime/tool-result-envelope.ts
2331
- var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
2332
- "schemaVersion",
2333
- "output",
2334
- "presentationKinds",
2335
- "ui"
2336
- ]);
2337
- function isRecord4(value) {
2338
- return value !== null && typeof value === "object" && !Array.isArray(value);
2339
- }
2340
- function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
2341
- if (value === null || typeof value === "string" || typeof value === "boolean") {
2342
- return true;
2343
- }
2344
- if (typeof value === "number") return Number.isFinite(value);
2345
- if (typeof value !== "object") return false;
2346
- if (seen.has(value)) return false;
2347
- seen.add(value);
2348
- const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
2349
- ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
2350
- );
2351
- seen.delete(value);
2352
- return valid;
2353
- }
2354
- function decodeEnvelope(value) {
2355
- if (typeof value !== "string") return value;
2356
- try {
2357
- return JSON.parse(value);
2358
- } catch {
2359
- return null;
2360
- }
2361
- }
2362
- function parseAgentToolResultEnvelope(value) {
2363
- const decoded = decodeEnvelope(value);
2364
- if (!isRecord4(decoded)) return null;
2365
- const keys = Object.keys(decoded);
2366
- 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) {
2367
- return null;
2368
- }
2369
- const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
2370
- if (typeof kind !== "string") return [];
2371
- const normalized = kind.trim();
2372
- return normalized && normalized.length <= 128 ? [normalized] : [];
2373
- });
2374
- if (presentationKinds.length !== decoded.presentationKinds.length) {
2375
- return null;
2376
- }
2377
- const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
2378
- if (decoded.ui !== void 0 && !ui) return null;
2379
- return {
2380
- schemaVersion: "webless.tool-result.v1",
2381
- output: decoded.output,
2382
- presentationKinds,
2383
- ...ui ? { ui } : {}
2384
- };
2385
- }
2386
-
2387
2475
  // src/react/lib/tool-result.ts
2388
2476
  var MAX_TEXT_LENGTH = 240;
2389
2477
  var MAX_DETAILS = 6;
@@ -2618,6 +2706,9 @@ function finalizeSummaryPresentation(result, proposed) {
2618
2706
  };
2619
2707
  }
2620
2708
  function presentVisitorToolResult(result, registry = []) {
2709
+ if (result.status === "completed" && toolResultFailed(result)) {
2710
+ result = { ...result, status: "failed" };
2711
+ }
2621
2712
  if (result.toolName === "search_discovery" && result.status === "completed") {
2622
2713
  const envelope2 = parseAgentToolResultEnvelope(result.output);
2623
2714
  const search = parseAgentSearchDiscoveryOutput(
@@ -2654,7 +2745,7 @@ function presentVisitorToolResult(result, registry = []) {
2654
2745
  surface: envelope.ui
2655
2746
  };
2656
2747
  }
2657
- if (!proposed) {
2748
+ if (!proposed || result.status !== "completed" && proposed.kind !== "summary") {
2658
2749
  if (result.status === "failed" || result.status === "rejected") {
2659
2750
  return {
2660
2751
  id: result.callId,
@@ -2777,14 +2868,15 @@ function isNearDuplicateAssistantText(left, right) {
2777
2868
  shorter.slice(0, Math.floor(shorter.length * 0.85))
2778
2869
  );
2779
2870
  }
2780
- function appendAgentTurnMessage(messages, displayText, searchResults = []) {
2871
+ function appendAgentTurnMessage(messages, displayText, searchResults = [], toolResults = []) {
2781
2872
  const trimmed = displayText.trim();
2782
- if (!trimmed && searchResults.length === 0) return [...messages];
2873
+ if (!trimmed && searchResults.length === 0 && toolResults.length === 0) return [...messages];
2783
2874
  const agentMessage = {
2784
2875
  id: `agent-${Date.now()}`,
2785
2876
  role: "agent",
2786
2877
  text: trimmed,
2787
2878
  ...searchResults.length ? { searchResults } : {},
2879
+ ...toolResults.length ? { toolResults } : {},
2788
2880
  createdAt: Date.now()
2789
2881
  };
2790
2882
  const last = messages.at(-1);
@@ -3166,10 +3258,11 @@ function useAgentChat({
3166
3258
  messages: appendAgentTurnMessage(
3167
3259
  prev.messages,
3168
3260
  displayText,
3169
- (prev.toolResults ?? []).filter((result) => result.kind === "search")
3261
+ (prev.toolResults ?? []).filter((result) => result.kind === "search"),
3262
+ (prev.toolResults ?? []).filter((result) => result.kind !== "search" && result.kind !== "input")
3170
3263
  ),
3171
3264
  toolResults: (prev.toolResults ?? []).filter(
3172
- (result) => result.kind !== "search"
3265
+ (result) => result.kind === "input"
3173
3266
  ),
3174
3267
  toolSteps: completeActivePlanning(prev.toolSteps),
3175
3268
  streamingText: "",
@@ -3231,16 +3324,18 @@ function useAgentChat({
3231
3324
  const submit = (0, import_react2.useCallback)(
3232
3325
  async (visitorText, options) => {
3233
3326
  const trimmed = visitorText.trim();
3234
- if (!trimmed) return null;
3327
+ const outgoing = options?.runtimeText ?? visitorText;
3328
+ if (!outgoing.trim()) return null;
3235
3329
  const chatResponse = chatInputResponseForText(
3236
3330
  state.pendingInputs ?? [],
3237
- trimmed
3331
+ outgoing.trim()
3238
3332
  );
3239
3333
  if (chatResponse) {
3240
3334
  const visitorMessage2 = {
3241
3335
  id: `visitor-${Date.now()}`,
3242
3336
  role: "visitor",
3243
3337
  text: trimmed,
3338
+ ...outgoing !== trimmed ? { runtimeText: outgoing } : {},
3244
3339
  createdAt: Date.now()
3245
3340
  };
3246
3341
  if (runRef.current) {
@@ -3280,7 +3375,6 @@ function useAgentChat({
3280
3375
  const controller = new AbortController();
3281
3376
  runRef.current = controller;
3282
3377
  const booking = pendingBookingRef.current;
3283
- const outgoing = options?.runtimeText ?? visitorText;
3284
3378
  const runtimeText = booking ? `${visitorBookingPrefix(booking)}
3285
3379
 
3286
3380
  ${outgoing}` : outgoing;
@@ -3659,6 +3753,10 @@ function joinLabels(labels) {
3659
3753
  return `${labels.slice(0, -1).join(", ")} and ${labels.at(-1) ?? ""}`;
3660
3754
  }
3661
3755
  function workSummary(steps, failed, brandLabel) {
3756
+ const activeTool = [...steps].reverse().find(
3757
+ (step) => step.kind === "tool" && step.state === "active"
3758
+ );
3759
+ if (activeTool) return activeTool.detail ?? "Working on your request";
3662
3760
  const activeSpecialists = steps.filter(
3663
3761
  (step) => step.kind === "specialist" && step.state === "active"
3664
3762
  );
@@ -3986,11 +4084,15 @@ function Composer({
3986
4084
  const trimmed = value.trim();
3987
4085
  if (!trimmed || disabled) return;
3988
4086
  const shareDismissal = savedForm && !draft.dismissalSent;
3989
- onSubmit?.(
3990
- shareDismissal ? `I'd like to keep chatting without sharing the requested details for now. Please don't ask for them again unless I choose to proceed with something that needs them.
4087
+ if (shareDismissal) {
4088
+ onSubmit?.(trimmed, {
4089
+ runtimeText: `I'd like to keep chatting without sharing the requested details for now. Please don't ask for them again unless I choose to proceed with something that needs them.
3991
4090
 
3992
- ${trimmed}` : trimmed
3993
- );
4091
+ ${trimmed}`
4092
+ });
4093
+ } else {
4094
+ onSubmit?.(trimmed);
4095
+ }
3994
4096
  if (shareDismissal)
3995
4097
  setDraft((current) => ({ ...current, dismissalSent: true }));
3996
4098
  setValue("");
@@ -4007,7 +4109,9 @@ ${trimmed}` : trimmed
4007
4109
  if (control instanceof HTMLElement) control.focus();
4008
4110
  return;
4009
4111
  }
4010
- onSubmit?.(formatComposerFormMessage(activeForm, draft.values));
4112
+ onSubmit?.("", {
4113
+ runtimeText: formatComposerFormMessage(activeForm, draft.values)
4114
+ });
4011
4115
  setDraft((current) => ({
4012
4116
  ...current,
4013
4117
  values: emptyValues(activeForm),
@@ -4271,25 +4375,11 @@ function SearchReferences({
4271
4375
  });
4272
4376
  if (!sources.length && !actions.length) return null;
4273
4377
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "search-references", children: [
4274
- sources.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { "aria-label": "Sources", children: [
4275
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("h3", { className: "search-references__heading", children: [
4276
- "Sources",
4277
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { title: "Pages used by Search & Discovery", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4278
- "svg",
4279
- {
4280
- viewBox: "0 0 24 24",
4281
- width: "14",
4282
- height: "14",
4283
- fill: "none",
4284
- stroke: "currentColor",
4285
- strokeWidth: "1.75",
4286
- "aria-hidden": "true",
4287
- children: [
4288
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "12", cy: "12", r: "9" }),
4289
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 11v6M12 7v1" })
4290
- ]
4291
- }
4292
- ) })
4378
+ sources.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "search-references__disclosure", "aria-label": "Sources", children: [
4379
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("summary", { className: "search-references__heading", children: [
4380
+ "Sources (",
4381
+ sources.length,
4382
+ ")"
4293
4383
  ] }),
4294
4384
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "search-references__list", children: sources.map((source) => {
4295
4385
  const content = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
@@ -4367,7 +4457,23 @@ function calendarCells(year, month) {
4367
4457
  function BookingCardLoader() {
4368
4458
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("section", { className: "booking-card", "aria-label": "Book a meeting", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "booking-card__loading", role: "status", children: "Finding available times\u2026" }) });
4369
4459
  }
4370
- function BookingCard({
4460
+ function BookingCard(props) {
4461
+ if (!props.readOnly) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(InteractiveBookingCard, { ...props });
4462
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "booking-card", "aria-label": "Recorded available times", children: [
4463
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "booking-card__title", children: "Available times offered" }),
4464
+ props.offer.slots.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { children: props.offer.slots.map((slot, index) => {
4465
+ const eventType = props.offer.eventTypes.find(
4466
+ (item) => item.uri === slot.eventTypeUri
4467
+ );
4468
+ const label = Number.isFinite(Date.parse(slot.startTime)) ? formatSlotLabel(slot.startTime) : "Time unavailable";
4469
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { children: [
4470
+ eventType ? `${eventType.name} \xB7 ` : "",
4471
+ label
4472
+ ] }, `${slot.eventTypeUri ?? ""}:${slot.startTime}:${index}`);
4473
+ }) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "No available times were recorded." })
4474
+ ] });
4475
+ }
4476
+ function InteractiveBookingCard({
4371
4477
  disabled = false,
4372
4478
  offer,
4373
4479
  onBook
@@ -4733,6 +4839,7 @@ function MessageBubble({
4733
4839
  message,
4734
4840
  brandLogoUrl,
4735
4841
  bookingDisabled = false,
4842
+ bookingReadOnly = false,
4736
4843
  offer,
4737
4844
  onBook
4738
4845
  }) {
@@ -4750,6 +4857,7 @@ function MessageBubble({
4750
4857
  offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
4751
4858
  );
4752
4859
  if (message.role === "visitor") {
4860
+ if (!message.text.trim()) return null;
4753
4861
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "message-bubble__text", children: message.text }) });
4754
4862
  }
4755
4863
  const citations = message.citations ?? [];
@@ -4768,8 +4876,8 @@ function MessageBubble({
4768
4876
  children: displayText
4769
4877
  }
4770
4878
  ),
4771
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchReferences, { results: message.searchResults ?? [] }),
4772
- citations.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: "message-bubble__sources", "aria-label": "Sources", children: citations.map((citation) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("a", { href: citation.url, target: "_blank", rel: "noreferrer", children: [
4879
+ !isStreaming ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchReferences, { results: message.searchResults ?? [] }) : null,
4880
+ !isStreaming && citations.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: "message-bubble__sources", "aria-label": "Sources", children: citations.map((citation) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("a", { href: citation.url, target: "_blank", rel: "noreferrer", children: [
4773
4881
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4774
4882
  "span",
4775
4883
  {
@@ -4781,6 +4889,7 @@ function MessageBubble({
4781
4889
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: citation.label })
4782
4890
  ] }) }, citation.id)) }) : null
4783
4891
  ] });
4892
+ if (!displayText && !message.searchResults?.length && offers.length === 0) return null;
4784
4893
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
4785
4894
  displayText || message.searchResults?.length ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__agent-row", children: [
4786
4895
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -4799,6 +4908,7 @@ function MessageBubble({
4799
4908
  BookingCard,
4800
4909
  {
4801
4910
  disabled: bookingDisabled,
4911
+ readOnly: bookingReadOnly,
4802
4912
  offer: nextOffer,
4803
4913
  onBook
4804
4914
  },
@@ -5957,6 +6067,7 @@ function CollectionResultCard({
5957
6067
  {
5958
6068
  className: `collection-result-card tool-result-card tool-result-card--${result.status}`,
5959
6069
  "aria-label": result.title,
6070
+ role: result.status === "completed" ? "status" : "alert",
5960
6071
  children: [
5961
6072
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "tool-result-card__heading", children: [
5962
6073
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
@@ -5981,31 +6092,44 @@ var import_jsx_runtime13 = require("react/jsx-runtime");
5981
6092
  function EntityResultCard({
5982
6093
  result
5983
6094
  }) {
6095
+ const compact = !result.description && !result.details?.length && !result.links?.length;
6096
+ const collapsible = result.status === "completed" && Boolean(result.details?.length) && !result.links?.length;
6097
+ const heading = /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "tool-result-card__heading", children: [
6098
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
6099
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: result.title })
6100
+ ] });
6101
+ const content = /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
6102
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: result.description }) : null,
6103
+ result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
6104
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: detail.label }),
6105
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: detail.value })
6106
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
6107
+ result.links?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6108
+ "a",
6109
+ {
6110
+ href: link.href,
6111
+ target: "_blank",
6112
+ rel: "noreferrer",
6113
+ children: link.label
6114
+ },
6115
+ link.href
6116
+ )) }) : null
6117
+ ] });
6118
+ if (collapsible) {
6119
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("details", { className: "entity-result-card tool-result-card entity-result-card--disclosure", children: [
6120
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("summary", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { role: "status", children: heading }) }),
6121
+ content
6122
+ ] });
6123
+ }
5984
6124
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
5985
6125
  "section",
5986
6126
  {
5987
- className: `entity-result-card tool-result-card tool-result-card--${result.status}`,
6127
+ className: `entity-result-card tool-result-card tool-result-card--${result.status}${compact ? " entity-result-card--compact" : ""}`,
5988
6128
  "aria-label": result.title,
6129
+ role: result.status === "completed" ? "status" : "alert",
5989
6130
  children: [
5990
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "tool-result-card__heading", children: [
5991
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
5992
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: result.title })
5993
- ] }),
5994
- result.description ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: result.description }) : null,
5995
- result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
5996
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: detail.label }),
5997
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: detail.value })
5998
- ] }, `${detail.label}:${detail.value}`)) }) : null,
5999
- result.links?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "tool-result-card__links", children: result.links.map((link) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6000
- "a",
6001
- {
6002
- href: link.href,
6003
- target: "_blank",
6004
- rel: "noreferrer",
6005
- children: link.label
6006
- },
6007
- link.href
6008
- )) }) : null
6131
+ heading,
6132
+ content
6009
6133
  ]
6010
6134
  }
6011
6135
  );
@@ -6054,6 +6178,7 @@ function ToolResultCard({
6054
6178
  {
6055
6179
  className: `tool-result-card tool-result-card--${result.status}`,
6056
6180
  "aria-label": result.title,
6181
+ role: result.status === "completed" ? "status" : "alert",
6057
6182
  children: [
6058
6183
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "tool-result-card__heading", children: [
6059
6184
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
@@ -6217,6 +6342,9 @@ function AgentRail({
6217
6342
  const railRef = (0, import_react14.useRef)(null);
6218
6343
  const overlayRef = (0, import_react14.useRef)(null);
6219
6344
  const transcriptRef = (0, import_react14.useRef)(null);
6345
+ const responseRef = (0, import_react14.useRef)(null);
6346
+ const threadRef = (0, import_react14.useRef)(null);
6347
+ const lastScrolledVisitorIdRef = (0, import_react14.useRef)(void 0);
6220
6348
  const pinnedToBottomRef = (0, import_react14.useRef)(true);
6221
6349
  const smoothScrollToLatestRef = (0, import_react14.useRef)(false);
6222
6350
  const lockedTranscriptScrollTopRef = (0, import_react14.useRef)(null);
@@ -6240,7 +6368,7 @@ function AgentRail({
6240
6368
  const activeVisitorToolInput = [...visitorToolResults].reverse().find((result) => result.kind === "input");
6241
6369
  const visibleVisitorToolResults = activeVisitorToolInput ? [activeVisitorToolInput] : visitorToolResults;
6242
6370
  const activityActive = state.toolSteps.some((step) => step.state === "active");
6243
- const showActivity = state.toolSteps.length > 0 && visibleVisitorToolResults.length === 0 && pendingInputRequests.length === 0 && !state.pendingOffer && activityActive;
6371
+ const showActivity = state.toolSteps.length > 0 && pendingInputRequests.length === 0 && !state.pendingOffer && activityActive;
6244
6372
  const hasVisitorMessages2 = state.messages.some(
6245
6373
  (message) => message.role === "visitor"
6246
6374
  );
@@ -6269,7 +6397,7 @@ function AgentRail({
6269
6397
  }
6270
6398
  }
6271
6399
  const lastIsAgent = lastMessage?.role === "agent";
6272
- const showMessageActions = state.phase === "complete" && lastIsAgent && hasVisitorMessages2;
6400
+ const showMessageActions = state.phase === "complete" && lastIsAgent && hasVisitorMessages2 && Boolean(hideToolCardFences(lastMessage.text).trim());
6273
6401
  const streamingMessage = state.phase === "streaming" && state.streamingText && !lastIsAgent ? {
6274
6402
  createdAt: 0,
6275
6403
  id: "streaming-response",
@@ -6299,15 +6427,21 @@ function AgentRail({
6299
6427
  hasPendingConfirmation,
6300
6428
  enabled: lastIsAgent && !isBusy
6301
6429
  });
6430
+ const latestResultId = [
6431
+ ...visibleMessages.flatMap(
6432
+ (message) => message.role === "agent" ? message.toolResults ?? [] : []
6433
+ ),
6434
+ ...visibleVisitorToolResults
6435
+ ].reverse().find((result) => result.kind !== "input")?.id;
6302
6436
  const receiptSteps = answerReceipt && state.toolSteps.length > 0 ? state.toolSteps : void 0;
6303
6437
  (0, import_react14.useEffect)(() => {
6304
6438
  if (state.phase !== "complete") {
6305
6439
  setReceiptOpen(false);
6306
6440
  }
6307
6441
  }, [state.phase]);
6308
- function handleSubmit(message) {
6442
+ function handleSubmit(message, options) {
6309
6443
  setReceiptOpen(false);
6310
- onSubmit?.(message);
6444
+ onSubmit?.(message, options);
6311
6445
  }
6312
6446
  function handleRegenerate() {
6313
6447
  setReceiptOpen(false);
@@ -6321,15 +6455,31 @@ function AgentRail({
6321
6455
  setReceiptOpen(false);
6322
6456
  onFollowUpSelect?.(label);
6323
6457
  }
6458
+ const latestVisitorId = visibleMessages[lastVisitorIndex]?.id;
6324
6459
  (0, import_react14.useEffect)(() => {
6325
6460
  const node = transcriptRef.current;
6326
6461
  if (!node) return;
6327
- const lastMessage2 = state.messages.at(-1);
6328
- const visitorJustSent = lastMessage2?.role === "visitor";
6329
- if (pinnedToBottomRef.current || visitorJustSent) {
6330
- node.scrollTop = node.scrollHeight;
6462
+ if (latestVisitorId !== lastScrolledVisitorIdRef.current) {
6463
+ lastScrolledVisitorIdRef.current = latestVisitorId;
6464
+ pinnedToBottomRef.current = true;
6331
6465
  }
6466
+ const followResponse = () => {
6467
+ if (node.clientHeight === 0 || !pinnedToBottomRef.current) return;
6468
+ const bottom = Math.max(0, node.scrollHeight - node.clientHeight);
6469
+ const response = responseRef.current;
6470
+ const responseTop = response ? node.scrollTop + response.getBoundingClientRect().top - node.getBoundingClientRect().top : bottom;
6471
+ node.scrollTop = Math.max(node.scrollTop, Math.min(bottom, responseTop));
6472
+ const pinned = bottom - node.scrollTop < 48;
6473
+ pinnedToBottomRef.current = pinned;
6474
+ setShowJumpToLatest(!pinned);
6475
+ };
6476
+ followResponse();
6477
+ const observer = new ResizeObserver(followResponse);
6478
+ observer.observe(node);
6479
+ if (threadRef.current) observer.observe(threadRef.current);
6480
+ return () => observer.disconnect();
6332
6481
  }, [
6482
+ latestVisitorId,
6333
6483
  state.messages,
6334
6484
  state.toolSteps,
6335
6485
  state.streamingText,
@@ -6350,18 +6500,6 @@ function AgentRail({
6350
6500
  handleScroll();
6351
6501
  return () => node.removeEventListener("scroll", handleScroll);
6352
6502
  }, []);
6353
- (0, import_react14.useEffect)(() => {
6354
- const node = transcriptRef.current;
6355
- if (!node) return;
6356
- const observer = new ResizeObserver(() => {
6357
- if (node.clientHeight === 0) return;
6358
- if (pinnedToBottomRef.current) {
6359
- node.scrollTo({ top: node.scrollHeight, behavior: "instant" });
6360
- }
6361
- });
6362
- observer.observe(node);
6363
- return () => observer.disconnect();
6364
- }, []);
6365
6503
  (0, import_react14.useEffect)(() => {
6366
6504
  if (!receiptOpen) {
6367
6505
  lockedTranscriptScrollTopRef.current = null;
@@ -6478,7 +6616,7 @@ function AgentRail({
6478
6616
  ) : null
6479
6617
  ] })
6480
6618
  ] }) }),
6481
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "agent-rail__thread", children: [
6619
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref: transcriptRef, className: "agent-rail__transcript", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { ref: threadRef, className: "agent-rail__thread", children: [
6482
6620
  !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
6483
6621
  greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6484
6622
  MessageBubble,
@@ -6525,59 +6663,68 @@ function AgentRail({
6525
6663
  request.requestId
6526
6664
  ))
6527
6665
  ] }) : null,
6528
- visibleMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "agent-rail__turn-block", children: [
6529
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6530
- MessageBubble,
6531
- {
6532
- message,
6533
- bookingDisabled: isBusy,
6534
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6535
- offer: index === lastAgentIndex ? state.pendingOffer : void 0,
6536
- onBook
6537
- }
6538
- ),
6539
- index === lastAgentIndex && showMessageActions && message.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6540
- MessageActions,
6541
- {
6542
- answeredAt: message.createdAt,
6543
- copyText: hideToolCardFences(message.text).trim() || message.text,
6544
- readAloud,
6545
- receiptSteps,
6546
- onOpenReceipt: receiptSteps ? openReceipt : void 0,
6547
- onRegenerate: onRegenerate ? handleRegenerate : void 0,
6548
- onFeedback: onFeedback ? (rating) => onFeedback(rating, message) : void 0
6549
- }
6550
- ) : null,
6551
- index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
6552
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6553
- AgentActivityBubble,
6554
- {
6555
- brandLabel: resolvedBrandLabel,
6556
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6557
- failed: state.phase === "error",
6558
- steps: state.toolSteps
6559
- }
6560
- ) : null,
6561
- visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6562
- VisitorToolResultView,
6563
- {
6564
- result,
6565
- disabled: semanticSurfaceDisabled,
6566
- onToolInput
6567
- },
6568
- result.id
6569
- )),
6570
- pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6571
- HumanInputCard,
6572
- {
6573
- request,
6574
- onRespond: onInputResponse
6575
- },
6576
- request.requestId
6577
- ))
6578
- ] }) : null
6579
- ] }, message.id)),
6580
- streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6666
+ visibleMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
6667
+ "div",
6668
+ {
6669
+ ref: index === lastVisitorIndex + 1 ? responseRef : void 0,
6670
+ className: "agent-rail__turn-block",
6671
+ children: [
6672
+ message.role === "agent" ? message.toolResults?.map((result) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VisitorToolResultView, { result }, result.id)) : null,
6673
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6674
+ MessageBubble,
6675
+ {
6676
+ message,
6677
+ bookingDisabled: isBusy,
6678
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6679
+ offer: index === lastAgentIndex ? state.pendingOffer : void 0,
6680
+ onBook
6681
+ }
6682
+ ),
6683
+ index === lastAgentIndex && showMessageActions && message.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6684
+ MessageActions,
6685
+ {
6686
+ answeredAt: message.createdAt,
6687
+ copyText: hideToolCardFences(message.text).trim() || message.text,
6688
+ readAloud,
6689
+ receiptSteps,
6690
+ onOpenReceipt: receiptSteps ? openReceipt : void 0,
6691
+ onRegenerate: onRegenerate ? handleRegenerate : void 0,
6692
+ onFeedback: onFeedback ? (rating) => onFeedback(rating, message) : void 0
6693
+ }
6694
+ ) : null,
6695
+ index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
6696
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6697
+ AgentActivityBubble,
6698
+ {
6699
+ brandLabel: resolvedBrandLabel,
6700
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6701
+ failed: state.phase === "error",
6702
+ steps: state.toolSteps
6703
+ }
6704
+ ) : null,
6705
+ visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6706
+ VisitorToolResultView,
6707
+ {
6708
+ result,
6709
+ disabled: semanticSurfaceDisabled,
6710
+ onToolInput
6711
+ },
6712
+ result.id
6713
+ )),
6714
+ pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6715
+ HumanInputCard,
6716
+ {
6717
+ request,
6718
+ onRespond: onInputResponse
6719
+ },
6720
+ request.requestId
6721
+ ))
6722
+ ] }) : null
6723
+ ]
6724
+ },
6725
+ message.id
6726
+ )),
6727
+ streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref: responseRef, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6581
6728
  MessageBubble,
6582
6729
  {
6583
6730
  message: streamingMessage,
@@ -6586,7 +6733,7 @@ function AgentRail({
6586
6733
  offer: state.pendingOffer,
6587
6734
  onBook
6588
6735
  }
6589
- ) : null,
6736
+ ) }) : null,
6590
6737
  waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(BookingCardLoader, {}) : null,
6591
6738
  state.error ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
6592
6739
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
@@ -6627,7 +6774,7 @@ function AgentRail({
6627
6774
  placeholder: composerPlaceholder,
6628
6775
  onSubmit: handleSubmit
6629
6776
  },
6630
- state.messages.find((message) => message.role === "visitor")?.id ?? "empty"
6777
+ `${state.messages.find((message) => message.role === "visitor")?.id ?? "empty"}:${latestResultId ?? ""}`
6631
6778
  ),
6632
6779
  poweredByLabel ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "agent-rail__footer", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: poweredByLabel }) }) }) : null
6633
6780
  ] })
@@ -6995,9 +7142,9 @@ function AgentWidget({
6995
7142
  });
6996
7143
  return () => unregisterAgentPanelController(customerId);
6997
7144
  }, [customerId, registerPanelController, reset, submit]);
6998
- async function handleSubmit(message) {
7145
+ async function handleSubmit(message, options) {
6999
7146
  if (isMobile) setRailCollapsed(false);
7000
- await submit(message);
7147
+ await submit(message, options);
7001
7148
  }
7002
7149
  function handleFeedback(rating, message) {
7003
7150
  if (!analytics) return;
@@ -7125,8 +7272,86 @@ function AgentWidget({
7125
7272
  ] });
7126
7273
  }
7127
7274
 
7128
- // src/react/components/AgentTranscript/AgentTranscript.tsx
7275
+ // src/react/components/AgentTranscript/TranscriptActivity.tsx
7129
7276
  var import_jsx_runtime20 = require("react/jsx-runtime");
7277
+ var statusLabels = {
7278
+ started: "No result recorded",
7279
+ completed: "",
7280
+ failed: "Failed",
7281
+ rejected: "Rejected"
7282
+ };
7283
+ function RecordedResult({
7284
+ result
7285
+ }) {
7286
+ if (result.kind === "search") return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SearchReferences, { results: [result] });
7287
+ if (result.kind === "booking") {
7288
+ const card = result.card;
7289
+ if (card.type === "booking_offer")
7290
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(BookingCard, { readOnly: true, offer: card });
7291
+ if (card.type === "booking_confirmed")
7292
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { children: [
7293
+ "Booking confirmed",
7294
+ card.startTime ? ` \xB7 ${card.startTime}` : ""
7295
+ ] });
7296
+ if (card.type === "booking_canceled") return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { children: "Booking canceled" });
7297
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { children: "Information requested" });
7298
+ }
7299
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(VisitorToolResultView, { disabled: true, result });
7300
+ }
7301
+ function TranscriptActivity({
7302
+ activity,
7303
+ formatTimestamp
7304
+ }) {
7305
+ const date = new Date(activity.createdAt);
7306
+ const validTimestamp = Number.isFinite(date.getTime());
7307
+ const result = activity.result?.kind === "hidden" ? void 0 : activity.result;
7308
+ const heading = /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
7309
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: activity.category === "specialist" ? `Specialist \xB7 ${activity.label}` : activity.label }),
7310
+ activity.status !== "completed" ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "agent-transcript__activity-status", children: statusLabels[activity.status] }) : null,
7311
+ validTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("time", { dateTime: date.toISOString(), children: formatTimestamp(activity.createdAt) }) : null
7312
+ ] });
7313
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("li", { className: "agent-transcript__activity", "data-status": activity.status, children: result ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("details", { children: [
7314
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("summary", { className: "agent-transcript__activity-heading", children: heading }),
7315
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "agent-transcript__activity-result", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(RecordedResult, { result }) })
7316
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "agent-transcript__activity-heading", children: heading }) });
7317
+ }
7318
+
7319
+ // src/react/components/AgentTranscript/group-activities.ts
7320
+ function groupTranscriptActivities(activities) {
7321
+ const calls = /* @__PURE__ */ new Map();
7322
+ for (const activity of activities) {
7323
+ const key = `${activity.category}:${activity.callId}`;
7324
+ const previous = calls.get(key);
7325
+ if (!previous) {
7326
+ calls.set(key, {
7327
+ latest: activity,
7328
+ id: activity.id,
7329
+ startedAt: activity.createdAt,
7330
+ label: activity.label
7331
+ });
7332
+ continue;
7333
+ }
7334
+ const previousHasResult = previous.latest.status !== "started";
7335
+ const nextHasResult = activity.status !== "started";
7336
+ const useNext = nextHasResult && !previousHasResult || nextHasResult === previousHasResult && activity.createdAt >= previous.latest.createdAt;
7337
+ const latest = useNext ? activity : previous.latest;
7338
+ calls.set(key, {
7339
+ latest,
7340
+ id: previous.id,
7341
+ startedAt: Math.min(previous.startedAt, activity.createdAt),
7342
+ label: activity.createdAt < previous.startedAt ? activity.label : previous.label
7343
+ });
7344
+ }
7345
+ return [...calls.values()].map(({ latest, id, startedAt, label }) => ({
7346
+ ...latest,
7347
+ id,
7348
+ label,
7349
+ createdAt: startedAt
7350
+ }));
7351
+ }
7352
+
7353
+ // src/react/components/AgentTranscript/AgentTranscript.tsx
7354
+ var import_jsx_runtime21 = require("react/jsx-runtime");
7130
7355
  var timestampFormat = new Intl.DateTimeFormat("en-US", {
7131
7356
  month: "short",
7132
7357
  day: "numeric",
@@ -7139,6 +7364,7 @@ function defaultTimestamp(createdAt) {
7139
7364
  }
7140
7365
  function AgentTranscript({
7141
7366
  messages,
7367
+ activities = [],
7142
7368
  theme,
7143
7369
  colorScheme,
7144
7370
  agentLabel = "Agent",
@@ -7147,7 +7373,14 @@ function AgentTranscript({
7147
7373
  formatTimestamp = defaultTimestamp
7148
7374
  }) {
7149
7375
  const scheme = useAgentColorScheme(colorScheme);
7150
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7376
+ const entries = [
7377
+ ...messages.map((message) => ({ kind: "message", ...message })),
7378
+ ...groupTranscriptActivities(activities).map((activity) => ({
7379
+ kind: "activity",
7380
+ ...activity
7381
+ }))
7382
+ ].sort((left, right) => left.createdAt - right.createdAt);
7383
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7151
7384
  "ol",
7152
7385
  {
7153
7386
  className: "webless-agent-root agent-transcript not-typeset",
@@ -7155,22 +7388,34 @@ function AgentTranscript({
7155
7388
  "data-color-scheme": scheme,
7156
7389
  "aria-label": label,
7157
7390
  style: agentThemeStyle(theme, scheme),
7158
- children: messages.map((message) => {
7391
+ children: entries.map((message) => {
7392
+ if (message.kind === "activity")
7393
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7394
+ TranscriptActivity,
7395
+ {
7396
+ activity: message,
7397
+ formatTimestamp
7398
+ },
7399
+ message.id
7400
+ );
7401
+ if (message.role === "visitor" && !message.text.trim()) return null;
7159
7402
  const date = new Date(message.createdAt);
7160
7403
  const validTimestamp = Number.isFinite(date.getTime());
7161
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
7404
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
7162
7405
  "li",
7163
7406
  {
7164
7407
  className: `agent-transcript__message agent-transcript__message--${message.role}`,
7165
7408
  children: [
7166
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "agent-transcript__meta", children: [
7167
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: message.role === "visitor" ? visitorLabel : agentLabel }),
7168
- validTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("time", { dateTime: date.toISOString(), children: formatTimestamp(message.createdAt) }) : null
7409
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "agent-transcript__meta", children: [
7410
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: message.role === "visitor" ? visitorLabel : agentLabel }),
7411
+ validTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("time", { dateTime: date.toISOString(), children: formatTimestamp(message.createdAt) }) : null
7169
7412
  ] }),
7170
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
7413
+ message.role === "agent" ? message.toolResults?.map((result) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(VisitorToolResultView, { result }, result.id)) : null,
7414
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7171
7415
  MessageBubble,
7172
7416
  {
7173
7417
  message: message.role === "agent" ? { ...message, streaming: false } : message,
7418
+ bookingReadOnly: true,
7174
7419
  bookingDisabled: true
7175
7420
  }
7176
7421
  )