@webless/agent 0.6.7 → 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-U5IPERNA.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;
@@ -552,8 +749,7 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
552
749
  if (event.type === "message.completed") {
553
750
  handlers.onComplete?.();
554
751
  }
555
- if (event.type === "action.result") emitActionResult(event, handlers);
556
- if (event.type === "input.requested") emitInputRequests(event, handlers);
752
+ emitVisitorInteractionEvent(event, handlers);
557
753
  if (event.type !== "message.appended") return rendered;
558
754
  const { messageDelta, messageSoFar } = event.data;
559
755
  let delta = messageDelta;
@@ -603,6 +799,13 @@ function emitInputRequests(event, handlers) {
603
799
  })
604
800
  );
605
801
  }
802
+ function emitVisitorInteractionEvent(event, handlers) {
803
+ if (event.type === "action.result") emitActionResult(event, handlers);
804
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
805
+ if (event.type === "subagent.event") {
806
+ emitVisitorInteractionEvent(event.data.event, handlers);
807
+ }
808
+ }
606
809
  function isResumeTurnMessage(received, candidate) {
607
810
  if (received === candidate) return true;
608
811
  return Boolean(candidate) && received.endsWith(`
@@ -775,11 +978,14 @@ var AgentSession = class {
775
978
  clientHost;
776
979
  session;
777
980
  activeResponse;
981
+ childStreams;
778
982
  capability;
779
983
  getActiveSessionId() {
780
984
  return this.session?.state.sessionId ?? loadPersistedAgentSession(this.visitorSessionId, this.storeOptions)?.sessionId;
781
985
  }
782
986
  reset() {
987
+ this.childStreams?.abortAll();
988
+ this.childStreams = void 0;
783
989
  if (this.activeResponse) {
784
990
  void this.activeResponse.cancel().catch(() => {
785
991
  });
@@ -875,6 +1081,12 @@ var AgentSession = class {
875
1081
  this.persistSessionCursor(session);
876
1082
  }
877
1083
  this.activeResponse = response;
1084
+ const childStreams = new SubagentChildStreamCoordinator(
1085
+ client,
1086
+ handlers,
1087
+ signal
1088
+ );
1089
+ this.childStreams = childStreams;
878
1090
  let streamIndex = session?.state.streamIndex ?? 0;
879
1091
  let rendered = "";
880
1092
  const workItems = /* @__PURE__ */ new Map();
@@ -882,6 +1094,7 @@ var AgentSession = class {
882
1094
  try {
883
1095
  for await (const event of response) {
884
1096
  if (signal.aborted) break;
1097
+ if (event.type === "subagent.called") childStreams.begin(event);
885
1098
  if (event.type === "input.requested") requestedInput = true;
886
1099
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
887
1100
  streamIndex += 1;
@@ -894,8 +1107,11 @@ var AgentSession = class {
894
1107
  );
895
1108
  }
896
1109
  }
1110
+ await childStreams.waitForAll();
897
1111
  } finally {
1112
+ childStreams.abortAll();
898
1113
  this.activeResponse = void 0;
1114
+ if (this.childStreams === childStreams) this.childStreams = void 0;
899
1115
  if (session) {
900
1116
  this.persistSessionCursor(session);
901
1117
  }
@@ -931,64 +1147,80 @@ var AgentSession = class {
931
1147
  }
932
1148
  let rendered = renderTurn(turnEvents);
933
1149
  const workItems = /* @__PURE__ */ new Map();
934
- for (const event of turnEvents) {
935
- applyWorkEvent(event, handlers, workItems);
936
- if (event.type === "input.requested") emitInputRequests(event, handlers);
937
- if (event.type === "action.result") emitActionResult(event, handlers);
938
- }
939
- if (rendered.startsWith(initialText)) {
940
- const missedText = rendered.slice(initialText.length);
941
- if (missedText) handlers.onDelta(missedText);
942
- } else if (initialText.startsWith(rendered)) {
943
- rendered = initialText;
944
- } else if (!initialText.startsWith(rendered)) {
945
- rendered = initialText + rendered;
946
- }
947
- let session = client.sessions.attach(snapshot.session.sessionId, {
948
- streamIndex: snapshot.session.streamIndex
949
- });
950
- this.session = session;
951
- this.persistSessionCursor(session);
952
- let snapshotBoundary;
953
- for (let index = turnEvents.length - 1; index >= 0; index -= 1) {
954
- const event = turnEvents[index];
955
- if (event && isTurnBoundary(event)) {
956
- snapshotBoundary = event;
957
- 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);
958
1161
  }
959
- }
960
- if (snapshotBoundary) {
961
- if (snapshotBoundary.type === "session.failed") {
962
- throw new Error(
963
- 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
964
1207
  );
1208
+ if (isTurnBoundary(event)) break;
965
1209
  }
966
- handlers.onComplete?.();
967
- 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) {
968
1217
  throw new Error("Empty response from runtime");
969
1218
  }
970
1219
  return rendered.trim();
1220
+ } finally {
1221
+ childStreams.abortAll();
1222
+ if (this.childStreams === childStreams) this.childStreams = void 0;
971
1223
  }
972
- let streamIndex = snapshot.session.streamIndex;
973
- for await (const event of session.stream({ signal })) {
974
- if (signal.aborted) break;
975
- rendered = applyMessageEvent(event, rendered, handlers, workItems);
976
- streamIndex += 1;
977
- savePersistedAgentSession(
978
- this.visitorSessionId,
979
- session.state.sessionId,
980
- streamIndex,
981
- this.storeOptions
982
- );
983
- if (isTurnBoundary(event)) break;
984
- }
985
- session = client.sessions.attach(session.state.sessionId, { streamIndex });
986
- this.session = session;
987
- this.persistSessionCursor(session);
988
- if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
989
- throw new Error("Empty response from runtime");
990
- }
991
- return rendered.trim();
992
1224
  }
993
1225
  async respondTurn(responses, signal, handlers) {
994
1226
  const client = this.ensureClient();
@@ -1009,6 +1241,12 @@ var AgentSession = class {
1009
1241
  () => session.respond(inputResponses, { signal })
1010
1242
  );
1011
1243
  this.activeResponse = response;
1244
+ const childStreams = new SubagentChildStreamCoordinator(
1245
+ client,
1246
+ handlers,
1247
+ signal
1248
+ );
1249
+ this.childStreams = childStreams;
1012
1250
  let streamIndex = session.state.streamIndex;
1013
1251
  let rendered = "";
1014
1252
  let requestedInput = false;
@@ -1016,6 +1254,7 @@ var AgentSession = class {
1016
1254
  try {
1017
1255
  for await (const event of response) {
1018
1256
  if (signal.aborted) break;
1257
+ if (event.type === "subagent.called") childStreams.begin(event);
1019
1258
  if (event.type === "input.requested") requestedInput = true;
1020
1259
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
1021
1260
  streamIndex += 1;
@@ -1026,8 +1265,11 @@ var AgentSession = class {
1026
1265
  this.storeOptions
1027
1266
  );
1028
1267
  }
1268
+ await childStreams.waitForAll();
1029
1269
  } finally {
1270
+ childStreams.abortAll();
1030
1271
  this.activeResponse = void 0;
1272
+ if (this.childStreams === childStreams) this.childStreams = void 0;
1031
1273
  this.session = client.sessions.attach(session.state.sessionId, {
1032
1274
  streamIndex
1033
1275
  });
@@ -1039,6 +1281,7 @@ var AgentSession = class {
1039
1281
  return rendered.trim();
1040
1282
  }
1041
1283
  cancelActive() {
1284
+ this.childStreams?.abortAll();
1042
1285
  if (this.activeResponse) {
1043
1286
  this.activeResponse.cancel().catch(() => {
1044
1287
  });
@@ -1154,7 +1397,7 @@ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
1154
1397
  "presentationKinds",
1155
1398
  "ui"
1156
1399
  ]);
1157
- function isRecord3(value) {
1400
+ function isRecord4(value) {
1158
1401
  return value !== null && typeof value === "object" && !Array.isArray(value);
1159
1402
  }
1160
1403
  function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
@@ -1181,7 +1424,7 @@ function decodeEnvelope(value) {
1181
1424
  }
1182
1425
  function parseAgentToolResultEnvelope(value) {
1183
1426
  const decoded = decodeEnvelope(value);
1184
- if (!isRecord3(decoded)) return null;
1427
+ if (!isRecord4(decoded)) return null;
1185
1428
  const keys = Object.keys(decoded);
1186
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) {
1187
1430
  return null;