@webless/agent 0.6.8 → 0.6.9

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.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resetAgentPanel,
10
10
  resolveAgentRuntimeConfig,
11
11
  submitAgentPanel
12
- } from "./chunk-CXIKIQJL.js";
12
+ } from "./chunk-U2SU2CGF.js";
13
13
 
14
14
  // src/embed/mount.tsx
15
15
  import { createRoot } from "react-dom/client";
package/dist/index.cjs CHANGED
@@ -309,6 +309,203 @@ function clearPersistedAgentSession(visitorSessionId, options) {
309
309
  sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
310
310
  }
311
311
 
312
+ // src/runtime/subagent-child-stream.ts
313
+ var INITIAL_RETRY_DELAY_MS = 100;
314
+ var MAX_RETRY_DELAY_MS = 2e3;
315
+ var MAX_CONSECUTIVE_RETRIES = 6;
316
+ function isRecord2(value) {
317
+ return typeof value === "object" && value !== null && !Array.isArray(value);
318
+ }
319
+ function parseError(value) {
320
+ if (!isRecord2(value)) return void 0;
321
+ const { code, message } = value;
322
+ if (typeof code !== "string" || typeof message !== "string") {
323
+ return void 0;
324
+ }
325
+ return { code, message };
326
+ }
327
+ function parseChildStreamEvent(value) {
328
+ if (!isRecord2(value) || typeof value.type !== "string") {
329
+ return { type: "other" };
330
+ }
331
+ if (value.type === "session.waiting" || value.type === "session.completed" || value.type === "session.failed") {
332
+ return { type: "session.boundary" };
333
+ }
334
+ if (value.type === "subagent.event" && isRecord2(value.data) && Object.hasOwn(value.data, "event")) {
335
+ return parseChildStreamEvent(value.data.event);
336
+ }
337
+ if (value.type === "subagent.called" && isRecord2(value.data)) {
338
+ const { childStreamPath } = value.data;
339
+ if (typeof childStreamPath === "string") {
340
+ return { childStreamPath, type: "subagent.called" };
341
+ }
342
+ }
343
+ if (value.type !== "action.result" || !isRecord2(value.data)) {
344
+ return { type: "other" };
345
+ }
346
+ const { data } = value;
347
+ if (data.status !== "completed" && data.status !== "failed" && data.status !== "rejected") {
348
+ return { type: "other" };
349
+ }
350
+ if (!isRecord2(data.result) || data.result.kind !== "tool-result") {
351
+ return { type: "other" };
352
+ }
353
+ const result = data.result;
354
+ if (typeof result.callId !== "string" || typeof result.toolName !== "string") {
355
+ return { type: "other" };
356
+ }
357
+ const error = parseError(data.error);
358
+ return {
359
+ type: "action.result",
360
+ hasOutput: Object.hasOwn(result, "output"),
361
+ result: {
362
+ callId: result.callId,
363
+ toolName: result.toolName,
364
+ status: data.status,
365
+ ...Object.hasOwn(result, "output") ? { output: result.output } : {},
366
+ ...error ? { error } : {}
367
+ }
368
+ };
369
+ }
370
+ async function* readNdjsonStream(body) {
371
+ const reader = body.getReader();
372
+ const decoder = new TextDecoder();
373
+ let buffer = "";
374
+ try {
375
+ while (true) {
376
+ const { done, value } = await reader.read();
377
+ buffer += decoder.decode(value, { stream: !done });
378
+ const lines = buffer.split("\n");
379
+ buffer = lines.pop() ?? "";
380
+ for (const line of lines) {
381
+ const trimmed2 = line.trim();
382
+ if (!trimmed2) continue;
383
+ try {
384
+ const parsed = JSON.parse(trimmed2);
385
+ yield parsed;
386
+ } catch {
387
+ }
388
+ }
389
+ if (done) break;
390
+ }
391
+ const trimmed = buffer.trim();
392
+ if (trimmed) {
393
+ try {
394
+ const parsed = JSON.parse(trimmed);
395
+ yield parsed;
396
+ } catch {
397
+ }
398
+ }
399
+ } finally {
400
+ reader.releaseLock();
401
+ }
402
+ }
403
+ function streamPathAt(path, streamIndex) {
404
+ if (streamIndex === 0) return path;
405
+ return `${path}${path.includes("?") ? "&" : "?"}startIndex=${streamIndex}`;
406
+ }
407
+ function abortableDelay(delayMs, signal) {
408
+ if (signal.aborted) return Promise.resolve();
409
+ return new Promise((resolve) => {
410
+ const finish = () => {
411
+ clearTimeout(timeout);
412
+ signal.removeEventListener("abort", finish);
413
+ resolve();
414
+ };
415
+ const timeout = setTimeout(finish, delayMs);
416
+ signal.addEventListener("abort", finish, { once: true });
417
+ });
418
+ }
419
+ var SubagentChildStreamCoordinator = class {
420
+ constructor(client, handlers, parentSignal) {
421
+ this.client = client;
422
+ this.handlers = handlers;
423
+ this.parentSignal = parentSignal;
424
+ }
425
+ client;
426
+ handlers;
427
+ parentSignal;
428
+ controllers = /* @__PURE__ */ new Map();
429
+ tasks = /* @__PURE__ */ new Map();
430
+ begin(event) {
431
+ this.beginPath(event.data.childStreamPath);
432
+ }
433
+ async waitForAll() {
434
+ let observedTaskCount = -1;
435
+ while (observedTaskCount !== this.tasks.size) {
436
+ observedTaskCount = this.tasks.size;
437
+ await Promise.all(this.tasks.values());
438
+ }
439
+ }
440
+ abortAll() {
441
+ for (const controller of this.controllers.values()) controller.abort();
442
+ this.controllers.clear();
443
+ }
444
+ beginPath(childStreamPath) {
445
+ if (this.tasks.has(childStreamPath)) return;
446
+ const controller = new AbortController();
447
+ const abort = () => controller.abort();
448
+ if (this.parentSignal.aborted) {
449
+ controller.abort();
450
+ } else {
451
+ this.parentSignal.addEventListener("abort", abort, { once: true });
452
+ }
453
+ this.controllers.set(childStreamPath, controller);
454
+ const task = this.consume(childStreamPath, controller.signal).finally(
455
+ () => {
456
+ this.parentSignal.removeEventListener("abort", abort);
457
+ if (this.controllers.get(childStreamPath) === controller) {
458
+ this.controllers.delete(childStreamPath);
459
+ }
460
+ }
461
+ );
462
+ this.tasks.set(childStreamPath, task);
463
+ }
464
+ async consume(path, signal) {
465
+ let streamIndex = 0;
466
+ let consecutiveRetries = 0;
467
+ let retryDelayMs = INITIAL_RETRY_DELAY_MS;
468
+ while (!signal.aborted && consecutiveRetries < MAX_CONSECUTIVE_RETRIES) {
469
+ let receivedEvent = false;
470
+ try {
471
+ const response = await this.client.fetch(
472
+ streamPathAt(path, streamIndex),
473
+ {
474
+ cache: "no-store",
475
+ signal
476
+ }
477
+ );
478
+ if (!response.ok || response.body === null) {
479
+ await response.body?.cancel().catch(() => {
480
+ });
481
+ throw new Error(`Child stream returned ${response.status}.`);
482
+ }
483
+ for await (const rawEvent of readNdjsonStream(response.body)) {
484
+ if (signal.aborted) return;
485
+ receivedEvent = true;
486
+ streamIndex += 1;
487
+ const event = parseChildStreamEvent(rawEvent);
488
+ if (event.type === "session.boundary") return;
489
+ if (event.type === "subagent.called") {
490
+ this.beginPath(event.childStreamPath);
491
+ continue;
492
+ }
493
+ if (event.type !== "action.result") continue;
494
+ this.handlers.onToolResult?.(event.result);
495
+ if (event.hasOutput) {
496
+ this.handlers.onActionResult?.(event.result.output);
497
+ }
498
+ }
499
+ } catch {
500
+ if (signal.aborted) return;
501
+ }
502
+ consecutiveRetries = receivedEvent ? 0 : consecutiveRetries + 1;
503
+ retryDelayMs = receivedEvent ? INITIAL_RETRY_DELAY_MS : Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
504
+ await abortableDelay(retryDelayMs, signal);
505
+ }
506
+ }
507
+ };
508
+
312
509
  // src/runtime/tool-ui.ts
313
510
  var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
314
511
  var AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION = "webless.structured-tool-input.v1";
@@ -322,7 +519,7 @@ function formatAgentStructuredToolInput(surface, values) {
322
519
  };
323
520
  return `${AGENT_STRUCTURED_TOOL_INPUT_HEADER} ${JSON.stringify(payload)}`;
324
521
  }
325
- function isRecord2(value) {
522
+ function isRecord3(value) {
326
523
  return value !== null && typeof value === "object" && !Array.isArray(value);
327
524
  }
328
525
  function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
@@ -371,7 +568,7 @@ function isFieldKind(value) {
371
568
  ].includes(value);
372
569
  }
373
570
  function parseField(value) {
374
- if (!isRecord2(value)) return null;
571
+ if (!isRecord3(value)) return null;
375
572
  if (!hasOnlyKeys(value, [
376
573
  "description",
377
574
  "kind",
@@ -422,7 +619,7 @@ function parseField(value) {
422
619
  if (!Array.isArray(value.options) || value.options.length > 100)
423
620
  return null;
424
621
  for (const option of value.options) {
425
- if (!isRecord2(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
622
+ if (!isRecord3(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
426
623
  return null;
427
624
  }
428
625
  }
@@ -445,7 +642,7 @@ function parseField(value) {
445
642
  };
446
643
  }
447
644
  function parseStep(value) {
448
- if (!isRecord2(value)) return null;
645
+ if (!isRecord3(value)) return null;
449
646
  if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
450
647
  return null;
451
648
  }
@@ -465,7 +662,7 @@ function parseStep(value) {
465
662
  };
466
663
  }
467
664
  function parseAction(value) {
468
- if (!isRecord2(value)) return null;
665
+ if (!isRecord3(value)) return null;
469
666
  if (!hasOnlyKeys(value, ["id", "label"])) return null;
470
667
  const label = boundedString(value.label, 80);
471
668
  if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
@@ -477,7 +674,7 @@ function parseAction(value) {
477
674
  };
478
675
  }
479
676
  function parseAgentToolUiSurface(value) {
480
- if (!isRecord2(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
677
+ if (!isRecord3(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
481
678
  return null;
482
679
  if (!hasOnlyKeys(value, [
483
680
  "actions",
@@ -515,7 +712,7 @@ function parseAgentToolUiSurface(value) {
515
712
  if (value.operationId !== void 0 && !operationId) return null;
516
713
  if (value.requestId !== void 0 && !requestId) return null;
517
714
  if (value.submitLabel !== void 0 && !submitLabel) return null;
518
- const values = value.values !== void 0 && isRecord2(value.values) ? value.values : void 0;
715
+ const values = value.values !== void 0 && isRecord3(value.values) ? value.values : void 0;
519
716
  if (value.values !== void 0) {
520
717
  if (!values || !Object.values(values).every((item) => isJsonValue(item)))
521
718
  return null;
@@ -781,11 +978,14 @@ var AgentSession = class {
781
978
  clientHost;
782
979
  session;
783
980
  activeResponse;
981
+ childStreams;
784
982
  capability;
785
983
  getActiveSessionId() {
786
984
  return this.session?.state.sessionId ?? loadPersistedAgentSession(this.visitorSessionId, this.storeOptions)?.sessionId;
787
985
  }
788
986
  reset() {
987
+ this.childStreams?.abortAll();
988
+ this.childStreams = void 0;
789
989
  if (this.activeResponse) {
790
990
  void this.activeResponse.cancel().catch(() => {
791
991
  });
@@ -881,6 +1081,12 @@ var AgentSession = class {
881
1081
  this.persistSessionCursor(session);
882
1082
  }
883
1083
  this.activeResponse = response;
1084
+ const childStreams = new SubagentChildStreamCoordinator(
1085
+ client,
1086
+ handlers,
1087
+ signal
1088
+ );
1089
+ this.childStreams = childStreams;
884
1090
  let streamIndex = session?.state.streamIndex ?? 0;
885
1091
  let rendered = "";
886
1092
  const workItems = /* @__PURE__ */ new Map();
@@ -888,6 +1094,7 @@ var AgentSession = class {
888
1094
  try {
889
1095
  for await (const event of response) {
890
1096
  if (signal.aborted) break;
1097
+ if (event.type === "subagent.called") childStreams.begin(event);
891
1098
  if (event.type === "input.requested") requestedInput = true;
892
1099
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
893
1100
  streamIndex += 1;
@@ -900,8 +1107,11 @@ var AgentSession = class {
900
1107
  );
901
1108
  }
902
1109
  }
1110
+ await childStreams.waitForAll();
903
1111
  } finally {
1112
+ childStreams.abortAll();
904
1113
  this.activeResponse = void 0;
1114
+ if (this.childStreams === childStreams) this.childStreams = void 0;
905
1115
  if (session) {
906
1116
  this.persistSessionCursor(session);
907
1117
  }
@@ -937,63 +1147,80 @@ var AgentSession = class {
937
1147
  }
938
1148
  let rendered = renderTurn(turnEvents);
939
1149
  const workItems = /* @__PURE__ */ new Map();
940
- for (const event of turnEvents) {
941
- applyWorkEvent(event, handlers, workItems);
942
- emitVisitorInteractionEvent(event, handlers);
943
- }
944
- if (rendered.startsWith(initialText)) {
945
- const missedText = rendered.slice(initialText.length);
946
- if (missedText) handlers.onDelta(missedText);
947
- } else if (initialText.startsWith(rendered)) {
948
- rendered = initialText;
949
- } else if (!initialText.startsWith(rendered)) {
950
- rendered = initialText + rendered;
951
- }
952
- let session = client.sessions.attach(snapshot.session.sessionId, {
953
- streamIndex: snapshot.session.streamIndex
954
- });
955
- this.session = session;
956
- this.persistSessionCursor(session);
957
- let snapshotBoundary;
958
- for (let index = turnEvents.length - 1; index >= 0; index -= 1) {
959
- const event = turnEvents[index];
960
- if (event && isTurnBoundary(event)) {
961
- snapshotBoundary = event;
962
- break;
1150
+ const childStreams = new SubagentChildStreamCoordinator(
1151
+ client,
1152
+ handlers,
1153
+ signal
1154
+ );
1155
+ this.childStreams = childStreams;
1156
+ try {
1157
+ for (const event of turnEvents) {
1158
+ if (event.type === "subagent.called") childStreams.begin(event);
1159
+ applyWorkEvent(event, handlers, workItems);
1160
+ emitVisitorInteractionEvent(event, handlers);
963
1161
  }
964
- }
965
- if (snapshotBoundary) {
966
- if (snapshotBoundary.type === "session.failed") {
967
- throw new Error(
968
- snapshotBoundary.data.message || snapshotBoundary.data.code
1162
+ if (rendered.startsWith(initialText)) {
1163
+ const missedText = rendered.slice(initialText.length);
1164
+ if (missedText) handlers.onDelta(missedText);
1165
+ } else if (initialText.startsWith(rendered)) {
1166
+ rendered = initialText;
1167
+ } else if (!initialText.startsWith(rendered)) {
1168
+ rendered = initialText + rendered;
1169
+ }
1170
+ let session = client.sessions.attach(snapshot.session.sessionId, {
1171
+ streamIndex: snapshot.session.streamIndex
1172
+ });
1173
+ this.session = session;
1174
+ this.persistSessionCursor(session);
1175
+ let snapshotBoundary;
1176
+ for (let index = turnEvents.length - 1; index >= 0; index -= 1) {
1177
+ const event = turnEvents[index];
1178
+ if (event && isTurnBoundary(event)) {
1179
+ snapshotBoundary = event;
1180
+ break;
1181
+ }
1182
+ }
1183
+ if (snapshotBoundary) {
1184
+ if (snapshotBoundary.type === "session.failed") {
1185
+ throw new Error(
1186
+ snapshotBoundary.data.message || snapshotBoundary.data.code
1187
+ );
1188
+ }
1189
+ handlers.onComplete?.();
1190
+ await childStreams.waitForAll();
1191
+ if (!rendered.trim() && !hasInputRequest) {
1192
+ throw new Error("Empty response from runtime");
1193
+ }
1194
+ return rendered.trim();
1195
+ }
1196
+ let streamIndex = snapshot.session.streamIndex;
1197
+ for await (const event of session.stream({ signal })) {
1198
+ if (signal.aborted) break;
1199
+ if (event.type === "subagent.called") childStreams.begin(event);
1200
+ rendered = applyMessageEvent(event, rendered, handlers, workItems);
1201
+ streamIndex += 1;
1202
+ savePersistedAgentSession(
1203
+ this.visitorSessionId,
1204
+ session.state.sessionId,
1205
+ streamIndex,
1206
+ this.storeOptions
969
1207
  );
1208
+ if (isTurnBoundary(event)) break;
970
1209
  }
971
- handlers.onComplete?.();
972
- if (!rendered.trim() && !hasInputRequest) {
1210
+ await childStreams.waitForAll();
1211
+ session = client.sessions.attach(session.state.sessionId, {
1212
+ streamIndex
1213
+ });
1214
+ this.session = session;
1215
+ this.persistSessionCursor(session);
1216
+ if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
973
1217
  throw new Error("Empty response from runtime");
974
1218
  }
975
1219
  return rendered.trim();
1220
+ } finally {
1221
+ childStreams.abortAll();
1222
+ if (this.childStreams === childStreams) this.childStreams = void 0;
976
1223
  }
977
- let streamIndex = snapshot.session.streamIndex;
978
- for await (const event of session.stream({ signal })) {
979
- if (signal.aborted) break;
980
- rendered = applyMessageEvent(event, rendered, handlers, workItems);
981
- streamIndex += 1;
982
- savePersistedAgentSession(
983
- this.visitorSessionId,
984
- session.state.sessionId,
985
- streamIndex,
986
- this.storeOptions
987
- );
988
- if (isTurnBoundary(event)) break;
989
- }
990
- session = client.sessions.attach(session.state.sessionId, { streamIndex });
991
- this.session = session;
992
- this.persistSessionCursor(session);
993
- if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
994
- throw new Error("Empty response from runtime");
995
- }
996
- return rendered.trim();
997
1224
  }
998
1225
  async respondTurn(responses, signal, handlers) {
999
1226
  const client = this.ensureClient();
@@ -1014,6 +1241,12 @@ var AgentSession = class {
1014
1241
  () => session.respond(inputResponses, { signal })
1015
1242
  );
1016
1243
  this.activeResponse = response;
1244
+ const childStreams = new SubagentChildStreamCoordinator(
1245
+ client,
1246
+ handlers,
1247
+ signal
1248
+ );
1249
+ this.childStreams = childStreams;
1017
1250
  let streamIndex = session.state.streamIndex;
1018
1251
  let rendered = "";
1019
1252
  let requestedInput = false;
@@ -1021,6 +1254,7 @@ var AgentSession = class {
1021
1254
  try {
1022
1255
  for await (const event of response) {
1023
1256
  if (signal.aborted) break;
1257
+ if (event.type === "subagent.called") childStreams.begin(event);
1024
1258
  if (event.type === "input.requested") requestedInput = true;
1025
1259
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
1026
1260
  streamIndex += 1;
@@ -1031,8 +1265,11 @@ var AgentSession = class {
1031
1265
  this.storeOptions
1032
1266
  );
1033
1267
  }
1268
+ await childStreams.waitForAll();
1034
1269
  } finally {
1270
+ childStreams.abortAll();
1035
1271
  this.activeResponse = void 0;
1272
+ if (this.childStreams === childStreams) this.childStreams = void 0;
1036
1273
  this.session = client.sessions.attach(session.state.sessionId, {
1037
1274
  streamIndex
1038
1275
  });
@@ -1044,6 +1281,7 @@ var AgentSession = class {
1044
1281
  return rendered.trim();
1045
1282
  }
1046
1283
  cancelActive() {
1284
+ this.childStreams?.abortAll();
1047
1285
  if (this.activeResponse) {
1048
1286
  this.activeResponse.cancel().catch(() => {
1049
1287
  });
@@ -1159,7 +1397,7 @@ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
1159
1397
  "presentationKinds",
1160
1398
  "ui"
1161
1399
  ]);
1162
- function isRecord3(value) {
1400
+ function isRecord4(value) {
1163
1401
  return value !== null && typeof value === "object" && !Array.isArray(value);
1164
1402
  }
1165
1403
  function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
@@ -1186,7 +1424,7 @@ function decodeEnvelope(value) {
1186
1424
  }
1187
1425
  function parseAgentToolResultEnvelope(value) {
1188
1426
  const decoded = decodeEnvelope(value);
1189
- if (!isRecord3(decoded)) return null;
1427
+ if (!isRecord4(decoded)) return null;
1190
1428
  const keys = Object.keys(decoded);
1191
1429
  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) {
1192
1430
  return null;