@webless/agent 0.9.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.
Files changed (39) hide show
  1. package/README.md +6 -0
  2. package/dist/{chunk-QMTI5646.js → chunk-3E632FNE.js} +332 -203
  3. package/dist/chunk-3E632FNE.js.map +1 -0
  4. package/dist/{chunk-5BCSXCLT.js → chunk-TCO62TRN.js} +40 -2
  5. package/dist/chunk-TCO62TRN.js.map +1 -0
  6. package/dist/embed.cjs +754 -625
  7. package/dist/embed.cjs.map +1 -1
  8. package/dist/embed.css +60 -10
  9. package/dist/embed.css.map +1 -1
  10. package/dist/embed.d.cts +3 -9
  11. package/dist/embed.d.ts +3 -9
  12. package/dist/embed.js +1 -1
  13. package/dist/index.cjs +752 -680
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +2 -2
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +43 -4
  18. package/dist/index.js.map +1 -1
  19. package/dist/{manifest-DgjT8-tW.d.cts → panel-controller-D_XIQa1U.d.cts} +9 -2
  20. package/dist/{manifest-DgjT8-tW.d.ts → panel-controller-D_XIQa1U.d.ts} +9 -2
  21. package/dist/presentation.cjs +79 -67
  22. package/dist/presentation.cjs.map +1 -1
  23. package/dist/presentation.d.cts +1 -1
  24. package/dist/presentation.d.ts +1 -1
  25. package/dist/presentation.js +7 -3
  26. package/dist/presentation.js.map +1 -1
  27. package/dist/react.cjs +756 -625
  28. package/dist/react.cjs.map +1 -1
  29. package/dist/react.css +60 -10
  30. package/dist/react.css.map +1 -1
  31. package/dist/react.d.cts +3 -3
  32. package/dist/react.d.ts +3 -3
  33. package/dist/react.js +3 -1
  34. package/dist/react.js.map +1 -1
  35. package/dist/{types-ohWeTG9i.d.cts → types-sLEIYDqm.d.cts} +1 -1
  36. package/dist/{types-ohWeTG9i.d.ts → types-sLEIYDqm.d.ts} +1 -1
  37. package/package.json +1 -1
  38. package/dist/chunk-5BCSXCLT.js.map +0 -1
  39. package/dist/chunk-QMTI5646.js.map +0 -1
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
  }
@@ -383,457 +706,250 @@ function loadPersistedAgentSession(visitorSessionId, options) {
383
706
  }
384
707
  function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, options) {
385
708
  if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !sessionId.trim()) {
386
- return;
387
- }
388
- 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)
709
+ return;
710
+ }
711
+ const prefix = resolvePrefix(options);
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: [
@@ -4769,6 +4859,7 @@ function MessageBubble({
4769
4859
  offers.length > 0 ? sanitizeBookingOfferCopy(visibleText) : looksLikeBookingAvailabilityDump(visibleText) ? sanitizeBookingOfferCopy(visibleText) : visibleText || (isStreaming ? "" : message.text)
4770
4860
  );
4771
4861
  if (message.role === "visitor") {
4862
+ if (!message.text.trim()) return null;
4772
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 }) });
4773
4864
  }
4774
4865
  const citations = message.citations ?? [];
@@ -4787,8 +4878,8 @@ function MessageBubble({
4787
4878
  children: displayText
4788
4879
  }
4789
4880
  ),
4790
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchReferences, { results: message.searchResults ?? [] }),
4791
- 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: [
4792
4883
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4793
4884
  "span",
4794
4885
  {
@@ -4800,6 +4891,7 @@ function MessageBubble({
4800
4891
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: citation.label })
4801
4892
  ] }) }, citation.id)) }) : null
4802
4893
  ] });
4894
+ if (!displayText && !message.searchResults?.length && offers.length === 0) return null;
4803
4895
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("article", { className: "message-bubble message-bubble--agent", children: [
4804
4896
  displayText || message.searchResults?.length ? showBrandLogo ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "message-bubble__agent-row", children: [
4805
4897
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "message-bubble__agent-avatar", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -5977,6 +6069,7 @@ function CollectionResultCard({
5977
6069
  {
5978
6070
  className: `collection-result-card tool-result-card tool-result-card--${result.status}`,
5979
6071
  "aria-label": result.title,
6072
+ role: result.status === "completed" ? "status" : "alert",
5980
6073
  children: [
5981
6074
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "tool-result-card__heading", children: [
5982
6075
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
@@ -6001,31 +6094,44 @@ var import_jsx_runtime13 = require("react/jsx-runtime");
6001
6094
  function EntityResultCard({
6002
6095
  result
6003
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
+ }
6004
6126
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
6005
6127
  "section",
6006
6128
  {
6007
- 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" : ""}`,
6008
6130
  "aria-label": result.title,
6131
+ role: result.status === "completed" ? "status" : "alert",
6009
6132
  children: [
6010
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "tool-result-card__heading", children: [
6011
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
6012
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: result.title })
6013
- ] }),
6014
- result.description ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: result.description }) : null,
6015
- result.details?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dl", { children: result.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
6016
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: detail.label }),
6017
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: detail.value })
6018
- ] }, `${detail.label}:${detail.value}`)) }) : null,
6019
- 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)(
6020
- "a",
6021
- {
6022
- href: link.href,
6023
- target: "_blank",
6024
- rel: "noreferrer",
6025
- children: link.label
6026
- },
6027
- link.href
6028
- )) }) : null
6133
+ heading,
6134
+ content
6029
6135
  ]
6030
6136
  }
6031
6137
  );
@@ -6074,6 +6180,7 @@ function ToolResultCard({
6074
6180
  {
6075
6181
  className: `tool-result-card tool-result-card--${result.status}`,
6076
6182
  "aria-label": result.title,
6183
+ role: result.status === "completed" ? "status" : "alert",
6077
6184
  children: [
6078
6185
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "tool-result-card__heading", children: [
6079
6186
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "tool-result-card__status", "aria-hidden": "true" }),
@@ -6237,6 +6344,9 @@ function AgentRail({
6237
6344
  const railRef = (0, import_react14.useRef)(null);
6238
6345
  const overlayRef = (0, import_react14.useRef)(null);
6239
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);
6240
6350
  const pinnedToBottomRef = (0, import_react14.useRef)(true);
6241
6351
  const smoothScrollToLatestRef = (0, import_react14.useRef)(false);
6242
6352
  const lockedTranscriptScrollTopRef = (0, import_react14.useRef)(null);
@@ -6260,7 +6370,7 @@ function AgentRail({
6260
6370
  const activeVisitorToolInput = [...visitorToolResults].reverse().find((result) => result.kind === "input");
6261
6371
  const visibleVisitorToolResults = activeVisitorToolInput ? [activeVisitorToolInput] : visitorToolResults;
6262
6372
  const activityActive = state.toolSteps.some((step) => step.state === "active");
6263
- 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;
6264
6374
  const hasVisitorMessages2 = state.messages.some(
6265
6375
  (message) => message.role === "visitor"
6266
6376
  );
@@ -6289,7 +6399,7 @@ function AgentRail({
6289
6399
  }
6290
6400
  }
6291
6401
  const lastIsAgent = lastMessage?.role === "agent";
6292
- const showMessageActions = state.phase === "complete" && lastIsAgent && hasVisitorMessages2;
6402
+ const showMessageActions = state.phase === "complete" && lastIsAgent && hasVisitorMessages2 && Boolean(hideToolCardFences(lastMessage.text).trim());
6293
6403
  const streamingMessage = state.phase === "streaming" && state.streamingText && !lastIsAgent ? {
6294
6404
  createdAt: 0,
6295
6405
  id: "streaming-response",
@@ -6319,15 +6429,21 @@ function AgentRail({
6319
6429
  hasPendingConfirmation,
6320
6430
  enabled: lastIsAgent && !isBusy
6321
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;
6322
6438
  const receiptSteps = answerReceipt && state.toolSteps.length > 0 ? state.toolSteps : void 0;
6323
6439
  (0, import_react14.useEffect)(() => {
6324
6440
  if (state.phase !== "complete") {
6325
6441
  setReceiptOpen(false);
6326
6442
  }
6327
6443
  }, [state.phase]);
6328
- function handleSubmit(message) {
6444
+ function handleSubmit(message, options) {
6329
6445
  setReceiptOpen(false);
6330
- onSubmit?.(message);
6446
+ onSubmit?.(message, options);
6331
6447
  }
6332
6448
  function handleRegenerate() {
6333
6449
  setReceiptOpen(false);
@@ -6341,15 +6457,31 @@ function AgentRail({
6341
6457
  setReceiptOpen(false);
6342
6458
  onFollowUpSelect?.(label);
6343
6459
  }
6460
+ const latestVisitorId = visibleMessages[lastVisitorIndex]?.id;
6344
6461
  (0, import_react14.useEffect)(() => {
6345
6462
  const node = transcriptRef.current;
6346
6463
  if (!node) return;
6347
- const lastMessage2 = state.messages.at(-1);
6348
- const visitorJustSent = lastMessage2?.role === "visitor";
6349
- if (pinnedToBottomRef.current || visitorJustSent) {
6350
- node.scrollTop = node.scrollHeight;
6464
+ if (latestVisitorId !== lastScrolledVisitorIdRef.current) {
6465
+ lastScrolledVisitorIdRef.current = latestVisitorId;
6466
+ pinnedToBottomRef.current = true;
6351
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();
6352
6483
  }, [
6484
+ latestVisitorId,
6353
6485
  state.messages,
6354
6486
  state.toolSteps,
6355
6487
  state.streamingText,
@@ -6370,18 +6502,6 @@ function AgentRail({
6370
6502
  handleScroll();
6371
6503
  return () => node.removeEventListener("scroll", handleScroll);
6372
6504
  }, []);
6373
- (0, import_react14.useEffect)(() => {
6374
- const node = transcriptRef.current;
6375
- if (!node) return;
6376
- const observer = new ResizeObserver(() => {
6377
- if (node.clientHeight === 0) return;
6378
- if (pinnedToBottomRef.current) {
6379
- node.scrollTo({ top: node.scrollHeight, behavior: "instant" });
6380
- }
6381
- });
6382
- observer.observe(node);
6383
- return () => observer.disconnect();
6384
- }, []);
6385
6505
  (0, import_react14.useEffect)(() => {
6386
6506
  if (!receiptOpen) {
6387
6507
  lockedTranscriptScrollTopRef.current = null;
@@ -6498,7 +6618,7 @@ function AgentRail({
6498
6618
  ) : null
6499
6619
  ] })
6500
6620
  ] }) }),
6501
- /* @__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: [
6502
6622
  !hasVisitorMessages2 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("section", { className: "agent-rail__welcome", "aria-label": "Welcome", children: [
6503
6623
  greeting?.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6504
6624
  MessageBubble,
@@ -6545,59 +6665,68 @@ function AgentRail({
6545
6665
  request.requestId
6546
6666
  ))
6547
6667
  ] }) : null,
6548
- visibleMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "agent-rail__turn-block", children: [
6549
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6550
- MessageBubble,
6551
- {
6552
- message,
6553
- bookingDisabled: isBusy,
6554
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6555
- offer: index === lastAgentIndex ? state.pendingOffer : void 0,
6556
- onBook
6557
- }
6558
- ),
6559
- index === lastAgentIndex && showMessageActions && message.role === "agent" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6560
- MessageActions,
6561
- {
6562
- answeredAt: message.createdAt,
6563
- copyText: hideToolCardFences(message.text).trim() || message.text,
6564
- readAloud,
6565
- receiptSteps,
6566
- onOpenReceipt: receiptSteps ? openReceipt : void 0,
6567
- onRegenerate: onRegenerate ? handleRegenerate : void 0,
6568
- onFeedback: onFeedback ? (rating) => onFeedback(rating, message) : void 0
6569
- }
6570
- ) : null,
6571
- index === lastVisitorIndex ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
6572
- showActivity ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6573
- AgentActivityBubble,
6574
- {
6575
- brandLabel: resolvedBrandLabel,
6576
- brandLogoUrl: showBrandLogo ? resolvedBrandLogoUrl : void 0,
6577
- failed: state.phase === "error",
6578
- steps: state.toolSteps
6579
- }
6580
- ) : null,
6581
- visibleVisitorToolResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6582
- VisitorToolResultView,
6583
- {
6584
- result,
6585
- disabled: semanticSurfaceDisabled,
6586
- onToolInput
6587
- },
6588
- result.id
6589
- )),
6590
- pendingInputRequests.slice(0, 1).map((request) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6591
- HumanInputCard,
6592
- {
6593
- request,
6594
- onRespond: onInputResponse
6595
- },
6596
- request.requestId
6597
- ))
6598
- ] }) : null
6599
- ] }, message.id)),
6600
- 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)(
6601
6730
  MessageBubble,
6602
6731
  {
6603
6732
  message: streamingMessage,
@@ -6606,7 +6735,7 @@ function AgentRail({
6606
6735
  offer: state.pendingOffer,
6607
6736
  onBook
6608
6737
  }
6609
- ) : null,
6738
+ ) }) : null,
6610
6739
  waitingForBooking ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(BookingCardLoader, {}) : null,
6611
6740
  state.error ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("section", { className: "agent-rail__error", role: "alert", children: [
6612
6741
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
@@ -6647,7 +6776,7 @@ function AgentRail({
6647
6776
  placeholder: composerPlaceholder,
6648
6777
  onSubmit: handleSubmit
6649
6778
  },
6650
- state.messages.find((message) => message.role === "visitor")?.id ?? "empty"
6779
+ `${state.messages.find((message) => message.role === "visitor")?.id ?? "empty"}:${latestResultId ?? ""}`
6651
6780
  ),
6652
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
6653
6782
  ] })
@@ -7015,9 +7144,9 @@ function AgentWidget({
7015
7144
  });
7016
7145
  return () => unregisterAgentPanelController(customerId);
7017
7146
  }, [customerId, registerPanelController, reset, submit]);
7018
- async function handleSubmit(message) {
7147
+ async function handleSubmit(message, options) {
7019
7148
  if (isMobile) setRailCollapsed(false);
7020
- await submit(message);
7149
+ await submit(message, options);
7021
7150
  }
7022
7151
  function handleFeedback(rating, message) {
7023
7152
  if (!analytics) return;