@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/index.js CHANGED
@@ -268,6 +268,203 @@ function clearPersistedAgentSession(visitorSessionId, options) {
268
268
  sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
269
269
  }
270
270
 
271
+ // src/runtime/subagent-child-stream.ts
272
+ var INITIAL_RETRY_DELAY_MS = 100;
273
+ var MAX_RETRY_DELAY_MS = 2e3;
274
+ var MAX_CONSECUTIVE_RETRIES = 6;
275
+ function isRecord2(value) {
276
+ return typeof value === "object" && value !== null && !Array.isArray(value);
277
+ }
278
+ function parseError(value) {
279
+ if (!isRecord2(value)) return void 0;
280
+ const { code, message } = value;
281
+ if (typeof code !== "string" || typeof message !== "string") {
282
+ return void 0;
283
+ }
284
+ return { code, message };
285
+ }
286
+ function parseChildStreamEvent(value) {
287
+ if (!isRecord2(value) || typeof value.type !== "string") {
288
+ return { type: "other" };
289
+ }
290
+ if (value.type === "session.waiting" || value.type === "session.completed" || value.type === "session.failed") {
291
+ return { type: "session.boundary" };
292
+ }
293
+ if (value.type === "subagent.event" && isRecord2(value.data) && Object.hasOwn(value.data, "event")) {
294
+ return parseChildStreamEvent(value.data.event);
295
+ }
296
+ if (value.type === "subagent.called" && isRecord2(value.data)) {
297
+ const { childStreamPath } = value.data;
298
+ if (typeof childStreamPath === "string") {
299
+ return { childStreamPath, type: "subagent.called" };
300
+ }
301
+ }
302
+ if (value.type !== "action.result" || !isRecord2(value.data)) {
303
+ return { type: "other" };
304
+ }
305
+ const { data } = value;
306
+ if (data.status !== "completed" && data.status !== "failed" && data.status !== "rejected") {
307
+ return { type: "other" };
308
+ }
309
+ if (!isRecord2(data.result) || data.result.kind !== "tool-result") {
310
+ return { type: "other" };
311
+ }
312
+ const result = data.result;
313
+ if (typeof result.callId !== "string" || typeof result.toolName !== "string") {
314
+ return { type: "other" };
315
+ }
316
+ const error = parseError(data.error);
317
+ return {
318
+ type: "action.result",
319
+ hasOutput: Object.hasOwn(result, "output"),
320
+ result: {
321
+ callId: result.callId,
322
+ toolName: result.toolName,
323
+ status: data.status,
324
+ ...Object.hasOwn(result, "output") ? { output: result.output } : {},
325
+ ...error ? { error } : {}
326
+ }
327
+ };
328
+ }
329
+ async function* readNdjsonStream(body) {
330
+ const reader = body.getReader();
331
+ const decoder = new TextDecoder();
332
+ let buffer = "";
333
+ try {
334
+ while (true) {
335
+ const { done, value } = await reader.read();
336
+ buffer += decoder.decode(value, { stream: !done });
337
+ const lines = buffer.split("\n");
338
+ buffer = lines.pop() ?? "";
339
+ for (const line of lines) {
340
+ const trimmed2 = line.trim();
341
+ if (!trimmed2) continue;
342
+ try {
343
+ const parsed = JSON.parse(trimmed2);
344
+ yield parsed;
345
+ } catch {
346
+ }
347
+ }
348
+ if (done) break;
349
+ }
350
+ const trimmed = buffer.trim();
351
+ if (trimmed) {
352
+ try {
353
+ const parsed = JSON.parse(trimmed);
354
+ yield parsed;
355
+ } catch {
356
+ }
357
+ }
358
+ } finally {
359
+ reader.releaseLock();
360
+ }
361
+ }
362
+ function streamPathAt(path, streamIndex) {
363
+ if (streamIndex === 0) return path;
364
+ return `${path}${path.includes("?") ? "&" : "?"}startIndex=${streamIndex}`;
365
+ }
366
+ function abortableDelay(delayMs, signal) {
367
+ if (signal.aborted) return Promise.resolve();
368
+ return new Promise((resolve) => {
369
+ const finish = () => {
370
+ clearTimeout(timeout);
371
+ signal.removeEventListener("abort", finish);
372
+ resolve();
373
+ };
374
+ const timeout = setTimeout(finish, delayMs);
375
+ signal.addEventListener("abort", finish, { once: true });
376
+ });
377
+ }
378
+ var SubagentChildStreamCoordinator = class {
379
+ constructor(client, handlers, parentSignal) {
380
+ this.client = client;
381
+ this.handlers = handlers;
382
+ this.parentSignal = parentSignal;
383
+ }
384
+ client;
385
+ handlers;
386
+ parentSignal;
387
+ controllers = /* @__PURE__ */ new Map();
388
+ tasks = /* @__PURE__ */ new Map();
389
+ begin(event) {
390
+ this.beginPath(event.data.childStreamPath);
391
+ }
392
+ async waitForAll() {
393
+ let observedTaskCount = -1;
394
+ while (observedTaskCount !== this.tasks.size) {
395
+ observedTaskCount = this.tasks.size;
396
+ await Promise.all(this.tasks.values());
397
+ }
398
+ }
399
+ abortAll() {
400
+ for (const controller of this.controllers.values()) controller.abort();
401
+ this.controllers.clear();
402
+ }
403
+ beginPath(childStreamPath) {
404
+ if (this.tasks.has(childStreamPath)) return;
405
+ const controller = new AbortController();
406
+ const abort = () => controller.abort();
407
+ if (this.parentSignal.aborted) {
408
+ controller.abort();
409
+ } else {
410
+ this.parentSignal.addEventListener("abort", abort, { once: true });
411
+ }
412
+ this.controllers.set(childStreamPath, controller);
413
+ const task = this.consume(childStreamPath, controller.signal).finally(
414
+ () => {
415
+ this.parentSignal.removeEventListener("abort", abort);
416
+ if (this.controllers.get(childStreamPath) === controller) {
417
+ this.controllers.delete(childStreamPath);
418
+ }
419
+ }
420
+ );
421
+ this.tasks.set(childStreamPath, task);
422
+ }
423
+ async consume(path, signal) {
424
+ let streamIndex = 0;
425
+ let consecutiveRetries = 0;
426
+ let retryDelayMs = INITIAL_RETRY_DELAY_MS;
427
+ while (!signal.aborted && consecutiveRetries < MAX_CONSECUTIVE_RETRIES) {
428
+ let receivedEvent = false;
429
+ try {
430
+ const response = await this.client.fetch(
431
+ streamPathAt(path, streamIndex),
432
+ {
433
+ cache: "no-store",
434
+ signal
435
+ }
436
+ );
437
+ if (!response.ok || response.body === null) {
438
+ await response.body?.cancel().catch(() => {
439
+ });
440
+ throw new Error(`Child stream returned ${response.status}.`);
441
+ }
442
+ for await (const rawEvent of readNdjsonStream(response.body)) {
443
+ if (signal.aborted) return;
444
+ receivedEvent = true;
445
+ streamIndex += 1;
446
+ const event = parseChildStreamEvent(rawEvent);
447
+ if (event.type === "session.boundary") return;
448
+ if (event.type === "subagent.called") {
449
+ this.beginPath(event.childStreamPath);
450
+ continue;
451
+ }
452
+ if (event.type !== "action.result") continue;
453
+ this.handlers.onToolResult?.(event.result);
454
+ if (event.hasOutput) {
455
+ this.handlers.onActionResult?.(event.result.output);
456
+ }
457
+ }
458
+ } catch {
459
+ if (signal.aborted) return;
460
+ }
461
+ consecutiveRetries = receivedEvent ? 0 : consecutiveRetries + 1;
462
+ retryDelayMs = receivedEvent ? INITIAL_RETRY_DELAY_MS : Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
463
+ await abortableDelay(retryDelayMs, signal);
464
+ }
465
+ }
466
+ };
467
+
271
468
  // src/runtime/tool-ui.ts
272
469
  var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
273
470
  var AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION = "webless.structured-tool-input.v1";
@@ -281,7 +478,7 @@ function formatAgentStructuredToolInput(surface, values) {
281
478
  };
282
479
  return `${AGENT_STRUCTURED_TOOL_INPUT_HEADER} ${JSON.stringify(payload)}`;
283
480
  }
284
- function isRecord2(value) {
481
+ function isRecord3(value) {
285
482
  return value !== null && typeof value === "object" && !Array.isArray(value);
286
483
  }
287
484
  function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
@@ -330,7 +527,7 @@ function isFieldKind(value) {
330
527
  ].includes(value);
331
528
  }
332
529
  function parseField(value) {
333
- if (!isRecord2(value)) return null;
530
+ if (!isRecord3(value)) return null;
334
531
  if (!hasOnlyKeys(value, [
335
532
  "description",
336
533
  "kind",
@@ -381,7 +578,7 @@ function parseField(value) {
381
578
  if (!Array.isArray(value.options) || value.options.length > 100)
382
579
  return null;
383
580
  for (const option of value.options) {
384
- if (!isRecord2(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
581
+ if (!isRecord3(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
385
582
  return null;
386
583
  }
387
584
  }
@@ -404,7 +601,7 @@ function parseField(value) {
404
601
  };
405
602
  }
406
603
  function parseStep(value) {
407
- if (!isRecord2(value)) return null;
604
+ if (!isRecord3(value)) return null;
408
605
  if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
409
606
  return null;
410
607
  }
@@ -424,7 +621,7 @@ function parseStep(value) {
424
621
  };
425
622
  }
426
623
  function parseAction(value) {
427
- if (!isRecord2(value)) return null;
624
+ if (!isRecord3(value)) return null;
428
625
  if (!hasOnlyKeys(value, ["id", "label"])) return null;
429
626
  const label = boundedString(value.label, 80);
430
627
  if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
@@ -436,7 +633,7 @@ function parseAction(value) {
436
633
  };
437
634
  }
438
635
  function parseAgentToolUiSurface(value) {
439
- if (!isRecord2(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
636
+ if (!isRecord3(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
440
637
  return null;
441
638
  if (!hasOnlyKeys(value, [
442
639
  "actions",
@@ -474,7 +671,7 @@ function parseAgentToolUiSurface(value) {
474
671
  if (value.operationId !== void 0 && !operationId) return null;
475
672
  if (value.requestId !== void 0 && !requestId) return null;
476
673
  if (value.submitLabel !== void 0 && !submitLabel) return null;
477
- const values = value.values !== void 0 && isRecord2(value.values) ? value.values : void 0;
674
+ const values = value.values !== void 0 && isRecord3(value.values) ? value.values : void 0;
478
675
  if (value.values !== void 0) {
479
676
  if (!values || !Object.values(values).every((item) => isJsonValue(item)))
480
677
  return null;
@@ -740,11 +937,14 @@ var AgentSession = class {
740
937
  clientHost;
741
938
  session;
742
939
  activeResponse;
940
+ childStreams;
743
941
  capability;
744
942
  getActiveSessionId() {
745
943
  return this.session?.state.sessionId ?? loadPersistedAgentSession(this.visitorSessionId, this.storeOptions)?.sessionId;
746
944
  }
747
945
  reset() {
946
+ this.childStreams?.abortAll();
947
+ this.childStreams = void 0;
748
948
  if (this.activeResponse) {
749
949
  void this.activeResponse.cancel().catch(() => {
750
950
  });
@@ -840,6 +1040,12 @@ var AgentSession = class {
840
1040
  this.persistSessionCursor(session);
841
1041
  }
842
1042
  this.activeResponse = response;
1043
+ const childStreams = new SubagentChildStreamCoordinator(
1044
+ client,
1045
+ handlers,
1046
+ signal
1047
+ );
1048
+ this.childStreams = childStreams;
843
1049
  let streamIndex = session?.state.streamIndex ?? 0;
844
1050
  let rendered = "";
845
1051
  const workItems = /* @__PURE__ */ new Map();
@@ -847,6 +1053,7 @@ var AgentSession = class {
847
1053
  try {
848
1054
  for await (const event of response) {
849
1055
  if (signal.aborted) break;
1056
+ if (event.type === "subagent.called") childStreams.begin(event);
850
1057
  if (event.type === "input.requested") requestedInput = true;
851
1058
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
852
1059
  streamIndex += 1;
@@ -859,8 +1066,11 @@ var AgentSession = class {
859
1066
  );
860
1067
  }
861
1068
  }
1069
+ await childStreams.waitForAll();
862
1070
  } finally {
1071
+ childStreams.abortAll();
863
1072
  this.activeResponse = void 0;
1073
+ if (this.childStreams === childStreams) this.childStreams = void 0;
864
1074
  if (session) {
865
1075
  this.persistSessionCursor(session);
866
1076
  }
@@ -896,63 +1106,80 @@ var AgentSession = class {
896
1106
  }
897
1107
  let rendered = renderTurn(turnEvents);
898
1108
  const workItems = /* @__PURE__ */ new Map();
899
- for (const event of turnEvents) {
900
- applyWorkEvent(event, handlers, workItems);
901
- emitVisitorInteractionEvent(event, handlers);
902
- }
903
- if (rendered.startsWith(initialText)) {
904
- const missedText = rendered.slice(initialText.length);
905
- if (missedText) handlers.onDelta(missedText);
906
- } else if (initialText.startsWith(rendered)) {
907
- rendered = initialText;
908
- } else if (!initialText.startsWith(rendered)) {
909
- rendered = initialText + rendered;
910
- }
911
- let session = client.sessions.attach(snapshot.session.sessionId, {
912
- streamIndex: snapshot.session.streamIndex
913
- });
914
- this.session = session;
915
- this.persistSessionCursor(session);
916
- let snapshotBoundary;
917
- for (let index = turnEvents.length - 1; index >= 0; index -= 1) {
918
- const event = turnEvents[index];
919
- if (event && isTurnBoundary(event)) {
920
- snapshotBoundary = event;
921
- break;
1109
+ const childStreams = new SubagentChildStreamCoordinator(
1110
+ client,
1111
+ handlers,
1112
+ signal
1113
+ );
1114
+ this.childStreams = childStreams;
1115
+ try {
1116
+ for (const event of turnEvents) {
1117
+ if (event.type === "subagent.called") childStreams.begin(event);
1118
+ applyWorkEvent(event, handlers, workItems);
1119
+ emitVisitorInteractionEvent(event, handlers);
922
1120
  }
923
- }
924
- if (snapshotBoundary) {
925
- if (snapshotBoundary.type === "session.failed") {
926
- throw new Error(
927
- snapshotBoundary.data.message || snapshotBoundary.data.code
1121
+ if (rendered.startsWith(initialText)) {
1122
+ const missedText = rendered.slice(initialText.length);
1123
+ if (missedText) handlers.onDelta(missedText);
1124
+ } else if (initialText.startsWith(rendered)) {
1125
+ rendered = initialText;
1126
+ } else if (!initialText.startsWith(rendered)) {
1127
+ rendered = initialText + rendered;
1128
+ }
1129
+ let session = client.sessions.attach(snapshot.session.sessionId, {
1130
+ streamIndex: snapshot.session.streamIndex
1131
+ });
1132
+ this.session = session;
1133
+ this.persistSessionCursor(session);
1134
+ let snapshotBoundary;
1135
+ for (let index = turnEvents.length - 1; index >= 0; index -= 1) {
1136
+ const event = turnEvents[index];
1137
+ if (event && isTurnBoundary(event)) {
1138
+ snapshotBoundary = event;
1139
+ break;
1140
+ }
1141
+ }
1142
+ if (snapshotBoundary) {
1143
+ if (snapshotBoundary.type === "session.failed") {
1144
+ throw new Error(
1145
+ snapshotBoundary.data.message || snapshotBoundary.data.code
1146
+ );
1147
+ }
1148
+ handlers.onComplete?.();
1149
+ await childStreams.waitForAll();
1150
+ if (!rendered.trim() && !hasInputRequest) {
1151
+ throw new Error("Empty response from runtime");
1152
+ }
1153
+ return rendered.trim();
1154
+ }
1155
+ let streamIndex = snapshot.session.streamIndex;
1156
+ for await (const event of session.stream({ signal })) {
1157
+ if (signal.aborted) break;
1158
+ if (event.type === "subagent.called") childStreams.begin(event);
1159
+ rendered = applyMessageEvent(event, rendered, handlers, workItems);
1160
+ streamIndex += 1;
1161
+ savePersistedAgentSession(
1162
+ this.visitorSessionId,
1163
+ session.state.sessionId,
1164
+ streamIndex,
1165
+ this.storeOptions
928
1166
  );
1167
+ if (isTurnBoundary(event)) break;
929
1168
  }
930
- handlers.onComplete?.();
931
- if (!rendered.trim() && !hasInputRequest) {
1169
+ await childStreams.waitForAll();
1170
+ session = client.sessions.attach(session.state.sessionId, {
1171
+ streamIndex
1172
+ });
1173
+ this.session = session;
1174
+ this.persistSessionCursor(session);
1175
+ if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
932
1176
  throw new Error("Empty response from runtime");
933
1177
  }
934
1178
  return rendered.trim();
1179
+ } finally {
1180
+ childStreams.abortAll();
1181
+ if (this.childStreams === childStreams) this.childStreams = void 0;
935
1182
  }
936
- let streamIndex = snapshot.session.streamIndex;
937
- for await (const event of session.stream({ signal })) {
938
- if (signal.aborted) break;
939
- rendered = applyMessageEvent(event, rendered, handlers, workItems);
940
- streamIndex += 1;
941
- savePersistedAgentSession(
942
- this.visitorSessionId,
943
- session.state.sessionId,
944
- streamIndex,
945
- this.storeOptions
946
- );
947
- if (isTurnBoundary(event)) break;
948
- }
949
- session = client.sessions.attach(session.state.sessionId, { streamIndex });
950
- this.session = session;
951
- this.persistSessionCursor(session);
952
- if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
953
- throw new Error("Empty response from runtime");
954
- }
955
- return rendered.trim();
956
1183
  }
957
1184
  async respondTurn(responses, signal, handlers) {
958
1185
  const client = this.ensureClient();
@@ -973,6 +1200,12 @@ var AgentSession = class {
973
1200
  () => session.respond(inputResponses, { signal })
974
1201
  );
975
1202
  this.activeResponse = response;
1203
+ const childStreams = new SubagentChildStreamCoordinator(
1204
+ client,
1205
+ handlers,
1206
+ signal
1207
+ );
1208
+ this.childStreams = childStreams;
976
1209
  let streamIndex = session.state.streamIndex;
977
1210
  let rendered = "";
978
1211
  let requestedInput = false;
@@ -980,6 +1213,7 @@ var AgentSession = class {
980
1213
  try {
981
1214
  for await (const event of response) {
982
1215
  if (signal.aborted) break;
1216
+ if (event.type === "subagent.called") childStreams.begin(event);
983
1217
  if (event.type === "input.requested") requestedInput = true;
984
1218
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
985
1219
  streamIndex += 1;
@@ -990,8 +1224,11 @@ var AgentSession = class {
990
1224
  this.storeOptions
991
1225
  );
992
1226
  }
1227
+ await childStreams.waitForAll();
993
1228
  } finally {
1229
+ childStreams.abortAll();
994
1230
  this.activeResponse = void 0;
1231
+ if (this.childStreams === childStreams) this.childStreams = void 0;
995
1232
  this.session = client.sessions.attach(session.state.sessionId, {
996
1233
  streamIndex
997
1234
  });
@@ -1003,6 +1240,7 @@ var AgentSession = class {
1003
1240
  return rendered.trim();
1004
1241
  }
1005
1242
  cancelActive() {
1243
+ this.childStreams?.abortAll();
1006
1244
  if (this.activeResponse) {
1007
1245
  this.activeResponse.cancel().catch(() => {
1008
1246
  });
@@ -1118,7 +1356,7 @@ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
1118
1356
  "presentationKinds",
1119
1357
  "ui"
1120
1358
  ]);
1121
- function isRecord3(value) {
1359
+ function isRecord4(value) {
1122
1360
  return value !== null && typeof value === "object" && !Array.isArray(value);
1123
1361
  }
1124
1362
  function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
@@ -1145,7 +1383,7 @@ function decodeEnvelope(value) {
1145
1383
  }
1146
1384
  function parseAgentToolResultEnvelope(value) {
1147
1385
  const decoded = decodeEnvelope(value);
1148
- if (!isRecord3(decoded)) return null;
1386
+ if (!isRecord4(decoded)) return null;
1149
1387
  const keys = Object.keys(decoded);
1150
1388
  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) {
1151
1389
  return null;