@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/embed.cjs CHANGED
@@ -149,6 +149,329 @@ function usePageShift(input) {
149
149
  // src/react/hooks/useAgentChat.ts
150
150
  var import_react2 = require("react");
151
151
 
152
+ // src/runtime/tool-ui.ts
153
+ var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
154
+ var AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION = "webless.structured-tool-input.v1";
155
+ var AGENT_STRUCTURED_TOOL_INPUT_HEADER = "Webless-Structured-Tool-Input-JSON:";
156
+ function formatAgentStructuredToolInput(surface, values) {
157
+ const payload = {
158
+ schemaVersion: AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION,
159
+ toolSlug: surface.toolSlug,
160
+ ...surface.operationId ? { operationId: surface.operationId } : {},
161
+ values
162
+ };
163
+ return `${AGENT_STRUCTURED_TOOL_INPUT_HEADER} ${JSON.stringify(payload)}`;
164
+ }
165
+ function isRecord(value) {
166
+ return value !== null && typeof value === "object" && !Array.isArray(value);
167
+ }
168
+ function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
169
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
170
+ return true;
171
+ }
172
+ if (typeof value === "number") return Number.isFinite(value);
173
+ if (typeof value !== "object") return false;
174
+ if (seen.has(value)) return false;
175
+ seen.add(value);
176
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
177
+ ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
178
+ );
179
+ seen.delete(value);
180
+ return valid;
181
+ }
182
+ function boundedString(value, max) {
183
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
184
+ return void 0;
185
+ }
186
+ return value.trim();
187
+ }
188
+ function numberValue(value) {
189
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
190
+ }
191
+ function integerValue(value) {
192
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
193
+ }
194
+ function isFieldKind(value) {
195
+ return typeof value === "string" && [
196
+ "text",
197
+ "textarea",
198
+ "email",
199
+ "number",
200
+ "select",
201
+ "multi-select",
202
+ "checkbox",
203
+ "confirmation",
204
+ "radio",
205
+ "date",
206
+ "time",
207
+ "date-time",
208
+ "calendar",
209
+ "range",
210
+ "json"
211
+ ].includes(value);
212
+ }
213
+ function parseField(value) {
214
+ if (!isRecord(value)) return null;
215
+ if (!hasOnlyKeys(value, [
216
+ "description",
217
+ "kind",
218
+ "label",
219
+ "max",
220
+ "maxItems",
221
+ "maxLength",
222
+ "min",
223
+ "minLength",
224
+ "options",
225
+ "path",
226
+ "placeholder",
227
+ "required",
228
+ "step",
229
+ "defaultValue"
230
+ ])) {
231
+ return null;
232
+ }
233
+ if (!isFieldKind(value.kind)) return null;
234
+ const path = boundedString(value.path, 160);
235
+ const label = boundedString(value.label, 160);
236
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
237
+ const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
238
+ const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
239
+ const min = value.min === void 0 ? void 0 : numberValue(value.min);
240
+ const max = value.max === void 0 ? void 0 : numberValue(value.max);
241
+ const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
242
+ const step = value.step === void 0 ? void 0 : numberValue(value.step);
243
+ const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
244
+ const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
245
+ const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
246
+ if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
247
+ return null;
248
+ }
249
+ if (value.description !== void 0 && !description) return null;
250
+ if (value.placeholder !== void 0 && placeholder === void 0) return null;
251
+ if (value.required !== void 0 && required === void 0) return null;
252
+ if (value.min !== void 0 && min === void 0) return null;
253
+ if (value.max !== void 0 && max === void 0) return null;
254
+ if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
255
+ return null;
256
+ if (value.step !== void 0 && step === void 0) return null;
257
+ if (value.minLength !== void 0 && minLength === void 0) return null;
258
+ if (value.maxLength !== void 0 && maxLength === void 0) return null;
259
+ if (value.defaultValue !== void 0 && defaultValue === void 0)
260
+ return null;
261
+ if (value.options !== void 0) {
262
+ if (!Array.isArray(value.options) || value.options.length > 100)
263
+ return null;
264
+ for (const option of value.options) {
265
+ if (!isRecord(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
266
+ return null;
267
+ }
268
+ }
269
+ }
270
+ return {
271
+ kind: value.kind,
272
+ path,
273
+ label,
274
+ ...description ? { description } : {},
275
+ ...placeholder !== void 0 ? { placeholder } : {},
276
+ ...required !== void 0 ? { required } : {},
277
+ ...defaultValue !== void 0 ? { defaultValue } : {},
278
+ ...value.options !== void 0 ? { options: value.options } : {},
279
+ ...min !== void 0 ? { min } : {},
280
+ ...max !== void 0 ? { max } : {},
281
+ ...maxItems !== void 0 ? { maxItems } : {},
282
+ ...step !== void 0 ? { step } : {},
283
+ ...minLength !== void 0 ? { minLength } : {},
284
+ ...maxLength !== void 0 ? { maxLength } : {}
285
+ };
286
+ }
287
+ function parseStep(value) {
288
+ if (!isRecord(value)) return null;
289
+ if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
290
+ return null;
291
+ }
292
+ const id = boundedString(value.id, 80);
293
+ const label = boundedString(value.label, 160);
294
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
295
+ 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(
296
+ (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
297
+ )) {
298
+ return null;
299
+ }
300
+ return {
301
+ id,
302
+ label,
303
+ fieldPaths: value.fieldPaths,
304
+ ...description ? { description } : {}
305
+ };
306
+ }
307
+ function parseAction(value) {
308
+ if (!isRecord(value)) return null;
309
+ if (!hasOnlyKeys(value, ["id", "label"])) return null;
310
+ const label = boundedString(value.label, 80);
311
+ if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
312
+ return null;
313
+ }
314
+ return {
315
+ id: value.id,
316
+ label
317
+ };
318
+ }
319
+ function parseAgentToolUiSurface(value) {
320
+ if (!isRecord(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
321
+ return null;
322
+ if (!hasOnlyKeys(value, [
323
+ "actions",
324
+ "description",
325
+ "fields",
326
+ "id",
327
+ "operationId",
328
+ "requestId",
329
+ "schemaVersion",
330
+ "steps",
331
+ "submitLabel",
332
+ "title",
333
+ "toolSlug",
334
+ "values"
335
+ ])) {
336
+ return null;
337
+ }
338
+ const id = boundedString(value.id, 200);
339
+ const title = boundedString(value.title, 200);
340
+ const toolSlug = boundedString(value.toolSlug, 200);
341
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
342
+ const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
343
+ const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
344
+ const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
345
+ if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
346
+ return null;
347
+ }
348
+ const fields = value.fields.map(parseField);
349
+ if (fields.some((field) => field === null)) return null;
350
+ const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
351
+ if (steps?.some((step) => step === null)) return null;
352
+ const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
353
+ if (actions?.some((action) => action === null)) return null;
354
+ if (value.description !== void 0 && !description) return null;
355
+ if (value.operationId !== void 0 && !operationId) return null;
356
+ if (value.requestId !== void 0 && !requestId) return null;
357
+ if (value.submitLabel !== void 0 && !submitLabel) return null;
358
+ const values = value.values !== void 0 && isRecord(value.values) ? value.values : void 0;
359
+ if (value.values !== void 0) {
360
+ if (!values || !Object.values(values).every((item) => isJsonValue(item)))
361
+ return null;
362
+ }
363
+ return {
364
+ schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
365
+ id,
366
+ title,
367
+ toolSlug,
368
+ fields,
369
+ ...actions ? { actions } : {},
370
+ ...description ? { description } : {},
371
+ ...operationId ? { operationId } : {},
372
+ ...requestId ? { requestId } : {},
373
+ ...submitLabel ? { submitLabel } : {},
374
+ ...steps ? { steps } : {},
375
+ ...values ? { values } : {}
376
+ };
377
+ }
378
+ function hasOnlyKeys(value, allowed) {
379
+ const allowedKeys = new Set(allowed);
380
+ return Object.keys(value).every((key) => allowedKeys.has(key));
381
+ }
382
+
383
+ // src/runtime/tool-result-envelope.ts
384
+ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
385
+ "schemaVersion",
386
+ "output",
387
+ "presentationKinds",
388
+ "ui"
389
+ ]);
390
+ function isRecord2(value) {
391
+ return value !== null && typeof value === "object" && !Array.isArray(value);
392
+ }
393
+ function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
394
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
395
+ return true;
396
+ }
397
+ if (typeof value === "number") return Number.isFinite(value);
398
+ if (typeof value !== "object") return false;
399
+ if (seen.has(value)) return false;
400
+ seen.add(value);
401
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
402
+ ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
403
+ );
404
+ seen.delete(value);
405
+ return valid;
406
+ }
407
+ function decodeEnvelope(value) {
408
+ if (typeof value !== "string") return value;
409
+ try {
410
+ return JSON.parse(value);
411
+ } catch {
412
+ return null;
413
+ }
414
+ }
415
+ function parseAgentToolResultEnvelope(value) {
416
+ const decoded = decodeEnvelope(value);
417
+ if (!isRecord2(decoded)) return null;
418
+ const keys = Object.keys(decoded);
419
+ 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) {
420
+ return null;
421
+ }
422
+ const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
423
+ if (typeof kind !== "string") return [];
424
+ const normalized = kind.trim();
425
+ return normalized && normalized.length <= 128 ? [normalized] : [];
426
+ });
427
+ if (presentationKinds.length !== decoded.presentationKinds.length) {
428
+ return null;
429
+ }
430
+ const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
431
+ if (decoded.ui !== void 0 && !ui) return null;
432
+ return {
433
+ schemaVersion: "webless.tool-result.v1",
434
+ output: decoded.output,
435
+ presentationKinds,
436
+ ...ui ? { ui } : {}
437
+ };
438
+ }
439
+
440
+ // src/runtime/connected-tool-work.ts
441
+ var HUBSPOT_ACTION_LABELS = {
442
+ HUBSPOT_LIST_CONTACTS: "Checking your details",
443
+ HUBSPOT_CREATE_CONTACT: "Saving your details",
444
+ HUBSPOT_UPDATE_CONTACT: "Updating your details",
445
+ HUBSPOT_CREATE_COMPANY: "Saving your company details"
446
+ };
447
+ function connectedToolWork(action) {
448
+ const slug = action.toolName === "COMPOSIO_MULTI_EXECUTE_TOOL" ? action.input.toolSlug : action.toolName;
449
+ if (typeof slug !== "string") return null;
450
+ const detail = HUBSPOT_ACTION_LABELS[slug];
451
+ return detail ? {
452
+ id: action.callId,
453
+ kind: "tool",
454
+ label: "Contact details",
455
+ detail,
456
+ state: "active"
457
+ } : null;
458
+ }
459
+ function toolResultFailed(result) {
460
+ if (result.status !== "completed") return true;
461
+ const output = parseAgentToolResultEnvelope(result.output)?.output ?? result.output;
462
+ if (typeof output !== "object" || output === null || Array.isArray(output))
463
+ return false;
464
+ return "error" in output && Boolean(output.error) || "providerError" in output && Boolean(output.providerError);
465
+ }
466
+ function completeConnectedToolWork(item, result) {
467
+ const failed = toolResultFailed(result);
468
+ return {
469
+ ...item,
470
+ state: failed ? "error" : "completed",
471
+ detail: failed ? "Action could not be confirmed" : "Action completed"
472
+ };
473
+ }
474
+
152
475
  // src/runtime/client.ts
153
476
  var import_client2 = require("eve/client");
154
477
 
@@ -175,11 +498,11 @@ function localBootstrapOrigins(origin) {
175
498
  ...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
176
499
  ];
177
500
  }
178
- function isRecord(value) {
501
+ function isRecord3(value) {
179
502
  return typeof value === "object" && value !== null && !Array.isArray(value);
180
503
  }
181
504
  function parseBootstrapResponse(value, indexId, now) {
182
- 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) {
505
+ 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) {
183
506
  throw new Error("Agent Runtime returned an invalid access response.");
184
507
  }
185
508
  const expiresAt = Date.parse(value.expiresAt);
@@ -199,7 +522,7 @@ async function readBootstrapError(response) {
199
522
  const fallback = `Agent Runtime is unavailable (${response.status}).`;
200
523
  try {
201
524
  const value = await response.json();
202
- return isRecord(value) && typeof value.error === "string" && value.error ? value.error : fallback;
525
+ return isRecord3(value) && typeof value.error === "string" && value.error ? value.error : fallback;
203
526
  } catch {
204
527
  return fallback;
205
528
  }
@@ -386,454 +709,247 @@ function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, opt
386
709
  return;
387
710
  }
388
711
  const prefix = resolvePrefix(options);
389
- sessionStorage.setItem(runtimeSessionIdKey(visitorSessionId, prefix), sessionId);
390
- sessionStorage.setItem(
391
- runtimeStreamIndexKey(visitorSessionId, prefix),
392
- String(Math.max(0, streamIndex))
393
- );
394
- }
395
- function savePersistedAgentTurnMessage(visitorSessionId, message, options) {
396
- if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !message) {
397
- return;
398
- }
399
- const prefix = resolvePrefix(options);
400
- sessionStorage.setItem(runtimeLastMessageKey(visitorSessionId, prefix), message);
401
- }
402
- function clearPersistedAgentSession(visitorSessionId, options) {
403
- if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return;
404
- const prefix = resolvePrefix(options);
405
- sessionStorage.removeItem(runtimeSessionIdKey(visitorSessionId, prefix));
406
- sessionStorage.removeItem(runtimeStreamIndexKey(visitorSessionId, prefix));
407
- sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
408
- }
409
-
410
- // src/runtime/subagent-child-stream.ts
411
- var INITIAL_RETRY_DELAY_MS = 100;
412
- var MAX_RETRY_DELAY_MS = 2e3;
413
- var MAX_CONSECUTIVE_RETRIES = 6;
414
- function isRecord2(value) {
415
- return typeof value === "object" && value !== null && !Array.isArray(value);
416
- }
417
- function parseError(value) {
418
- if (!isRecord2(value)) return void 0;
419
- const { code, message } = value;
420
- if (typeof code !== "string" || typeof message !== "string") {
421
- return void 0;
422
- }
423
- return { code, message };
424
- }
425
- function parseChildStreamEvent(value) {
426
- if (!isRecord2(value) || typeof value.type !== "string") {
427
- return { type: "other" };
428
- }
429
- if (value.type === "session.waiting" || value.type === "session.completed" || value.type === "session.failed") {
430
- return { type: "session.boundary" };
431
- }
432
- if (value.type === "subagent.event" && isRecord2(value.data) && Object.hasOwn(value.data, "event")) {
433
- return parseChildStreamEvent(value.data.event);
434
- }
435
- if (value.type === "subagent.called" && isRecord2(value.data)) {
436
- const { childStreamPath } = value.data;
437
- if (typeof childStreamPath === "string") {
438
- return { childStreamPath, type: "subagent.called" };
439
- }
440
- }
441
- if (value.type !== "action.result" || !isRecord2(value.data)) {
442
- return { type: "other" };
443
- }
444
- const { data } = value;
445
- if (data.status !== "completed" && data.status !== "failed" && data.status !== "rejected") {
446
- return { type: "other" };
447
- }
448
- if (!isRecord2(data.result) || data.result.kind !== "tool-result") {
449
- return { type: "other" };
450
- }
451
- const result = data.result;
452
- if (typeof result.callId !== "string" || typeof result.toolName !== "string") {
453
- return { type: "other" };
454
- }
455
- const error = parseError(data.error);
456
- return {
457
- type: "action.result",
458
- hasOutput: Object.hasOwn(result, "output"),
459
- result: {
460
- callId: result.callId,
461
- toolName: result.toolName,
462
- status: data.status,
463
- ...Object.hasOwn(result, "output") ? { output: result.output } : {},
464
- ...error ? { error } : {}
465
- }
466
- };
467
- }
468
- async function* readNdjsonStream(body) {
469
- const reader = body.getReader();
470
- const decoder = new TextDecoder();
471
- let buffer = "";
472
- try {
473
- while (true) {
474
- const { done, value } = await reader.read();
475
- buffer += decoder.decode(value, { stream: !done });
476
- const lines = buffer.split("\n");
477
- buffer = lines.pop() ?? "";
478
- for (const line of lines) {
479
- const trimmed2 = line.trim();
480
- if (!trimmed2) continue;
481
- try {
482
- const parsed = JSON.parse(trimmed2);
483
- yield parsed;
484
- } catch {
485
- }
486
- }
487
- if (done) break;
488
- }
489
- const trimmed = buffer.trim();
490
- if (trimmed) {
491
- try {
492
- const parsed = JSON.parse(trimmed);
493
- yield parsed;
494
- } catch {
495
- }
496
- }
497
- } finally {
498
- reader.releaseLock();
499
- }
500
- }
501
- function streamPathAt(path, streamIndex) {
502
- if (streamIndex === 0) return path;
503
- return `${path}${path.includes("?") ? "&" : "?"}startIndex=${streamIndex}`;
504
- }
505
- function abortableDelay(delayMs, signal) {
506
- if (signal.aborted) return Promise.resolve();
507
- return new Promise((resolve) => {
508
- const finish = () => {
509
- clearTimeout(timeout);
510
- signal.removeEventListener("abort", finish);
511
- resolve();
512
- };
513
- const timeout = setTimeout(finish, delayMs);
514
- signal.addEventListener("abort", finish, { once: true });
515
- });
516
- }
517
- var SubagentChildStreamCoordinator = class {
518
- constructor(client, handlers, parentSignal) {
519
- this.client = client;
520
- this.handlers = handlers;
521
- this.parentSignal = parentSignal;
522
- }
523
- client;
524
- handlers;
525
- parentSignal;
526
- controllers = /* @__PURE__ */ new Map();
527
- tasks = /* @__PURE__ */ new Map();
528
- begin(event) {
529
- this.beginPath(event.data.childStreamPath);
530
- }
531
- async waitForAll() {
532
- let observedTaskCount = -1;
533
- while (observedTaskCount !== this.tasks.size) {
534
- observedTaskCount = this.tasks.size;
535
- await Promise.all(this.tasks.values());
536
- }
537
- }
538
- abortAll() {
539
- for (const controller of this.controllers.values()) controller.abort();
540
- this.controllers.clear();
541
- }
542
- beginPath(childStreamPath) {
543
- if (this.tasks.has(childStreamPath)) return;
544
- const controller = new AbortController();
545
- const abort = () => controller.abort();
546
- if (this.parentSignal.aborted) {
547
- controller.abort();
548
- } else {
549
- this.parentSignal.addEventListener("abort", abort, { once: true });
550
- }
551
- this.controllers.set(childStreamPath, controller);
552
- const task = this.consume(childStreamPath, controller.signal).finally(
553
- () => {
554
- this.parentSignal.removeEventListener("abort", abort);
555
- if (this.controllers.get(childStreamPath) === controller) {
556
- this.controllers.delete(childStreamPath);
557
- }
558
- }
559
- );
560
- this.tasks.set(childStreamPath, task);
561
- }
562
- async consume(path, signal) {
563
- let streamIndex = 0;
564
- let consecutiveRetries = 0;
565
- let retryDelayMs = INITIAL_RETRY_DELAY_MS;
566
- while (!signal.aborted && consecutiveRetries < MAX_CONSECUTIVE_RETRIES) {
567
- let receivedEvent = false;
568
- try {
569
- const response = await this.client.fetch(
570
- streamPathAt(path, streamIndex),
571
- {
572
- cache: "no-store",
573
- signal
574
- }
575
- );
576
- if (!response.ok || response.body === null) {
577
- await response.body?.cancel().catch(() => {
578
- });
579
- throw new Error(`Child stream returned ${response.status}.`);
580
- }
581
- for await (const rawEvent of readNdjsonStream(response.body)) {
582
- if (signal.aborted) return;
583
- receivedEvent = true;
584
- streamIndex += 1;
585
- const event = parseChildStreamEvent(rawEvent);
586
- if (event.type === "session.boundary") return;
587
- if (event.type === "subagent.called") {
588
- this.beginPath(event.childStreamPath);
589
- continue;
590
- }
591
- if (event.type !== "action.result") continue;
592
- this.handlers.onToolResult?.(event.result);
593
- if (event.hasOutput) {
594
- this.handlers.onActionResult?.(event.result.output);
595
- }
596
- }
597
- } catch {
598
- if (signal.aborted) return;
599
- }
600
- consecutiveRetries = receivedEvent ? 0 : consecutiveRetries + 1;
601
- retryDelayMs = receivedEvent ? INITIAL_RETRY_DELAY_MS : Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
602
- await abortableDelay(retryDelayMs, signal);
603
- }
604
- }
605
- };
606
-
607
- // src/runtime/tool-ui.ts
608
- var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
609
- var AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION = "webless.structured-tool-input.v1";
610
- var AGENT_STRUCTURED_TOOL_INPUT_HEADER = "Webless-Structured-Tool-Input-JSON:";
611
- function formatAgentStructuredToolInput(surface, values) {
612
- const payload = {
613
- schemaVersion: AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION,
614
- toolSlug: surface.toolSlug,
615
- ...surface.operationId ? { operationId: surface.operationId } : {},
616
- values
617
- };
618
- return `${AGENT_STRUCTURED_TOOL_INPUT_HEADER} ${JSON.stringify(payload)}`;
619
- }
620
- function isRecord3(value) {
621
- return value !== null && typeof value === "object" && !Array.isArray(value);
622
- }
623
- function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
624
- if (value === null || typeof value === "string" || typeof value === "boolean") {
625
- return true;
626
- }
627
- if (typeof value === "number") return Number.isFinite(value);
628
- if (typeof value !== "object") return false;
629
- if (seen.has(value)) return false;
630
- seen.add(value);
631
- const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
632
- ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
712
+ sessionStorage.setItem(runtimeSessionIdKey(visitorSessionId, prefix), sessionId);
713
+ sessionStorage.setItem(
714
+ runtimeStreamIndexKey(visitorSessionId, prefix),
715
+ String(Math.max(0, streamIndex))
633
716
  );
634
- seen.delete(value);
635
- return valid;
636
717
  }
637
- function boundedString(value, max) {
638
- if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
639
- return void 0;
718
+ function savePersistedAgentTurnMessage(visitorSessionId, message, options) {
719
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !message) {
720
+ return;
640
721
  }
641
- return value.trim();
722
+ const prefix = resolvePrefix(options);
723
+ sessionStorage.setItem(runtimeLastMessageKey(visitorSessionId, prefix), message);
642
724
  }
643
- function numberValue(value) {
644
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
725
+ function clearPersistedAgentSession(visitorSessionId, options) {
726
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return;
727
+ const prefix = resolvePrefix(options);
728
+ sessionStorage.removeItem(runtimeSessionIdKey(visitorSessionId, prefix));
729
+ sessionStorage.removeItem(runtimeStreamIndexKey(visitorSessionId, prefix));
730
+ sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
645
731
  }
646
- function integerValue(value) {
647
- return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
732
+
733
+ // src/runtime/subagent-child-stream.ts
734
+ var INITIAL_RETRY_DELAY_MS = 100;
735
+ var MAX_RETRY_DELAY_MS = 2e3;
736
+ var MAX_CONSECUTIVE_RETRIES = 6;
737
+ function isRecord4(value) {
738
+ return typeof value === "object" && value !== null && !Array.isArray(value);
648
739
  }
649
- function isFieldKind(value) {
650
- return typeof value === "string" && [
651
- "text",
652
- "textarea",
653
- "email",
654
- "number",
655
- "select",
656
- "multi-select",
657
- "checkbox",
658
- "confirmation",
659
- "radio",
660
- "date",
661
- "time",
662
- "date-time",
663
- "calendar",
664
- "range",
665
- "json"
666
- ].includes(value);
740
+ function parseError(value) {
741
+ if (!isRecord4(value)) return void 0;
742
+ const { code, message } = value;
743
+ if (typeof code !== "string" || typeof message !== "string") {
744
+ return void 0;
745
+ }
746
+ return { code, message };
667
747
  }
668
- function parseField(value) {
669
- if (!isRecord3(value)) return null;
670
- if (!hasOnlyKeys(value, [
671
- "description",
672
- "kind",
673
- "label",
674
- "max",
675
- "maxItems",
676
- "maxLength",
677
- "min",
678
- "minLength",
679
- "options",
680
- "path",
681
- "placeholder",
682
- "required",
683
- "step",
684
- "defaultValue"
685
- ])) {
686
- return null;
748
+ function parseChildStreamEvent(value) {
749
+ if (!isRecord4(value) || typeof value.type !== "string") {
750
+ return { type: "other" };
687
751
  }
688
- if (!isFieldKind(value.kind)) return null;
689
- const path = boundedString(value.path, 160);
690
- const label = boundedString(value.label, 160);
691
- const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
692
- const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
693
- const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
694
- const min = value.min === void 0 ? void 0 : numberValue(value.min);
695
- const max = value.max === void 0 ? void 0 : numberValue(value.max);
696
- const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
697
- const step = value.step === void 0 ? void 0 : numberValue(value.step);
698
- const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
699
- const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
700
- const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
701
- if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
702
- return null;
752
+ if (value.type === "session.waiting" || value.type === "session.completed" || value.type === "session.failed") {
753
+ return { type: "session.boundary" };
703
754
  }
704
- if (value.description !== void 0 && !description) return null;
705
- if (value.placeholder !== void 0 && placeholder === void 0) return null;
706
- if (value.required !== void 0 && required === void 0) return null;
707
- if (value.min !== void 0 && min === void 0) return null;
708
- if (value.max !== void 0 && max === void 0) return null;
709
- if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
710
- return null;
711
- if (value.step !== void 0 && step === void 0) return null;
712
- if (value.minLength !== void 0 && minLength === void 0) return null;
713
- if (value.maxLength !== void 0 && maxLength === void 0) return null;
714
- if (value.defaultValue !== void 0 && defaultValue === void 0)
715
- return null;
716
- if (value.options !== void 0) {
717
- if (!Array.isArray(value.options) || value.options.length > 100)
718
- return null;
719
- for (const option of value.options) {
720
- if (!isRecord3(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
721
- return null;
722
- }
755
+ if (value.type === "subagent.event" && isRecord4(value.data) && Object.hasOwn(value.data, "event")) {
756
+ return parseChildStreamEvent(value.data.event);
757
+ }
758
+ if (value.type === "subagent.called" && isRecord4(value.data)) {
759
+ const { childStreamPath } = value.data;
760
+ if (typeof childStreamPath === "string") {
761
+ return { childStreamPath, type: "subagent.called" };
723
762
  }
724
763
  }
725
- return {
726
- kind: value.kind,
727
- path,
728
- label,
729
- ...description ? { description } : {},
730
- ...placeholder !== void 0 ? { placeholder } : {},
731
- ...required !== void 0 ? { required } : {},
732
- ...defaultValue !== void 0 ? { defaultValue } : {},
733
- ...value.options !== void 0 ? { options: value.options } : {},
734
- ...min !== void 0 ? { min } : {},
735
- ...max !== void 0 ? { max } : {},
736
- ...maxItems !== void 0 ? { maxItems } : {},
737
- ...step !== void 0 ? { step } : {},
738
- ...minLength !== void 0 ? { minLength } : {},
739
- ...maxLength !== void 0 ? { maxLength } : {}
740
- };
741
- }
742
- function parseStep(value) {
743
- if (!isRecord3(value)) return null;
744
- if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
745
- return null;
764
+ if (value.type === "actions.requested" && isRecord4(value.data) && Array.isArray(value.data.actions)) {
765
+ const items = value.data.actions.flatMap((action) => {
766
+ if (!isRecord4(action) || action.kind !== "tool-call" || typeof action.callId !== "string" || typeof action.toolName !== "string" || !isRecord4(action.input)) return [];
767
+ const item = connectedToolWork({
768
+ callId: action.callId,
769
+ toolName: action.toolName,
770
+ input: action.input
771
+ });
772
+ return item ? [item] : [];
773
+ });
774
+ return { type: "actions.requested", items };
746
775
  }
747
- const id = boundedString(value.id, 80);
748
- const label = boundedString(value.label, 160);
749
- const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
750
- 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(
751
- (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
752
- )) {
753
- return null;
776
+ if (value.type !== "action.result" || !isRecord4(value.data)) {
777
+ return { type: "other" };
778
+ }
779
+ const { data } = value;
780
+ if (data.status !== "completed" && data.status !== "failed" && data.status !== "rejected") {
781
+ return { type: "other" };
782
+ }
783
+ if (!isRecord4(data.result) || data.result.kind !== "tool-result") {
784
+ return { type: "other" };
785
+ }
786
+ const result = data.result;
787
+ if (typeof result.callId !== "string" || typeof result.toolName !== "string") {
788
+ return { type: "other" };
754
789
  }
790
+ const error = parseError(data.error);
755
791
  return {
756
- id,
757
- label,
758
- fieldPaths: value.fieldPaths,
759
- ...description ? { description } : {}
792
+ type: "action.result",
793
+ hasOutput: Object.hasOwn(result, "output"),
794
+ result: {
795
+ callId: result.callId,
796
+ toolName: result.toolName,
797
+ status: result.isError === true ? "failed" : data.status,
798
+ ...Object.hasOwn(result, "output") ? { output: result.output } : {},
799
+ ...error ? { error } : {}
800
+ }
760
801
  };
761
802
  }
762
- function parseAction(value) {
763
- if (!isRecord3(value)) return null;
764
- if (!hasOnlyKeys(value, ["id", "label"])) return null;
765
- const label = boundedString(value.label, 80);
766
- if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
767
- return null;
803
+ async function* readNdjsonStream(body) {
804
+ const reader = body.getReader();
805
+ const decoder = new TextDecoder();
806
+ let buffer = "";
807
+ try {
808
+ while (true) {
809
+ const { done, value } = await reader.read();
810
+ buffer += decoder.decode(value, { stream: !done });
811
+ const lines = buffer.split("\n");
812
+ buffer = lines.pop() ?? "";
813
+ for (const line of lines) {
814
+ const trimmed2 = line.trim();
815
+ if (!trimmed2) continue;
816
+ try {
817
+ const parsed = JSON.parse(trimmed2);
818
+ yield parsed;
819
+ } catch {
820
+ }
821
+ }
822
+ if (done) break;
823
+ }
824
+ const trimmed = buffer.trim();
825
+ if (trimmed) {
826
+ try {
827
+ const parsed = JSON.parse(trimmed);
828
+ yield parsed;
829
+ } catch {
830
+ }
831
+ }
832
+ } finally {
833
+ reader.releaseLock();
768
834
  }
769
- return {
770
- id: value.id,
771
- label
772
- };
773
835
  }
774
- function parseAgentToolUiSurface(value) {
775
- if (!isRecord3(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
776
- return null;
777
- if (!hasOnlyKeys(value, [
778
- "actions",
779
- "description",
780
- "fields",
781
- "id",
782
- "operationId",
783
- "requestId",
784
- "schemaVersion",
785
- "steps",
786
- "submitLabel",
787
- "title",
788
- "toolSlug",
789
- "values"
790
- ])) {
791
- return null;
836
+ function streamPathAt(path, streamIndex) {
837
+ if (streamIndex === 0) return path;
838
+ return `${path}${path.includes("?") ? "&" : "?"}startIndex=${streamIndex}`;
839
+ }
840
+ function abortableDelay(delayMs, signal) {
841
+ if (signal.aborted) return Promise.resolve();
842
+ return new Promise((resolve) => {
843
+ const finish = () => {
844
+ clearTimeout(timeout);
845
+ signal.removeEventListener("abort", finish);
846
+ resolve();
847
+ };
848
+ const timeout = setTimeout(finish, delayMs);
849
+ signal.addEventListener("abort", finish, { once: true });
850
+ });
851
+ }
852
+ var SubagentChildStreamCoordinator = class {
853
+ constructor(client, handlers, parentSignal) {
854
+ this.client = client;
855
+ this.handlers = handlers;
856
+ this.parentSignal = parentSignal;
857
+ }
858
+ client;
859
+ handlers;
860
+ parentSignal;
861
+ controllers = /* @__PURE__ */ new Map();
862
+ tasks = /* @__PURE__ */ new Map();
863
+ begin(event) {
864
+ this.beginPath(event.data.childStreamPath);
792
865
  }
793
- const id = boundedString(value.id, 200);
794
- const title = boundedString(value.title, 200);
795
- const toolSlug = boundedString(value.toolSlug, 200);
796
- const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
797
- const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
798
- const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
799
- const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
800
- if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
801
- return null;
866
+ async waitForAll() {
867
+ let observedTaskCount = -1;
868
+ while (observedTaskCount !== this.tasks.size) {
869
+ observedTaskCount = this.tasks.size;
870
+ await Promise.all(this.tasks.values());
871
+ }
802
872
  }
803
- const fields = value.fields.map(parseField);
804
- if (fields.some((field) => field === null)) return null;
805
- const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
806
- if (steps?.some((step) => step === null)) return null;
807
- const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
808
- if (actions?.some((action) => action === null)) return null;
809
- if (value.description !== void 0 && !description) return null;
810
- if (value.operationId !== void 0 && !operationId) return null;
811
- if (value.requestId !== void 0 && !requestId) return null;
812
- if (value.submitLabel !== void 0 && !submitLabel) return null;
813
- const values = value.values !== void 0 && isRecord3(value.values) ? value.values : void 0;
814
- if (value.values !== void 0) {
815
- if (!values || !Object.values(values).every((item) => isJsonValue(item)))
816
- return null;
873
+ abortAll() {
874
+ for (const controller of this.controllers.values()) controller.abort();
875
+ this.controllers.clear();
817
876
  }
818
- return {
819
- schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
820
- id,
821
- title,
822
- toolSlug,
823
- fields,
824
- ...actions ? { actions } : {},
825
- ...description ? { description } : {},
826
- ...operationId ? { operationId } : {},
827
- ...requestId ? { requestId } : {},
828
- ...submitLabel ? { submitLabel } : {},
829
- ...steps ? { steps } : {},
830
- ...values ? { values } : {}
831
- };
832
- }
833
- function hasOnlyKeys(value, allowed) {
834
- const allowedKeys = new Set(allowed);
835
- return Object.keys(value).every((key) => allowedKeys.has(key));
836
- }
877
+ beginPath(childStreamPath) {
878
+ if (this.tasks.has(childStreamPath)) return;
879
+ const controller = new AbortController();
880
+ const abort = () => controller.abort();
881
+ if (this.parentSignal.aborted) {
882
+ controller.abort();
883
+ } else {
884
+ this.parentSignal.addEventListener("abort", abort, { once: true });
885
+ }
886
+ this.controllers.set(childStreamPath, controller);
887
+ const task = this.consume(childStreamPath, controller.signal).finally(
888
+ () => {
889
+ this.parentSignal.removeEventListener("abort", abort);
890
+ if (this.controllers.get(childStreamPath) === controller) {
891
+ this.controllers.delete(childStreamPath);
892
+ }
893
+ }
894
+ );
895
+ this.tasks.set(childStreamPath, task);
896
+ }
897
+ async consume(path, signal) {
898
+ const workItems = /* @__PURE__ */ new Map();
899
+ let streamIndex = 0;
900
+ let consecutiveRetries = 0;
901
+ let retryDelayMs = INITIAL_RETRY_DELAY_MS;
902
+ while (!signal.aborted && consecutiveRetries < MAX_CONSECUTIVE_RETRIES) {
903
+ let receivedEvent = false;
904
+ try {
905
+ const response = await this.client.fetch(
906
+ streamPathAt(path, streamIndex),
907
+ {
908
+ cache: "no-store",
909
+ signal
910
+ }
911
+ );
912
+ if (!response.ok || response.body === null) {
913
+ await response.body?.cancel().catch(() => {
914
+ });
915
+ throw new Error(`Child stream returned ${response.status}.`);
916
+ }
917
+ for await (const rawEvent of readNdjsonStream(response.body)) {
918
+ if (signal.aborted) return;
919
+ receivedEvent = true;
920
+ streamIndex += 1;
921
+ const event = parseChildStreamEvent(rawEvent);
922
+ if (event.type === "session.boundary") return;
923
+ if (event.type === "subagent.called") {
924
+ this.beginPath(event.childStreamPath);
925
+ continue;
926
+ }
927
+ if (event.type === "actions.requested") {
928
+ for (const item2 of event.items) {
929
+ workItems.set(item2.id, item2);
930
+ this.handlers.onWork?.(item2);
931
+ }
932
+ continue;
933
+ }
934
+ if (event.type !== "action.result") continue;
935
+ const item = workItems.get(event.result.callId);
936
+ if (item) {
937
+ this.handlers.onWork?.(completeConnectedToolWork(item, event.result));
938
+ }
939
+ this.handlers.onToolResult?.(event.result);
940
+ if (event.hasOutput) {
941
+ this.handlers.onActionResult?.(event.result.output);
942
+ }
943
+ }
944
+ } catch {
945
+ if (signal.aborted) return;
946
+ }
947
+ consecutiveRetries = receivedEvent ? 0 : consecutiveRetries + 1;
948
+ retryDelayMs = receivedEvent ? INITIAL_RETRY_DELAY_MS : Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
949
+ await abortableDelay(retryDelayMs, signal);
950
+ }
951
+ }
952
+ };
837
953
 
838
954
  // src/runtime/client.ts
839
955
  function isTurnBoundary(event) {
@@ -867,7 +983,7 @@ function emitActionResult(event, handlers) {
867
983
  handlers.onToolResult?.({
868
984
  callId: result.callId,
869
985
  toolName: result.toolName,
870
- status: event.data.status,
986
+ status: result.isError ? "failed" : event.data.status,
871
987
  output: result.output,
872
988
  ...event.data.error ? { error: event.data.error } : {}
873
989
  });
@@ -993,9 +1109,13 @@ function requestedWorkItem(action) {
993
1109
  state: "active"
994
1110
  };
995
1111
  }
996
- return null;
1112
+ return action.kind === "tool-call" ? connectedToolWork(action) : null;
997
1113
  }
998
1114
  function applyWorkEvent(event, handlers, workItems) {
1115
+ if (event.type === "subagent.event") {
1116
+ applyWorkEvent(event.data.event, handlers, workItems);
1117
+ return;
1118
+ }
999
1119
  if (event.type === "step.started" && workItems.size === 0) {
1000
1120
  emitWorkItem(
1001
1121
  {
@@ -1054,6 +1174,15 @@ function applyWorkEvent(event, handlers, workItems) {
1054
1174
  const { result, status } = event.data;
1055
1175
  const current = workItems.get(result.callId);
1056
1176
  if (!current) return;
1177
+ if (current.kind === "tool" && result.kind === "tool-result") {
1178
+ emitWorkItem(completeConnectedToolWork(current, {
1179
+ callId: result.callId,
1180
+ toolName: result.toolName,
1181
+ status: result.isError ? "failed" : status,
1182
+ output: result.output
1183
+ }), handlers, workItems);
1184
+ return;
1185
+ }
1057
1186
  const failed = status !== "completed" || result.isError === true;
1058
1187
  emitWorkItem(
1059
1188
  {
@@ -2091,10 +2220,15 @@ function parseMessage(value) {
2091
2220
  const result = parseToolResult(value2);
2092
2221
  return result?.kind === "search" ? [result] : [];
2093
2222
  }) : [];
2223
+ const toolResults = Array.isArray(record2.toolResults) ? record2.toolResults.flatMap((value2) => {
2224
+ const result = parseToolResult(value2);
2225
+ return result && result.kind !== "search" && result.kind !== "input" ? [result] : [];
2226
+ }) : [];
2094
2227
  return {
2095
2228
  id: record2.id,
2096
2229
  role: "agent",
2097
2230
  ...searchResults.length ? { searchResults } : {},
2231
+ ...toolResults.length ? { toolResults } : {},
2098
2232
  text: record2.text,
2099
2233
  createdAt: record2.createdAt
2100
2234
  };
@@ -2102,7 +2236,7 @@ function parseMessage(value) {
2102
2236
  function parseToolStep(value) {
2103
2237
  if (typeof value !== "object" || value === null) return null;
2104
2238
  const record2 = value;
2105
- 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") {
2239
+ 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") {
2106
2240
  return null;
2107
2241
  }
2108
2242
  return {
@@ -2163,6 +2297,15 @@ function parseInputRequest(value) {
2163
2297
  ...ui ? { ui } : {}
2164
2298
  };
2165
2299
  }
2300
+ function parseResultDetails(value) {
2301
+ if (!Array.isArray(value)) return [];
2302
+ return value.slice(0, 6).flatMap((item) => {
2303
+ if (typeof item !== "object" || item === null) return [];
2304
+ const detail = item;
2305
+ if (typeof detail.label !== "string" || typeof detail.value !== "string") return [];
2306
+ return [{ label: detail.label.slice(0, 240), value: detail.value.slice(0, 240) }];
2307
+ });
2308
+ }
2166
2309
  function parseToolResult(value) {
2167
2310
  if (typeof value !== "object" || value === null) return null;
2168
2311
  const record2 = value;
@@ -2198,6 +2341,7 @@ function parseToolResult(value) {
2198
2341
  status: record2.status,
2199
2342
  kind: "entity",
2200
2343
  title: record2.title,
2344
+ details: parseResultDetails(record2.details),
2201
2345
  ...typeof record2.description === "string" ? { description: record2.description } : {}
2202
2346
  };
2203
2347
  }
@@ -2241,6 +2385,7 @@ function parseToolResult(value) {
2241
2385
  status: record2.status,
2242
2386
  kind: "summary",
2243
2387
  title: record2.title,
2388
+ details: parseResultDetails(record2.details),
2244
2389
  ...typeof record2.description === "string" ? { description: record2.description } : {}
2245
2390
  };
2246
2391
  }
@@ -2345,63 +2490,6 @@ function clearPendingWidgetBooking(storageKeyPrefix, visitorSessionId) {
2345
2490
  );
2346
2491
  }
2347
2492
 
2348
- // src/runtime/tool-result-envelope.ts
2349
- var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
2350
- "schemaVersion",
2351
- "output",
2352
- "presentationKinds",
2353
- "ui"
2354
- ]);
2355
- function isRecord4(value) {
2356
- return value !== null && typeof value === "object" && !Array.isArray(value);
2357
- }
2358
- function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
2359
- if (value === null || typeof value === "string" || typeof value === "boolean") {
2360
- return true;
2361
- }
2362
- if (typeof value === "number") return Number.isFinite(value);
2363
- if (typeof value !== "object") return false;
2364
- if (seen.has(value)) return false;
2365
- seen.add(value);
2366
- const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
2367
- ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
2368
- );
2369
- seen.delete(value);
2370
- return valid;
2371
- }
2372
- function decodeEnvelope(value) {
2373
- if (typeof value !== "string") return value;
2374
- try {
2375
- return JSON.parse(value);
2376
- } catch {
2377
- return null;
2378
- }
2379
- }
2380
- function parseAgentToolResultEnvelope(value) {
2381
- const decoded = decodeEnvelope(value);
2382
- if (!isRecord4(decoded)) return null;
2383
- const keys = Object.keys(decoded);
2384
- 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) {
2385
- return null;
2386
- }
2387
- const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
2388
- if (typeof kind !== "string") return [];
2389
- const normalized = kind.trim();
2390
- return normalized && normalized.length <= 128 ? [normalized] : [];
2391
- });
2392
- if (presentationKinds.length !== decoded.presentationKinds.length) {
2393
- return null;
2394
- }
2395
- const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
2396
- if (decoded.ui !== void 0 && !ui) return null;
2397
- return {
2398
- schemaVersion: "webless.tool-result.v1",
2399
- output: decoded.output,
2400
- presentationKinds,
2401
- ...ui ? { ui } : {}
2402
- };
2403
- }
2404
-
2405
2493
  // src/react/lib/tool-result.ts
2406
2494
  var MAX_TEXT_LENGTH = 240;
2407
2495
  var MAX_DETAILS = 6;
@@ -2636,6 +2724,9 @@ function finalizeSummaryPresentation(result, proposed) {
2636
2724
  };
2637
2725
  }
2638
2726
  function presentVisitorToolResult(result, registry = []) {
2727
+ if (result.status === "completed" && toolResultFailed(result)) {
2728
+ result = { ...result, status: "failed" };
2729
+ }
2639
2730
  if (result.toolName === "search_discovery" && result.status === "completed") {
2640
2731
  const envelope2 = parseAgentToolResultEnvelope(result.output);
2641
2732
  const search = parseAgentSearchDiscoveryOutput(
@@ -2672,7 +2763,7 @@ function presentVisitorToolResult(result, registry = []) {
2672
2763
  surface: envelope.ui
2673
2764
  };
2674
2765
  }
2675
- if (!proposed) {
2766
+ if (!proposed || result.status !== "completed" && proposed.kind !== "summary") {
2676
2767
  if (result.status === "failed" || result.status === "rejected") {
2677
2768
  return {
2678
2769
  id: result.callId,
@@ -2795,14 +2886,15 @@ function isNearDuplicateAssistantText(left, right) {
2795
2886
  shorter.slice(0, Math.floor(shorter.length * 0.85))
2796
2887
  );
2797
2888
  }
2798
- function appendAgentTurnMessage(messages, displayText, searchResults = []) {
2889
+ function appendAgentTurnMessage(messages, displayText, searchResults = [], toolResults = []) {
2799
2890
  const trimmed = displayText.trim();
2800
- if (!trimmed && searchResults.length === 0) return [...messages];
2891
+ if (!trimmed && searchResults.length === 0 && toolResults.length === 0) return [...messages];
2801
2892
  const agentMessage = {
2802
2893
  id: `agent-${Date.now()}`,
2803
2894
  role: "agent",
2804
2895
  text: trimmed,
2805
2896
  ...searchResults.length ? { searchResults } : {},
2897
+ ...toolResults.length ? { toolResults } : {},
2806
2898
  createdAt: Date.now()
2807
2899
  };
2808
2900
  const last = messages.at(-1);
@@ -3184,10 +3276,11 @@ function useAgentChat({
3184
3276
  messages: appendAgentTurnMessage(
3185
3277
  prev.messages,
3186
3278
  displayText,
3187
- (prev.toolResults ?? []).filter((result) => result.kind === "search")
3279
+ (prev.toolResults ?? []).filter((result) => result.kind === "search"),
3280
+ (prev.toolResults ?? []).filter((result) => result.kind !== "search" && result.kind !== "input")
3188
3281
  ),
3189
3282
  toolResults: (prev.toolResults ?? []).filter(
3190
- (result) => result.kind !== "search"
3283
+ (result) => result.kind === "input"
3191
3284
  ),
3192
3285
  toolSteps: completeActivePlanning(prev.toolSteps),
3193
3286
  streamingText: "",
@@ -3249,16 +3342,18 @@ function useAgentChat({
3249
3342
  const submit = (0, import_react2.useCallback)(
3250
3343
  async (visitorText, options) => {
3251
3344
  const trimmed = visitorText.trim();
3252
- if (!trimmed) return null;
3345
+ const outgoing = options?.runtimeText ?? visitorText;
3346
+ if (!outgoing.trim()) return null;
3253
3347
  const chatResponse = chatInputResponseForText(
3254
3348
  state.pendingInputs ?? [],
3255
- trimmed
3349
+ outgoing.trim()
3256
3350
  );
3257
3351
  if (chatResponse) {
3258
3352
  const visitorMessage2 = {
3259
3353
  id: `visitor-${Date.now()}`,
3260
3354
  role: "visitor",
3261
3355
  text: trimmed,
3356
+ ...outgoing !== trimmed ? { runtimeText: outgoing } : {},
3262
3357
  createdAt: Date.now()
3263
3358
  };
3264
3359
  if (runRef.current) {
@@ -3298,7 +3393,6 @@ function useAgentChat({
3298
3393
  const controller = new AbortController();
3299
3394
  runRef.current = controller;
3300
3395
  const booking = pendingBookingRef.current;
3301
- const outgoing = options?.runtimeText ?? visitorText;
3302
3396
  const runtimeText = booking ? `${visitorBookingPrefix(booking)}
3303
3397
 
3304
3398
  ${outgoing}` : outgoing;
@@ -3661,6 +3755,10 @@ function joinLabels(labels) {
3661
3755
  return `${labels.slice(0, -1).join(", ")} and ${labels.at(-1) ?? ""}`;
3662
3756
  }
3663
3757
  function workSummary(steps, failed, brandLabel) {
3758
+ const activeTool = [...steps].reverse().find(
3759
+ (step) => step.kind === "tool" && step.state === "active"
3760
+ );
3761
+ if (activeTool) return activeTool.detail ?? "Working on your request";
3664
3762
  const activeSpecialists = steps.filter(
3665
3763
  (step) => step.kind === "specialist" && step.state === "active"
3666
3764
  );
@@ -3988,11 +4086,15 @@ function Composer({
3988
4086
  const trimmed = value.trim();
3989
4087
  if (!trimmed || disabled) return;
3990
4088
  const shareDismissal = savedForm && !draft.dismissalSent;
3991
- onSubmit?.(
3992
- 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.
4089
+ if (shareDismissal) {
4090
+ onSubmit?.(trimmed, {
4091
+ 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.
3993
4092
 
3994
- ${trimmed}` : trimmed
3995
- );
4093
+ ${trimmed}`
4094
+ });
4095
+ } else {
4096
+ onSubmit?.(trimmed);
4097
+ }
3996
4098
  if (shareDismissal)
3997
4099
  setDraft((current) => ({ ...current, dismissalSent: true }));
3998
4100
  setValue("");
@@ -4009,7 +4111,9 @@ ${trimmed}` : trimmed
4009
4111
  if (control instanceof HTMLElement) control.focus();
4010
4112
  return;
4011
4113
  }
4012
- onSubmit?.(formatComposerFormMessage(activeForm, draft.values));
4114
+ onSubmit?.("", {
4115
+ runtimeText: formatComposerFormMessage(activeForm, draft.values)
4116
+ });
4013
4117
  setDraft((current) => ({
4014
4118
  ...current,
4015
4119
  values: emptyValues(activeForm),
@@ -4273,25 +4377,11 @@ function SearchReferences({
4273
4377
  });
4274
4378
  if (!sources.length && !actions.length) return null;
4275
4379
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "search-references", children: [
4276
- sources.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { "aria-label": "Sources", children: [
4277
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("h3", { className: "search-references__heading", children: [
4278
- "Sources",
4279
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { title: "Pages used by Search & Discovery", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4280
- "svg",
4281
- {
4282
- viewBox: "0 0 24 24",
4283
- width: "14",
4284
- height: "14",
4285
- fill: "none",
4286
- stroke: "currentColor",
4287
- strokeWidth: "1.75",
4288
- "aria-hidden": "true",
4289
- children: [
4290
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "12", cy: "12", r: "9" }),
4291
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 11v6M12 7v1" })
4292
- ]
4293
- }
4294
- ) })
4380
+ sources.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "search-references__disclosure", "aria-label": "Sources", children: [
4381
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("summary", { className: "search-references__heading", children: [
4382
+ "Sources (",
4383
+ sources.length,
4384
+ ")"
4295
4385
  ] }),
4296
4386
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "search-references__list", children: sources.map((source) => {
4297
4387
  const content = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
@@ -4369,7 +4459,23 @@ function calendarCells(year, month) {
4369
4459
  function BookingCardLoader() {
4370
4460
  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" }) });
4371
4461
  }
4372
- function BookingCard({
4462
+ function BookingCard(props) {
4463
+ if (!props.readOnly) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(InteractiveBookingCard, { ...props });
4464
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "booking-card", "aria-label": "Recorded available times", children: [
4465
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "booking-card__title", children: "Available times offered" }),
4466
+ props.offer.slots.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { children: props.offer.slots.map((slot, index) => {
4467
+ const eventType = props.offer.eventTypes.find(
4468
+ (item) => item.uri === slot.eventTypeUri
4469
+ );
4470
+ const label = Number.isFinite(Date.parse(slot.startTime)) ? formatSlotLabel(slot.startTime) : "Time unavailable";
4471
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { children: [
4472
+ eventType ? `${eventType.name} \xB7 ` : "",
4473
+ label
4474
+ ] }, `${slot.eventTypeUri ?? ""}:${slot.startTime}:${index}`);
4475
+ }) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "No available times were recorded." })
4476
+ ] });
4477
+ }
4478
+ function InteractiveBookingCard({
4373
4479
  disabled = false,
4374
4480
  offer,
4375
4481
  onBook
@@ -4735,6 +4841,7 @@ function MessageBubble({
4735
4841
  message,
4736
4842
  brandLogoUrl,
4737
4843
  bookingDisabled = false,
4844
+ bookingReadOnly = false,
4738
4845
  offer,
4739
4846
  onBook
4740
4847
  }) {
@@ -4752,6 +4859,7 @@ function MessageBubble({
4752
4859
  offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
4753
4860
  );
4754
4861
  if (message.role === "visitor") {
4862
+ if (!message.text.trim()) return null;
4755
4863
  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 }) });
4756
4864
  }
4757
4865
  const citations = message.citations ?? [];
@@ -4770,8 +4878,8 @@ function MessageBubble({
4770
4878
  children: displayText
4771
4879
  }
4772
4880
  ),
4773
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchReferences, { results: message.searchResults ?? [] }),
4774
- 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: [
4881
+ !isStreaming ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchReferences, { results: message.searchResults ?? [] }) : null,
4882
+ !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: [
4775
4883
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4776
4884
  "span",
4777
4885
  {
@@ -4783,6 +4891,7 @@ function MessageBubble({
4783
4891
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: citation.label })
4784
4892
  ] }) }, citation.id)) }) : null
4785
4893
  ] });
4894
+ if (!displayText && !message.searchResults?.length && offers.length === 0) return null;
4786
4895
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
4787
4896
  displayText || message.searchResults?.length ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__agent-row", children: [
4788
4897
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -4801,6 +4910,7 @@ function MessageBubble({
4801
4910
  BookingCard,
4802
4911
  {
4803
4912
  disabled: bookingDisabled,
4913
+ readOnly: bookingReadOnly,
4804
4914
  offer: nextOffer,
4805
4915
  onBook
4806
4916
  },
@@ -5959,6 +6069,7 @@ function CollectionResultCard({
5959
6069
  {
5960
6070
  className: `collection-result-card tool-result-card tool-result-card--${result.status}`,
5961
6071
  "aria-label": result.title,
6072
+ role: result.status === "completed" ? "status" : "alert",
5962
6073
  children: [
5963
6074
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "tool-result-card__heading", children: [
5964
6075
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
@@ -5983,31 +6094,44 @@ var import_jsx_runtime13 = require("react/jsx-runtime");
5983
6094
  function EntityResultCard({
5984
6095
  result
5985
6096
  }) {
6097
+ const compact = !result.description && !result.details?.length && !result.links?.length;
6098
+ const collapsible = result.status === "completed" && Boolean(result.details?.length) && !result.links?.length;
6099
+ const heading = /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "tool-result-card__heading", children: [
6100
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
6101
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: result.title })
6102
+ ] });
6103
+ const content = /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
6104
+ result.description ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: result.description }) : null,
6105
+ result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
6106
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: detail.label }),
6107
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: detail.value })
6108
+ ] }, `${detail.label}:${detail.value}`)) }) : null,
6109
+ 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)(
6110
+ "a",
6111
+ {
6112
+ href: link.href,
6113
+ target: "_blank",
6114
+ rel: "noreferrer",
6115
+ children: link.label
6116
+ },
6117
+ link.href
6118
+ )) }) : null
6119
+ ] });
6120
+ if (collapsible) {
6121
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("details", { className: "entity-result-card tool-result-card entity-result-card--disclosure", children: [
6122
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("summary", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { role: "status", children: heading }) }),
6123
+ content
6124
+ ] });
6125
+ }
5986
6126
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
5987
6127
  "section",
5988
6128
  {
5989
- className: `entity-result-card tool-result-card tool-result-card--${result.status}`,
6129
+ className: `entity-result-card tool-result-card tool-result-card--${result.status}${compact ? " entity-result-card--compact" : ""}`,
5990
6130
  "aria-label": result.title,
6131
+ role: result.status === "completed" ? "status" : "alert",
5991
6132
  children: [
5992
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "tool-result-card__heading", children: [
5993
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
5994
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: result.title })
5995
- ] }),
5996
- result.description ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: result.description }) : null,
5997
- result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
5998
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: detail.label }),
5999
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: detail.value })
6000
- ] }, `${detail.label}:${detail.value}`)) }) : null,
6001
- 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)(
6002
- "a",
6003
- {
6004
- href: link.href,
6005
- target: "_blank",
6006
- rel: "noreferrer",
6007
- children: link.label
6008
- },
6009
- link.href
6010
- )) }) : null
6133
+ heading,
6134
+ content
6011
6135
  ]
6012
6136
  }
6013
6137
  );
@@ -6056,6 +6180,7 @@ function ToolResultCard({
6056
6180
  {
6057
6181
  className: `tool-result-card tool-result-card--${result.status}`,
6058
6182
  "aria-label": result.title,
6183
+ role: result.status === "completed" ? "status" : "alert",
6059
6184
  children: [
6060
6185
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "tool-result-card__heading", children: [
6061
6186
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
@@ -6219,6 +6344,9 @@ function AgentRail({
6219
6344
  const railRef = (0, import_react14.useRef)(null);
6220
6345
  const overlayRef = (0, import_react14.useRef)(null);
6221
6346
  const transcriptRef = (0, import_react14.useRef)(null);
6347
+ const responseRef = (0, import_react14.useRef)(null);
6348
+ const threadRef = (0, import_react14.useRef)(null);
6349
+ const lastScrolledVisitorIdRef = (0, import_react14.useRef)(void 0);
6222
6350
  const pinnedToBottomRef = (0, import_react14.useRef)(true);
6223
6351
  const smoothScrollToLatestRef = (0, import_react14.useRef)(false);
6224
6352
  const lockedTranscriptScrollTopRef = (0, import_react14.useRef)(null);
@@ -6242,7 +6370,7 @@ function AgentRail({
6242
6370
  const activeVisitorToolInput = [...visitorToolResults].reverse().find((result) => result.kind === "input");
6243
6371
  const visibleVisitorToolResults = activeVisitorToolInput ? [activeVisitorToolInput] : visitorToolResults;
6244
6372
  const activityActive = state.toolSteps.some((step) => step.state === "active");
6245
- const showActivity = state.toolSteps.length > 0 && visibleVisitorToolResults.length === 0 && pendingInputRequests.length === 0 && !state.pendingOffer && activityActive;
6373
+ const showActivity = state.toolSteps.length > 0 && pendingInputRequests.length === 0 && !state.pendingOffer && activityActive;
6246
6374
  const hasVisitorMessages2 = state.messages.some(
6247
6375
  (message) => message.role === "visitor"
6248
6376
  );
@@ -6271,7 +6399,7 @@ function AgentRail({
6271
6399
  }
6272
6400
  }
6273
6401
  const lastIsAgent = lastMessage?.role === "agent";
6274
- const showMessageActions = state.phase === "complete" && lastIsAgent && hasVisitorMessages2;
6402
+ const showMessageActions = state.phase === "complete" && lastIsAgent && hasVisitorMessages2 && Boolean(hideToolCardFences(lastMessage.text).trim());
6275
6403
  const streamingMessage = state.phase === "streaming" && state.streamingText && !lastIsAgent ? {
6276
6404
  createdAt: 0,
6277
6405
  id: "streaming-response",
@@ -6301,15 +6429,21 @@ function AgentRail({
6301
6429
  hasPendingConfirmation,
6302
6430
  enabled: lastIsAgent && !isBusy
6303
6431
  });
6432
+ const latestResultId = [
6433
+ ...visibleMessages.flatMap(
6434
+ (message) => message.role === "agent" ? message.toolResults ?? [] : []
6435
+ ),
6436
+ ...visibleVisitorToolResults
6437
+ ].reverse().find((result) => result.kind !== "input")?.id;
6304
6438
  const receiptSteps = answerReceipt && state.toolSteps.length > 0 ? state.toolSteps : void 0;
6305
6439
  (0, import_react14.useEffect)(() => {
6306
6440
  if (state.phase !== "complete") {
6307
6441
  setReceiptOpen(false);
6308
6442
  }
6309
6443
  }, [state.phase]);
6310
- function handleSubmit(message) {
6444
+ function handleSubmit(message, options) {
6311
6445
  setReceiptOpen(false);
6312
- onSubmit?.(message);
6446
+ onSubmit?.(message, options);
6313
6447
  }
6314
6448
  function handleRegenerate() {
6315
6449
  setReceiptOpen(false);
@@ -6323,15 +6457,31 @@ function AgentRail({
6323
6457
  setReceiptOpen(false);
6324
6458
  onFollowUpSelect?.(label);
6325
6459
  }
6460
+ const latestVisitorId = visibleMessages[lastVisitorIndex]?.id;
6326
6461
  (0, import_react14.useEffect)(() => {
6327
6462
  const node = transcriptRef.current;
6328
6463
  if (!node) return;
6329
- const lastMessage2 = state.messages.at(-1);
6330
- const visitorJustSent = lastMessage2?.role === "visitor";
6331
- if (pinnedToBottomRef.current || visitorJustSent) {
6332
- node.scrollTop = node.scrollHeight;
6464
+ if (latestVisitorId !== lastScrolledVisitorIdRef.current) {
6465
+ lastScrolledVisitorIdRef.current = latestVisitorId;
6466
+ pinnedToBottomRef.current = true;
6333
6467
  }
6468
+ const followResponse = () => {
6469
+ if (node.clientHeight === 0 || !pinnedToBottomRef.current) return;
6470
+ const bottom = Math.max(0, node.scrollHeight - node.clientHeight);
6471
+ const response = responseRef.current;
6472
+ const responseTop = response ? node.scrollTop + response.getBoundingClientRect().top - node.getBoundingClientRect().top : bottom;
6473
+ node.scrollTop = Math.max(node.scrollTop, Math.min(bottom, responseTop));
6474
+ const pinned = bottom - node.scrollTop < 48;
6475
+ pinnedToBottomRef.current = pinned;
6476
+ setShowJumpToLatest(!pinned);
6477
+ };
6478
+ followResponse();
6479
+ const observer = new ResizeObserver(followResponse);
6480
+ observer.observe(node);
6481
+ if (threadRef.current) observer.observe(threadRef.current);
6482
+ return () => observer.disconnect();
6334
6483
  }, [
6484
+ latestVisitorId,
6335
6485
  state.messages,
6336
6486
  state.toolSteps,
6337
6487
  state.streamingText,
@@ -6352,18 +6502,6 @@ function AgentRail({
6352
6502
  handleScroll();
6353
6503
  return () => node.removeEventListener("scroll", handleScroll);
6354
6504
  }, []);
6355
- (0, import_react14.useEffect)(() => {
6356
- const node = transcriptRef.current;
6357
- if (!node) return;
6358
- const observer = new ResizeObserver(() => {
6359
- if (node.clientHeight === 0) return;
6360
- if (pinnedToBottomRef.current) {
6361
- node.scrollTo({ top: node.scrollHeight, behavior: "instant" });
6362
- }
6363
- });
6364
- observer.observe(node);
6365
- return () => observer.disconnect();
6366
- }, []);
6367
6505
  (0, import_react14.useEffect)(() => {
6368
6506
  if (!receiptOpen) {
6369
6507
  lockedTranscriptScrollTopRef.current = null;
@@ -6480,7 +6618,7 @@ function AgentRail({
6480
6618
  ) : null
6481
6619
  ] })
6482
6620
  ] }) }),
6483
- /* @__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: [
6621
+ /* @__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: [
6484
6622
  !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
6485
6623
  greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6486
6624
  MessageBubble,
@@ -6527,59 +6665,68 @@ function AgentRail({
6527
6665
  request.requestId
6528
6666
  ))
6529
6667
  ] }) : null,
6530
- visibleMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "agent-rail__turn-block", children: [
6531
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6532
- MessageBubble,
6533
- {
6534
- message,
6535
- bookingDisabled: isBusy,
6536
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6537
- offer: index === lastAgentIndex ? state.pendingOffer : void 0,
6538
- onBook
6539
- }
6540
- ),
6541
- index === lastAgentIndex && showMessageActions && message.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6542
- MessageActions,
6543
- {
6544
- answeredAt: message.createdAt,
6545
- copyText: hideToolCardFences(message.text).trim() || message.text,
6546
- readAloud,
6547
- receiptSteps,
6548
- onOpenReceipt: receiptSteps ? openReceipt : void 0,
6549
- onRegenerate: onRegenerate ? handleRegenerate : void 0,
6550
- onFeedback: onFeedback ? (rating) => onFeedback(rating, message) : void 0
6551
- }
6552
- ) : null,
6553
- index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
6554
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6555
- AgentActivityBubble,
6556
- {
6557
- brandLabel: resolvedBrandLabel,
6558
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6559
- failed: state.phase === "error",
6560
- steps: state.toolSteps
6561
- }
6562
- ) : null,
6563
- visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6564
- VisitorToolResultView,
6565
- {
6566
- result,
6567
- disabled: semanticSurfaceDisabled,
6568
- onToolInput
6569
- },
6570
- result.id
6571
- )),
6572
- pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6573
- HumanInputCard,
6574
- {
6575
- request,
6576
- onRespond: onInputResponse
6577
- },
6578
- request.requestId
6579
- ))
6580
- ] }) : null
6581
- ] }, message.id)),
6582
- streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6668
+ visibleMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
6669
+ "div",
6670
+ {
6671
+ ref: index === lastVisitorIndex + 1 ? responseRef : void 0,
6672
+ className: "agent-rail__turn-block",
6673
+ children: [
6674
+ message.role === "agent" ? message.toolResults?.map((result) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VisitorToolResultView, { result }, result.id)) : null,
6675
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6676
+ MessageBubble,
6677
+ {
6678
+ message,
6679
+ bookingDisabled: isBusy,
6680
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6681
+ offer: index === lastAgentIndex ? state.pendingOffer : void 0,
6682
+ onBook
6683
+ }
6684
+ ),
6685
+ index === lastAgentIndex && showMessageActions && message.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6686
+ MessageActions,
6687
+ {
6688
+ answeredAt: message.createdAt,
6689
+ copyText: hideToolCardFences(message.text).trim() || message.text,
6690
+ readAloud,
6691
+ receiptSteps,
6692
+ onOpenReceipt: receiptSteps ? openReceipt : void 0,
6693
+ onRegenerate: onRegenerate ? handleRegenerate : void 0,
6694
+ onFeedback: onFeedback ? (rating) => onFeedback(rating, message) : void 0
6695
+ }
6696
+ ) : null,
6697
+ index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
6698
+ showActivity ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6699
+ AgentActivityBubble,
6700
+ {
6701
+ brandLabel: resolvedBrandLabel,
6702
+ brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6703
+ failed: state.phase === "error",
6704
+ steps: state.toolSteps
6705
+ }
6706
+ ) : null,
6707
+ visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6708
+ VisitorToolResultView,
6709
+ {
6710
+ result,
6711
+ disabled: semanticSurfaceDisabled,
6712
+ onToolInput
6713
+ },
6714
+ result.id
6715
+ )),
6716
+ pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6717
+ HumanInputCard,
6718
+ {
6719
+ request,
6720
+ onRespond: onInputResponse
6721
+ },
6722
+ request.requestId
6723
+ ))
6724
+ ] }) : null
6725
+ ]
6726
+ },
6727
+ message.id
6728
+ )),
6729
+ streamingMessage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref: responseRef, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6583
6730
  MessageBubble,
6584
6731
  {
6585
6732
  message: streamingMessage,
@@ -6588,7 +6735,7 @@ function AgentRail({
6588
6735
  offer: state.pendingOffer,
6589
6736
  onBook
6590
6737
  }
6591
- ) : null,
6738
+ ) }) : null,
6592
6739
  waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(BookingCardLoader, {}) : null,
6593
6740
  state.error ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
6594
6741
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
@@ -6629,7 +6776,7 @@ function AgentRail({
6629
6776
  placeholder: composerPlaceholder,
6630
6777
  onSubmit: handleSubmit
6631
6778
  },
6632
- state.messages.find((message) => message.role === "visitor")?.id ?? "empty"
6779
+ `${state.messages.find((message) => message.role === "visitor")?.id ?? "empty"}:${latestResultId ?? ""}`
6633
6780
  ),
6634
6781
  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
6635
6782
  ] })
@@ -6997,9 +7144,9 @@ function AgentWidget({
6997
7144
  });
6998
7145
  return () => unregisterAgentPanelController(customerId);
6999
7146
  }, [customerId, registerPanelController, reset, submit]);
7000
- async function handleSubmit(message) {
7147
+ async function handleSubmit(message, options) {
7001
7148
  if (isMobile) setRailCollapsed(false);
7002
- await submit(message);
7149
+ await submit(message, options);
7003
7150
  }
7004
7151
  function handleFeedback(rating, message) {
7005
7152
  if (!analytics) return;