langchain_agentx_stream_ui 0.1.8 → 0.2.0

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
@@ -23,7 +23,7 @@ import {
23
23
  fixDiagramSvg,
24
24
  markEventIdSeen,
25
25
  shouldSkipDuplicateEvent
26
- } from "./chunk-WPW3TQQF.js";
26
+ } from "./chunk-2T26L3VT.js";
27
27
  import {
28
28
  DefaultToolBody,
29
29
  InteractionBusContext,
@@ -111,7 +111,7 @@ import {
111
111
  useSessionStatus,
112
112
  useSessionViewOptions,
113
113
  useTimeline
114
- } from "./chunk-LBJFJF22.js";
114
+ } from "./chunk-ZNXAQ4ZZ.js";
115
115
  import {
116
116
  Agent,
117
117
  AgentToolBody,
@@ -768,6 +768,30 @@ function createWorkflowRunningLoopTree(seedMs = Date.now()) {
768
768
  tree.meta.startedAt = seedMs;
769
769
  return tree;
770
770
  }
771
+ function finalizeWorkflowLoopTree(tree, terminalStatus = "done") {
772
+ if (tree.status === "done" || tree.status === "error") {
773
+ return tree;
774
+ }
775
+ const byId = { ...tree.byId };
776
+ for (const [id, node] of Object.entries(byId)) {
777
+ if (!node) continue;
778
+ if ((node.kind === "text" || node.kind === "reasoning") && node.status === "streaming") {
779
+ byId[id] = { ...node, status: "done" };
780
+ }
781
+ if (node.kind === "tool_call" && node.status === "running") {
782
+ byId[id] = {
783
+ ...node,
784
+ status: terminalStatus === "error" ? "failed" : "done"
785
+ };
786
+ }
787
+ }
788
+ return {
789
+ ...tree,
790
+ byId,
791
+ activeRoundId: null,
792
+ status: terminalStatus
793
+ };
794
+ }
771
795
 
772
796
  // src/types/workflowDisplay.ts
773
797
  function readDisplayPayload(data) {
@@ -926,7 +950,7 @@ function contentBlockFromEvent(event) {
926
950
  return null;
927
951
  }
928
952
  function resolveContentScopeKey(data) {
929
- for (const keyName of ["stage_key", "item_key", "branch_key", "debug_key"]) {
953
+ for (const keyName of ["stage_key", "item_key", "branch_key", "aggregate_key", "debug_key"]) {
930
954
  const value = readString(data, keyName);
931
955
  if (value) return value;
932
956
  }
@@ -1145,17 +1169,24 @@ var WorkflowContainerStateMachine = class {
1145
1169
  (node) => workflowPath === node.workflow_path || workflowPath.startsWith(`${node.workflow_path}>`)
1146
1170
  );
1147
1171
  if (prefixCandidates.length > 0) {
1148
- const parent = prefixCandidates.reduce(
1149
- (best, node) => this.active.compareContainerRank(node.container_id, best.container_id) > 0 ? node : best
1150
- );
1151
- if (parent.workflow_path === workflowPath && parent.scope === "workflow") {
1152
- return parent.container_id;
1153
- }
1154
- if (workflowPath.startsWith(`${parent.workflow_path}>`)) {
1155
- return parent.container_id;
1156
- }
1157
- if (parent.workflow_path === workflowPath && parent.scope !== scope) {
1158
- return parent.container_id;
1172
+ const parentCandidates = prefixCandidates.filter((node) => {
1173
+ if (scope === "item") return node.scope !== "item";
1174
+ if (scope === "aggregate") return node.scope !== "item" && node.scope !== "aggregate";
1175
+ return node.scope !== scope;
1176
+ });
1177
+ if (parentCandidates.length > 0) {
1178
+ const parent = parentCandidates.reduce(
1179
+ (best, node) => this.active.compareContainerRank(node.container_id, best.container_id) > 0 ? node : best
1180
+ );
1181
+ if (parent.workflow_path === workflowPath && parent.scope === "workflow") {
1182
+ return parent.container_id;
1183
+ }
1184
+ if (workflowPath.startsWith(`${parent.workflow_path}>`)) {
1185
+ return parent.container_id;
1186
+ }
1187
+ if (parent.workflow_path === workflowPath && parent.scope !== scope) {
1188
+ return parent.container_id;
1189
+ }
1159
1190
  }
1160
1191
  }
1161
1192
  const workflowCandidates = openNodes.filter((node) => node.scope === "workflow");
@@ -1226,6 +1257,7 @@ function readString2(data, key) {
1226
1257
  var LOOP_TASK_OPEN_SCOPES = {
1227
1258
  "stage-start": "stage",
1228
1259
  "parallel-item-start": "item",
1260
+ "parallel-aggregate-start": "aggregate",
1229
1261
  "route-branch-start": "branch"
1230
1262
  };
1231
1263
  function buildLoopSessionId(workflowId, taskKey) {
@@ -1244,7 +1276,7 @@ function deriveLoopSessionIdFromAgentEvent(event) {
1244
1276
  const data = event.data ?? {};
1245
1277
  const workflowId = readString2(data, "workflow_id") ?? readString2(data, "workflow_path");
1246
1278
  if (!workflowId) return null;
1247
- const taskKey = readString2(data, "stage_key") ?? readString2(data, "item_key") ?? readString2(data, "branch_key");
1279
+ const taskKey = readString2(data, "stage_key") ?? readString2(data, "item_key") ?? readString2(data, "branch_key") ?? readString2(data, "aggregate_key");
1248
1280
  if (!taskKey) return null;
1249
1281
  return buildLoopSessionId(workflowId, taskKey);
1250
1282
  }
@@ -1295,11 +1327,426 @@ function filterAgentLoopReplayEvents(events) {
1295
1327
  );
1296
1328
  }
1297
1329
 
1298
- // src/core/workflowReducer.ts
1330
+ // src/core/workflow/workflowPathUtils.ts
1331
+ function splitWorkflowPathSegments(workflowPath) {
1332
+ return workflowPath.split(">").filter(Boolean);
1333
+ }
1334
+ function parentWorkflowPath(workflowPath) {
1335
+ const segments = splitWorkflowPathSegments(workflowPath);
1336
+ if (segments.length <= 1) return null;
1337
+ return segments.slice(0, -1).join(">");
1338
+ }
1339
+ function lastWorkflowPathSegment(workflowPath) {
1340
+ const segments = splitWorkflowPathSegments(workflowPath);
1341
+ return segments.length > 0 ? segments[segments.length - 1] : null;
1342
+ }
1343
+ function isWorkflowPathUnderAncestor(workflowPath, ancestorPath) {
1344
+ if (workflowPath === ancestorPath) return false;
1345
+ const prefix = `${ancestorPath}>`;
1346
+ return workflowPath.startsWith(prefix);
1347
+ }
1348
+ function isWorkflowPathEqualOrUnder(workflowPath, ancestorPath) {
1349
+ return workflowPath === ancestorPath || isWorkflowPathUnderAncestor(workflowPath, ancestorPath);
1350
+ }
1351
+
1352
+ // src/core/workflow/nativeNestedUtils.ts
1299
1353
  function readString3(data, key) {
1300
1354
  const value = data[key];
1301
1355
  return typeof value === "string" ? value : void 0;
1302
1356
  }
1357
+ function readNumber(data, key) {
1358
+ const value = data[key];
1359
+ return typeof value === "number" ? value : void 0;
1360
+ }
1361
+ function isNativeNestedStructureEvent(event) {
1362
+ if (!isWorkflowStructureEventType(event.event_type)) return false;
1363
+ const data = event.data ?? {};
1364
+ const path = readString3(data, "workflow_path") ?? "";
1365
+ const depth = readNumber(data, "workflow_depth") ?? 0;
1366
+ return depth >= 1 && path.includes(">");
1367
+ }
1368
+ function resolveHostStageContainerId(tree, workflowPath) {
1369
+ const parentPath = parentWorkflowPath(workflowPath);
1370
+ const childSegment = lastWorkflowPathSegment(workflowPath);
1371
+ if (!parentPath || !childSegment) return null;
1372
+ const stageId = buildContainerId(parentPath, "stage", childSegment);
1373
+ return tree.containersById[stageId] != null ? stageId : null;
1374
+ }
1375
+ function isHostStageNestedChildPath(tree, workflowPath) {
1376
+ return resolveHostStageContainerId(tree, workflowPath) != null;
1377
+ }
1378
+ function hasNativeNestedStructureInTree(tree) {
1379
+ for (const node of Object.values(tree.containersById)) {
1380
+ if (node.workflow_depth < 1) continue;
1381
+ if (!node.workflow_path.includes(">")) continue;
1382
+ if (isHostStageNestedChildPath(tree, node.workflow_path)) {
1383
+ return true;
1384
+ }
1385
+ }
1386
+ return false;
1387
+ }
1388
+ function clearHostStageLoopSession(tree, hostStageId) {
1389
+ if (!hostStageId) return tree;
1390
+ const hostStage = tree.containersById[hostStageId];
1391
+ if (!hostStage || hostStage.scope !== "stage" || !hostStage.loopSessionId) return tree;
1392
+ return {
1393
+ ...tree,
1394
+ containersById: {
1395
+ ...tree.containersById,
1396
+ [hostStageId]: { ...hostStage, loopSessionId: null }
1397
+ }
1398
+ };
1399
+ }
1400
+ function isNestedHostStageShell(tree, stageNode) {
1401
+ if (stageNode.scope !== "stage") return false;
1402
+ return Object.values(tree.containersById).some(
1403
+ (node) => node.scope === "subworkflow" && node.parent_container_id === stageNode.container_id
1404
+ );
1405
+ }
1406
+ function clearHostStageLoopOnNestedChildOpen(tree, event) {
1407
+ const eventType = event.event_type;
1408
+ if (eventType !== "subworkflow-start" && eventType !== "parallel-item-start") return tree;
1409
+ const data = event.data ?? {};
1410
+ const workflowPath = readString3(data, "workflow_path");
1411
+ if (!workflowPath) return tree;
1412
+ if (eventType === "subworkflow-start") {
1413
+ const childWorkflowId = readString3(data, "child_workflow_id");
1414
+ if (!childWorkflowId) return tree;
1415
+ const subId = buildContainerId(workflowPath, "subworkflow", childWorkflowId);
1416
+ const sub = tree.containersById[subId];
1417
+ const hostStageId = sub?.parent_container_id ?? resolveHostStageContainerId(tree, workflowPath);
1418
+ return clearHostStageLoopSession(tree, hostStageId);
1419
+ }
1420
+ return clearHostStageLoopSession(tree, resolveHostStageContainerId(tree, workflowPath));
1421
+ }
1422
+ function dropStaleEmbeddedPathsOnNativeSubworkflowStart(tree, event) {
1423
+ if (event.event_type !== "subworkflow-start" || !isNativeNestedStructureEvent(event)) {
1424
+ return tree;
1425
+ }
1426
+ const data = event.data ?? {};
1427
+ const nativeChildPath = readString3(data, "workflow_path");
1428
+ if (!nativeChildPath) return tree;
1429
+ const rootAnchor = splitWorkflowPathSegments(nativeChildPath)[0];
1430
+ if (!rootAnchor) return tree;
1431
+ let changed = false;
1432
+ const containersById = { ...tree.containersById };
1433
+ let activeContainerIds = [...tree.activeContainerIds];
1434
+ for (const [id, node] of Object.entries(tree.containersById)) {
1435
+ if (node.workflow_depth < 1) continue;
1436
+ if (!node.workflow_path.startsWith(`${rootAnchor}>`)) continue;
1437
+ if (node.workflow_path === nativeChildPath || isWorkflowPathEqualOrUnder(node.workflow_path, nativeChildPath)) {
1438
+ continue;
1439
+ }
1440
+ if (isHostStageNestedChildPath(tree, node.workflow_path)) continue;
1441
+ delete containersById[id];
1442
+ activeContainerIds = activeContainerIds.filter((cid) => cid !== id);
1443
+ changed = true;
1444
+ }
1445
+ return changed ? { ...tree, containersById, activeContainerIds } : tree;
1446
+ }
1447
+
1448
+ // src/core/workflow/embeddedSubworkflowUtils.ts
1449
+ function parseLoopSessionParts(sessionId) {
1450
+ const trimmed = sessionId.trim();
1451
+ const dash = trimmed.lastIndexOf("-");
1452
+ if (dash <= 0 || dash >= trimmed.length - 1) return null;
1453
+ return {
1454
+ workflowId: trimmed.slice(0, dash),
1455
+ taskKey: trimmed.slice(dash + 1)
1456
+ };
1457
+ }
1458
+ function isEmbeddedChildLoopSession(sessionId, rootWorkflowId) {
1459
+ const parts = parseLoopSessionParts(sessionId);
1460
+ if (!parts) return false;
1461
+ if (parts.workflowId === rootWorkflowId) return false;
1462
+ if (parts.workflowId.startsWith(`${rootWorkflowId}-`)) return false;
1463
+ if (rootWorkflowId.startsWith(`${parts.workflowId}-`)) return false;
1464
+ return parts.workflowId.split("-").length >= 3;
1465
+ }
1466
+ function readWorkflowIdFromData(data) {
1467
+ const value = data.workflow_id;
1468
+ return typeof value === "string" ? value : void 0;
1469
+ }
1470
+ function readStageKeyFromData(data) {
1471
+ const value = data.stage_key;
1472
+ return typeof value === "string" ? value : void 0;
1473
+ }
1474
+ function shouldPrebindLoopSessionFromStructureOpen(eventType, eventSessionId, derivedLoopSessionId, data = {}) {
1475
+ if (!eventSessionId) return true;
1476
+ if (eventType !== "stage-start") return true;
1477
+ if (eventSessionId === derivedLoopSessionId) return true;
1478
+ const rootWorkflowId = readWorkflowIdFromData(data);
1479
+ const stageKey = readStageKeyFromData(data);
1480
+ const parsed = parseLoopSessionParts(eventSessionId);
1481
+ if (rootWorkflowId && stageKey && parsed) {
1482
+ if (parsed.workflowId === rootWorkflowId && parsed.taskKey !== stageKey) {
1483
+ return false;
1484
+ }
1485
+ }
1486
+ return true;
1487
+ }
1488
+ function resolveRootWorkflow(tree) {
1489
+ const rootId = tree.rootContainerIds[0];
1490
+ if (!rootId) return null;
1491
+ const root = tree.containersById[rootId];
1492
+ if (!root || root.scope !== "workflow") return null;
1493
+ return {
1494
+ workflowId: root.scope_key,
1495
+ workflowPath: root.workflow_path
1496
+ };
1497
+ }
1498
+ function humanizeTaskTitle(taskKey) {
1499
+ if (taskKey === "aggregate") return "Merge Perspectives";
1500
+ if (taskKey === "architecture") return "Architecture Path";
1501
+ if (taskKey === "implementation") return "Implementation Path";
1502
+ return taskKey.split("_").map((part) => part ? part[0].toUpperCase() + part.slice(1) : part).join(" ");
1503
+ }
1504
+ function findEmbeddedHostStage(tree, rootWorkflowPath, childWorkflowId) {
1505
+ const childPath = `${rootWorkflowPath}>${childWorkflowId}`;
1506
+ const subId = buildContainerId(childPath, "subworkflow", childWorkflowId);
1507
+ const existingSub = tree.containersById[subId];
1508
+ if (existingSub?.parent_container_id) {
1509
+ return tree.containersById[existingSub.parent_container_id] ?? null;
1510
+ }
1511
+ const runningStages = tree.activeContainerIds.map((id) => tree.containersById[id]).filter(
1512
+ (node) => node != null && node.scope === "stage" && node.workflow_path === rootWorkflowPath && node.is_open
1513
+ );
1514
+ if (runningStages.length === 0) return null;
1515
+ return runningStages.reduce(
1516
+ (best, node) => (node.display?.progress_current ?? 0) >= (best.display?.progress_current ?? 0) ? node : best
1517
+ );
1518
+ }
1519
+ function upsertRuntimeNode(tree, node) {
1520
+ const activeContainerIds = tree.activeContainerIds.includes(node.container_id) ? tree.activeContainerIds : node.is_open ? [...tree.activeContainerIds, node.container_id] : tree.activeContainerIds;
1521
+ return {
1522
+ ...tree,
1523
+ activeContainerIds,
1524
+ containersById: {
1525
+ ...tree.containersById,
1526
+ [node.container_id]: node
1527
+ }
1528
+ };
1529
+ }
1530
+ function completeOpenItemsUnderSubworkflow(tree, subworkflowId) {
1531
+ let next = tree;
1532
+ for (const node of Object.values(tree.containersById)) {
1533
+ if (node.parent_container_id === subworkflowId && node.scope === "item" && node.status === "running") {
1534
+ next = upsertRuntimeNode(next, {
1535
+ ...node,
1536
+ status: "completed",
1537
+ is_open: false
1538
+ });
1539
+ }
1540
+ }
1541
+ const activeContainerIds = next.activeContainerIds.filter(
1542
+ (id) => next.containersById[id]?.is_open
1543
+ );
1544
+ return { ...next, activeContainerIds };
1545
+ }
1546
+ function resolveLeafScope(taskKey) {
1547
+ return taskKey === "aggregate" ? "aggregate" : "item";
1548
+ }
1549
+ function shouldUseEmbeddedSynthesis(treeOrState, pendingEvent) {
1550
+ const tree = "containerTree" in treeOrState ? treeOrState.containerTree : treeOrState;
1551
+ if (pendingEvent && isNativeNestedStructureEvent(pendingEvent)) {
1552
+ return false;
1553
+ }
1554
+ if (hasNativeNestedStructureInTree(tree)) {
1555
+ return false;
1556
+ }
1557
+ return true;
1558
+ }
1559
+ function ensureEmbeddedSubworkflowContainers(tree, loopSessionId) {
1560
+ if (!shouldUseEmbeddedSynthesis(tree)) {
1561
+ return tree;
1562
+ }
1563
+ const root = resolveRootWorkflow(tree);
1564
+ const parts = parseLoopSessionParts(loopSessionId);
1565
+ if (!root || !parts || !isEmbeddedChildLoopSession(loopSessionId, root.workflowId)) {
1566
+ return tree;
1567
+ }
1568
+ const hostStage = findEmbeddedHostStage(tree, root.workflowPath, parts.workflowId);
1569
+ if (!hostStage) return tree;
1570
+ let next = tree;
1571
+ if (hostStage.loopSessionId) {
1572
+ next = upsertRuntimeNode(next, { ...hostStage, loopSessionId: null });
1573
+ }
1574
+ const childPath = `${root.workflowPath}>${parts.workflowId}`;
1575
+ const subId = buildContainerId(childPath, "subworkflow", parts.workflowId);
1576
+ let subNode = next.containersById[subId];
1577
+ if (!subNode) {
1578
+ subNode = {
1579
+ container_id: subId,
1580
+ scope: "subworkflow",
1581
+ workflow_path: childPath,
1582
+ workflow_depth: hostStage.workflow_depth + 1,
1583
+ scope_key: parts.workflowId,
1584
+ title: "Dual-Perspective Review",
1585
+ subtitle: "Nested parallel sub-workflow",
1586
+ status: "running",
1587
+ pattern: "parallelization",
1588
+ parent_container_id: hostStage.container_id,
1589
+ display: {
1590
+ title: "Dual-Perspective Review",
1591
+ description: "Nested parallel sub-workflow",
1592
+ pattern: "parallelization",
1593
+ status: "running"
1594
+ },
1595
+ content_blocks: [],
1596
+ is_open: true,
1597
+ loopSessionId: null
1598
+ };
1599
+ next = upsertRuntimeNode(next, subNode);
1600
+ }
1601
+ const leafScope = resolveLeafScope(parts.taskKey);
1602
+ const leafId = buildContainerId(childPath, leafScope, parts.taskKey);
1603
+ let leafNode = next.containersById[leafId];
1604
+ if (!leafNode) {
1605
+ leafNode = {
1606
+ container_id: leafId,
1607
+ scope: leafScope,
1608
+ workflow_path: childPath,
1609
+ workflow_depth: hostStage.workflow_depth + 1,
1610
+ scope_key: parts.taskKey,
1611
+ title: humanizeTaskTitle(parts.taskKey),
1612
+ subtitle: "",
1613
+ status: "running",
1614
+ pattern: "parallelization",
1615
+ parent_container_id: subId,
1616
+ display: {
1617
+ title: humanizeTaskTitle(parts.taskKey),
1618
+ pattern: "parallelization",
1619
+ status: "running"
1620
+ },
1621
+ content_blocks: [],
1622
+ is_open: true,
1623
+ loopSessionId: null
1624
+ };
1625
+ next = upsertRuntimeNode(next, leafNode);
1626
+ }
1627
+ if (leafScope === "aggregate") {
1628
+ next = completeOpenItemsUnderSubworkflow(next, subId);
1629
+ }
1630
+ if (leafNode.loopSessionId !== loopSessionId) {
1631
+ next = upsertRuntimeNode(next, {
1632
+ ...leafNode,
1633
+ loopSessionId,
1634
+ content_blocks: []
1635
+ });
1636
+ }
1637
+ return next;
1638
+ }
1639
+ function findContainerIdByLoopSession(tree, loopSessionId) {
1640
+ for (const [containerId, node] of Object.entries(tree.containersById)) {
1641
+ if (node.loopSessionId === loopSessionId) return containerId;
1642
+ }
1643
+ return null;
1644
+ }
1645
+ var PARALLEL_ITEM_STRUCTURE_EVENTS = /* @__PURE__ */ new Set([
1646
+ "parallel-item-start",
1647
+ "parallel-item-done",
1648
+ "parallel-item-failed"
1649
+ ]);
1650
+ function readItemKey(data) {
1651
+ const value = data.item_key;
1652
+ return typeof value === "string" ? value : void 0;
1653
+ }
1654
+ function resolveParallelItemLoopSessionId(eventType, eventSessionId, derivedLoopSessionId, data) {
1655
+ if (!PARALLEL_ITEM_STRUCTURE_EVENTS.has(eventType) || !eventSessionId) return null;
1656
+ if (eventSessionId === derivedLoopSessionId) return null;
1657
+ const itemKey = readItemKey(data);
1658
+ const parsed = parseLoopSessionParts(eventSessionId);
1659
+ if (itemKey && parsed?.taskKey === itemKey) {
1660
+ return eventSessionId;
1661
+ }
1662
+ return null;
1663
+ }
1664
+ function resolveStructureOpenLoopSessionId(eventType, eventSessionId, derivedLoopSessionId, data) {
1665
+ const rootWorkflowId = readWorkflowId(data) ?? "";
1666
+ if ((eventType === "parallel-aggregate-start" || eventType === "parallel-aggregate-done" || eventType === "parallel-aggregate-failed") && eventSessionId && isEmbeddedChildLoopSession(eventSessionId, rootWorkflowId)) {
1667
+ return eventSessionId;
1668
+ }
1669
+ const parallelItemSessionId = resolveParallelItemLoopSessionId(
1670
+ eventType,
1671
+ eventSessionId,
1672
+ derivedLoopSessionId,
1673
+ data
1674
+ );
1675
+ if (parallelItemSessionId) {
1676
+ return parallelItemSessionId;
1677
+ }
1678
+ return derivedLoopSessionId;
1679
+ }
1680
+ var EMBEDDED_AGGREGATE_STRUCTURE_EVENTS = /* @__PURE__ */ new Set([
1681
+ "parallel-aggregate-start",
1682
+ "parallel-aggregate-done",
1683
+ "parallel-aggregate-failed"
1684
+ ]);
1685
+ function patchEmbeddedStructureEvent(event, rootWorkflowId) {
1686
+ if (!EMBEDDED_AGGREGATE_STRUCTURE_EVENTS.has(event.event_type)) {
1687
+ return event;
1688
+ }
1689
+ if (!event.session_id || !isEmbeddedChildLoopSession(event.session_id, rootWorkflowId)) {
1690
+ return event;
1691
+ }
1692
+ const parts = parseLoopSessionParts(event.session_id);
1693
+ if (!parts) return event;
1694
+ const data = event.data ?? {};
1695
+ const rootPath = readString4(data, "workflow_path") ?? rootWorkflowId;
1696
+ const childPath = `${rootPath}>${parts.workflowId}`;
1697
+ const workflowDepth = typeof data.workflow_depth === "number" ? data.workflow_depth + 1 : 1;
1698
+ return {
1699
+ ...event,
1700
+ data: {
1701
+ ...data,
1702
+ workflow_path: childPath,
1703
+ workflow_depth: workflowDepth
1704
+ }
1705
+ };
1706
+ }
1707
+ function readString4(data, key) {
1708
+ const value = data[key];
1709
+ return typeof value === "string" ? value : void 0;
1710
+ }
1711
+ function isDescendantOf(containersById, ancestorId, nodeId) {
1712
+ const seen = /* @__PURE__ */ new Set();
1713
+ let current = containersById[nodeId]?.parent_container_id ?? null;
1714
+ while (current) {
1715
+ if (current === ancestorId) return true;
1716
+ if (seen.has(current)) return false;
1717
+ seen.add(current);
1718
+ current = containersById[current]?.parent_container_id ?? null;
1719
+ }
1720
+ return false;
1721
+ }
1722
+ function settleCompletedStageSubtree(tree, stageContainerId) {
1723
+ let next = tree;
1724
+ for (const node of Object.values(tree.containersById)) {
1725
+ if (node.container_id !== stageContainerId && !isDescendantOf(tree.containersById, stageContainerId, node.container_id)) {
1726
+ continue;
1727
+ }
1728
+ if (node.container_id === stageContainerId) continue;
1729
+ if (node.status === "completed" || node.status === "failed") continue;
1730
+ next = upsertRuntimeNode(next, {
1731
+ ...node,
1732
+ status: "completed",
1733
+ is_open: false
1734
+ });
1735
+ }
1736
+ const activeContainerIds = next.activeContainerIds.filter(
1737
+ (id) => next.containersById[id]?.is_open
1738
+ );
1739
+ return { ...next, activeContainerIds };
1740
+ }
1741
+ function readWorkflowId(data) {
1742
+ return readWorkflowIdFromData(data);
1743
+ }
1744
+
1745
+ // src/core/workflowReducer.ts
1746
+ function readString5(data, key) {
1747
+ const value = data[key];
1748
+ return typeof value === "string" ? value : void 0;
1749
+ }
1303
1750
  function mapContainerStatusToProgressStatus(status) {
1304
1751
  switch (status) {
1305
1752
  case "completed":
@@ -1376,23 +1823,43 @@ function applyStructureToContainerTree(tree, event) {
1376
1823
  };
1377
1824
  }
1378
1825
  function prebindLoopSessionFromStructureOpen(tree, event) {
1379
- const loopSessionId = deriveLoopSessionIdFromStructureOpen(event);
1826
+ const derivedLoopSessionId = deriveLoopSessionIdFromStructureOpen(event);
1380
1827
  const scope = loopTaskScopeForStructureOpen(event.event_type);
1381
- if (!loopSessionId || !scope) return tree;
1828
+ if (!derivedLoopSessionId || !scope) return tree;
1382
1829
  const data = event.data ?? {};
1383
- const workflowPath = readString3(data, "workflow_path") ?? "";
1830
+ let loopSessionId = resolveStructureOpenLoopSessionId(
1831
+ event.event_type,
1832
+ event.session_id,
1833
+ derivedLoopSessionId,
1834
+ data
1835
+ );
1836
+ if (!shouldPrebindLoopSessionFromStructureOpen(
1837
+ event.event_type,
1838
+ event.session_id,
1839
+ derivedLoopSessionId,
1840
+ data
1841
+ )) {
1842
+ loopSessionId = derivedLoopSessionId;
1843
+ }
1844
+ let nextTree = event.event_type === "parallel-aggregate-start" ? ensureEmbeddedSubworkflowContainers(tree, loopSessionId) : tree;
1845
+ const workflowPath = readString5(data, "workflow_path") ?? "";
1384
1846
  const scopeKey = resolveScopeKey(event.event_type, data);
1385
1847
  const containerId = buildContainerId(workflowPath, scope, scopeKey);
1386
- const node = tree.containersById[containerId];
1387
- if (!node) return tree;
1848
+ let targetId = containerId;
1849
+ const embeddedTargetId = findContainerIdByLoopSession(nextTree, loopSessionId);
1850
+ if (embeddedTargetId) {
1851
+ targetId = embeddedTargetId;
1852
+ }
1853
+ const node = nextTree.containersById[targetId];
1854
+ if (!node) return nextTree;
1388
1855
  if (node.loopSessionId === loopSessionId && node.content_blocks.length === 0) {
1389
- return tree;
1856
+ return nextTree;
1390
1857
  }
1391
1858
  return {
1392
- ...tree,
1859
+ ...nextTree,
1393
1860
  containersById: {
1394
- ...tree.containersById,
1395
- [containerId]: {
1861
+ ...nextTree.containersById,
1862
+ [targetId]: {
1396
1863
  ...node,
1397
1864
  loopSessionId,
1398
1865
  content_blocks: []
@@ -1438,10 +1905,7 @@ function enrichWorkflowRootFromStages(containersById, rootContainerIds) {
1438
1905
  };
1439
1906
  }
1440
1907
  function findContainerByLoopSession(tree, loopSessionId) {
1441
- for (const [containerId, node] of Object.entries(tree.containersById)) {
1442
- if (node.loopSessionId === loopSessionId) return containerId;
1443
- }
1444
- return null;
1908
+ return findContainerIdByLoopSession(tree, loopSessionId);
1445
1909
  }
1446
1910
  var WORKFLOW_DEFAULT_LOOP_SESSION_ID = "workflow-loop-default";
1447
1911
  function resolveAgentLoopSessionId(event, activeLoopSessionId) {
@@ -1456,56 +1920,184 @@ function resolveAgentLoopSessionId(event, activeLoopSessionId) {
1456
1920
  function bindLoopSessionToContainerTree(tree, event, activeLoopSessionId) {
1457
1921
  const sessionId = resolveAgentLoopSessionId(event, activeLoopSessionId);
1458
1922
  if (!sessionId) return tree;
1459
- if (findContainerByLoopSession(tree, sessionId)) return tree;
1923
+ let nextTree = ensureEmbeddedSubworkflowContainers(tree, sessionId);
1924
+ if (findContainerByLoopSession(nextTree, sessionId)) return nextTree;
1460
1925
  const data = event.data ?? {};
1461
- const workflowPath = readString3(data, "workflow_path") ?? null;
1926
+ const workflowPath = readString5(data, "workflow_path") ?? null;
1462
1927
  const scopeKey = resolveContentScopeKey(data);
1463
- let targetId = null;
1464
- if (scopeKey) {
1928
+ let targetId = findContainerByLoopSession(nextTree, sessionId);
1929
+ if (!targetId && scopeKey) {
1465
1930
  targetId = resolveContentTargetFromSnapshot(
1466
- containerTreeToSnapshot(tree),
1931
+ containerTreeToSnapshot(nextTree),
1467
1932
  workflowPath,
1468
1933
  scopeKey
1469
1934
  );
1470
1935
  }
1471
1936
  if (!targetId) {
1472
- for (const containerId of tree.activeContainerIds) {
1473
- const node2 = tree.containersById[containerId];
1474
- if ((node2?.scope === "item" || node2?.scope === "stage") && !node2.loopSessionId) {
1937
+ const fallbackScopes = ["item", "aggregate", "stage"];
1938
+ for (const scope of fallbackScopes) {
1939
+ for (const containerId of nextTree.activeContainerIds) {
1940
+ const node2 = nextTree.containersById[containerId];
1941
+ if (!node2 || node2.loopSessionId) continue;
1942
+ if (node2.scope !== scope) continue;
1943
+ if (node2.scope === "stage" && isNestedHostStageShell(nextTree, node2)) continue;
1475
1944
  targetId = containerId;
1476
1945
  break;
1477
1946
  }
1947
+ if (targetId) break;
1478
1948
  }
1479
1949
  }
1480
1950
  if (!targetId) {
1481
1951
  targetId = resolveContentTargetFromSnapshot(
1482
- containerTreeToSnapshot(tree),
1952
+ containerTreeToSnapshot(nextTree),
1483
1953
  workflowPath,
1484
1954
  null
1485
1955
  );
1486
1956
  }
1487
- if (!targetId) return tree;
1488
- const node = tree.containersById[targetId];
1489
- if (!node || node.loopSessionId) return tree;
1490
- if (node.scope !== "stage" && node.scope !== "item" && node.scope !== "branch") {
1491
- return tree;
1957
+ if (!targetId) return nextTree;
1958
+ const node = nextTree.containersById[targetId];
1959
+ if (!node || node.loopSessionId) return nextTree;
1960
+ if (node.scope === "stage" && isNestedHostStageShell(nextTree, node)) {
1961
+ return nextTree;
1962
+ }
1963
+ if (node.scope !== "stage" && node.scope !== "item" && node.scope !== "branch" && node.scope !== "aggregate") {
1964
+ return nextTree;
1492
1965
  }
1493
1966
  return {
1494
- ...tree,
1967
+ ...nextTree,
1495
1968
  containersById: {
1496
- ...tree.containersById,
1969
+ ...nextTree.containersById,
1497
1970
  [targetId]: { ...node, loopSessionId: sessionId }
1498
1971
  }
1499
1972
  };
1500
1973
  }
1974
+ var LOOP_TASK_STRUCTURE_CLOSE_EVENTS = /* @__PURE__ */ new Set([
1975
+ "stage-done",
1976
+ "stage-failed",
1977
+ "parallel-item-done",
1978
+ "parallel-item-failed",
1979
+ "parallel-aggregate-done",
1980
+ "parallel-aggregate-failed",
1981
+ "route-branch-done",
1982
+ "route-branch-failed"
1983
+ ]);
1984
+ function resolveStructureCloseLoopSessionId(event) {
1985
+ const data = event.data ?? {};
1986
+ const derived = deriveLoopSessionIdFromStructureOpen(event) ?? deriveLoopSessionIdFromAgentEvent(event);
1987
+ if (!derived && !event.session_id) return null;
1988
+ if (!derived) return event.session_id ?? null;
1989
+ return resolveStructureOpenLoopSessionId(
1990
+ event.event_type,
1991
+ event.session_id,
1992
+ derived,
1993
+ data
1994
+ );
1995
+ }
1996
+ function finalizeLoopTreesForCompletedContainers(state, containerTree) {
1997
+ let loopTreesBySessionId = state.loopTreesBySessionId;
1998
+ for (const node of Object.values(containerTree.containersById)) {
1999
+ if (!node.loopSessionId) continue;
2000
+ if (node.status !== "completed" && node.status !== "failed") continue;
2001
+ const loopTree = loopTreesBySessionId[node.loopSessionId];
2002
+ if (!loopTree || loopTree.status === "done" || loopTree.status === "error") continue;
2003
+ loopTreesBySessionId = {
2004
+ ...loopTreesBySessionId,
2005
+ [node.loopSessionId]: finalizeWorkflowLoopTree(
2006
+ loopTree,
2007
+ node.status === "failed" ? "error" : "done"
2008
+ )
2009
+ };
2010
+ }
2011
+ return { ...state, loopTreesBySessionId };
2012
+ }
2013
+ function clearActiveLoopIfFinalized(state) {
2014
+ const activeId = state.activeLoopSessionId;
2015
+ if (!activeId) return state;
2016
+ const loop = state.loopTreesBySessionId[activeId];
2017
+ if (loop && (loop.status === "done" || loop.status === "error")) {
2018
+ return { ...state, activeLoopSessionId: null };
2019
+ }
2020
+ return state;
2021
+ }
2022
+ function resolveRootWorkflowId(containerTree) {
2023
+ const rootId = containerTree.rootContainerIds[0];
2024
+ const root = rootId ? containerTree.containersById[rootId] : void 0;
2025
+ return root?.scope === "workflow" ? root.scope_key : null;
2026
+ }
2027
+ function findContainerForLoopSession(tree, loopSessionId) {
2028
+ return Object.values(tree.containersById).find(
2029
+ (node) => node.loopSessionId === loopSessionId
2030
+ );
2031
+ }
2032
+ function reconcileLoopTreeWithContainerStatus(containerTree, loopSessionId, loopTree) {
2033
+ const container = findContainerForLoopSession(containerTree, loopSessionId);
2034
+ if (!container) return loopTree;
2035
+ if (container.status === "completed") {
2036
+ return finalizeWorkflowLoopTree(loopTree, "done");
2037
+ }
2038
+ if (container.status === "failed") {
2039
+ return finalizeWorkflowLoopTree(loopTree, "error");
2040
+ }
2041
+ return loopTree;
2042
+ }
2043
+ function finalizeLoopTreeOnStructureClose(state, event) {
2044
+ if (!LOOP_TASK_STRUCTURE_CLOSE_EVENTS.has(event.event_type)) {
2045
+ return state;
2046
+ }
2047
+ const loopSessionId = resolveStructureCloseLoopSessionId(event);
2048
+ if (!loopSessionId) return state;
2049
+ const loopTree = state.loopTreesBySessionId[loopSessionId];
2050
+ if (!loopTree) return state;
2051
+ const terminalStatus = event.event_type.endsWith("-failed") ? "error" : "done";
2052
+ return {
2053
+ ...state,
2054
+ loopTreesBySessionId: {
2055
+ ...state.loopTreesBySessionId,
2056
+ [loopSessionId]: finalizeWorkflowLoopTree(loopTree, terminalStatus)
2057
+ }
2058
+ };
2059
+ }
1501
2060
  function applyStructureEvent(state, event) {
1502
- let containerTree = applyStructureToContainerTree(state.containerTree, event);
1503
- containerTree = prebindLoopSessionFromStructureOpen(containerTree, event);
1504
- const loopSessionId = deriveLoopSessionIdFromStructureOpen(event);
2061
+ const rootWorkflowId = resolveRootWorkflowId(state.containerTree);
2062
+ const useEmbeddedSynthesis = shouldUseEmbeddedSynthesis(state, event);
2063
+ const projectedEvent = rootWorkflowId != null && useEmbeddedSynthesis ? patchEmbeddedStructureEvent(event, rootWorkflowId) : event;
2064
+ let containerTree = applyStructureToContainerTree(state.containerTree, projectedEvent);
2065
+ if (projectedEvent.event_type === "subworkflow-start" || projectedEvent.event_type === "parallel-item-start") {
2066
+ containerTree = clearHostStageLoopOnNestedChildOpen(containerTree, projectedEvent);
2067
+ }
2068
+ if (projectedEvent.event_type === "subworkflow-start") {
2069
+ containerTree = dropStaleEmbeddedPathsOnNativeSubworkflowStart(containerTree, projectedEvent);
2070
+ }
2071
+ const derivedLoopSessionId = deriveLoopSessionIdFromStructureOpen(projectedEvent);
2072
+ const structureData = projectedEvent.data ?? {};
2073
+ const structureLoopSessionId = derivedLoopSessionId ? resolveStructureOpenLoopSessionId(
2074
+ projectedEvent.event_type,
2075
+ projectedEvent.session_id,
2076
+ derivedLoopSessionId,
2077
+ structureData
2078
+ ) : null;
2079
+ if (structureLoopSessionId && useEmbeddedSynthesis) {
2080
+ containerTree = ensureEmbeddedSubworkflowContainers(containerTree, structureLoopSessionId);
2081
+ }
2082
+ containerTree = prebindLoopSessionFromStructureOpen(containerTree, projectedEvent);
2083
+ if (projectedEvent.event_type === "stage-done" || projectedEvent.event_type === "stage-failed") {
2084
+ const stageKey = readString5(structureData, "stage_key");
2085
+ const workflowPath = readString5(structureData, "workflow_path") ?? "";
2086
+ if (stageKey) {
2087
+ const stageId = buildContainerId(workflowPath, "stage", stageKey);
2088
+ if (containerTree.containersById[stageId]) {
2089
+ containerTree = settleCompletedStageSubtree(containerTree, stageId);
2090
+ }
2091
+ }
2092
+ }
2093
+ const loopSessionId = structureLoopSessionId ?? derivedLoopSessionId;
1505
2094
  let nextState = { ...state, containerTree };
1506
2095
  if (loopSessionId) {
1507
2096
  nextState = ensureLoopTree(nextState, loopSessionId);
1508
2097
  }
2098
+ nextState = finalizeLoopTreeOnStructureClose(nextState, projectedEvent);
2099
+ nextState = finalizeLoopTreesForCompletedContainers(nextState, containerTree);
2100
+ nextState = clearActiveLoopIfFinalized(nextState);
1509
2101
  const workflowProgress = syncWorkflowProgressFromContainerTree(
1510
2102
  containerTree,
1511
2103
  state.workflowProgress
@@ -1547,7 +2139,12 @@ function reduceWorkflowSession(state, event, eventIndex, options) {
1547
2139
  const loopSessionId2 = taskListId ?? event.session_id ?? "workflow-loop-default";
1548
2140
  let next2 = ensureLoopTree(state, loopSessionId2);
1549
2141
  const loopTree2 = next2.loopTreesBySessionId[loopSessionId2];
1550
- const reducedLoop2 = reduceTree(loopTree2, event, eventIndex, options);
2142
+ let reducedLoop2 = reduceTree(loopTree2, event, eventIndex, options);
2143
+ reducedLoop2 = reconcileLoopTreeWithContainerStatus(
2144
+ next2.containerTree,
2145
+ loopSessionId2,
2146
+ reducedLoop2
2147
+ );
1551
2148
  return {
1552
2149
  ...next2,
1553
2150
  loopTreesBySessionId: {
@@ -1569,7 +2166,12 @@ function reduceWorkflowSession(state, event, eventIndex, options) {
1569
2166
  containerTree = applyStructureToContainerTree(containerTree, event);
1570
2167
  let next = ensureLoopTree({ ...state, containerTree }, loopSessionId);
1571
2168
  const loopTree = next.loopTreesBySessionId[loopSessionId];
1572
- const reducedLoop = reduceTree(loopTree, event, eventIndex, options);
2169
+ let reducedLoop = reduceTree(loopTree, event, eventIndex, options);
2170
+ reducedLoop = reconcileLoopTreeWithContainerStatus(
2171
+ next.containerTree,
2172
+ loopSessionId,
2173
+ reducedLoop
2174
+ );
1573
2175
  const workflowProgress = syncWorkflowProgressFromContainerTree(
1574
2176
  next.containerTree,
1575
2177
  next.workflowProgress
@@ -1736,6 +2338,18 @@ function evictCompletedLoopOverflow(state, maxHydratedCompletedLoops) {
1736
2338
  }
1737
2339
 
1738
2340
  // src/core/workflowSessionStore.ts
2341
+ var DEFAULT_HYDRATE_CHUNK_SIZE = 50;
2342
+ function nextAnimationFrame() {
2343
+ return new Promise((resolve) => {
2344
+ requestAnimationFrame(() => resolve());
2345
+ });
2346
+ }
2347
+ function withoutHydrating(ids, loopSessionId) {
2348
+ if (!ids[loopSessionId]) return ids;
2349
+ const next = { ...ids };
2350
+ delete next[loopSessionId];
2351
+ return next;
2352
+ }
1739
2353
  function safeReduceWorkflow(state, event, eventIndex, options) {
1740
2354
  try {
1741
2355
  return { state: reduceWorkflowSession(state, event, eventIndex, options) };
@@ -1756,6 +2370,39 @@ function safeReduceWorkflow(state, event, eventIndex, options) {
1756
2370
  };
1757
2371
  }
1758
2372
  }
2373
+ function finalizeWorkflowSessionOnStreamError(state, message, eventIndex, stack) {
2374
+ const containersById = {
2375
+ ...state.containerTree.containersById
2376
+ };
2377
+ for (const [id, node] of Object.entries(containersById)) {
2378
+ if (node.status === "running" || node.status === "pending") {
2379
+ containersById[id] = { ...node, status: "failed", is_open: false };
2380
+ }
2381
+ }
2382
+ const loopTreesBySessionId = { ...state.loopTreesBySessionId };
2383
+ for (const [loopSessionId, tree] of Object.entries(loopTreesBySessionId)) {
2384
+ if (tree.status === "running" || tree.status === "connecting") {
2385
+ loopTreesBySessionId[loopSessionId] = finalizeWorkflowLoopTree(tree, "error");
2386
+ }
2387
+ }
2388
+ return {
2389
+ ...state,
2390
+ status: "error",
2391
+ containerTree: {
2392
+ ...state.containerTree,
2393
+ containersById,
2394
+ activeContainerIds: state.containerTree.activeContainerIds.filter(
2395
+ (id) => containersById[id]?.is_open
2396
+ )
2397
+ },
2398
+ loopTreesBySessionId,
2399
+ activeLoopSessionId: null,
2400
+ internalErrors: [
2401
+ ...state.internalErrors,
2402
+ { eventIndex, eventType: "stream-error", message, stack }
2403
+ ]
2404
+ };
2405
+ }
1759
2406
  function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionState(), storeOptions) {
1760
2407
  const reduceOpts = {
1761
2408
  tierOverrides: storeOptions?.tierOverrides
@@ -1772,6 +2419,7 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
1772
2419
  return createStore2((set, get) => ({
1773
2420
  state: initialState,
1774
2421
  eventCount: initialEventCount,
2422
+ hydratingLoopSessionIds: {},
1775
2423
  applyEvent(event, ctx) {
1776
2424
  const sseEventId = ctx?.sseEventId;
1777
2425
  if (shouldSkipDuplicateEvent(seenEventIds, sseEventId)) return;
@@ -1798,26 +2446,20 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
1798
2446
  seenEventIds.clear();
1799
2447
  set({
1800
2448
  state: createEmptyWorkflowSessionState(),
1801
- eventCount: 0
2449
+ eventCount: 0,
2450
+ hydratingLoopSessionIds: {}
1802
2451
  });
1803
2452
  },
1804
2453
  markAsError(error) {
1805
- const { state } = get();
2454
+ const { state, eventCount } = get();
1806
2455
  const message = error?.message ?? "Workflow stream error";
1807
2456
  set({
1808
- state: {
1809
- ...state,
1810
- status: "error",
1811
- internalErrors: [
1812
- ...state.internalErrors,
1813
- {
1814
- eventIndex: get().eventCount,
1815
- eventType: "stream-error",
1816
- message,
1817
- stack: error?.stack
1818
- }
1819
- ]
1820
- }
2457
+ state: finalizeWorkflowSessionOnStreamError(
2458
+ state,
2459
+ message,
2460
+ eventCount,
2461
+ error?.stack
2462
+ )
1821
2463
  });
1822
2464
  },
1823
2465
  /**
@@ -1857,6 +2499,71 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
1857
2499
  next = finalizeState(next);
1858
2500
  return { state: next };
1859
2501
  });
2502
+ },
2503
+ async hydrateLoopSessionAsync(loopSessionId, events, options) {
2504
+ const { hydratingLoopSessionIds } = get();
2505
+ if (hydratingLoopSessionIds[loopSessionId]) return;
2506
+ const filtered = filterAgentLoopReplayEvents(events);
2507
+ if (filtered.length === 0) return;
2508
+ const chunkSize = options?.chunkSize ?? DEFAULT_HYDRATE_CHUNK_SIZE;
2509
+ const signal = options?.signal;
2510
+ set((current) => ({
2511
+ hydratingLoopSessionIds: {
2512
+ ...current.hydratingLoopSessionIds,
2513
+ [loopSessionId]: true
2514
+ }
2515
+ }));
2516
+ try {
2517
+ let tree = get().state.loopTreesBySessionId[loopSessionId] ?? createEmptyTree();
2518
+ let idx = get().eventCount;
2519
+ for (let offset = 0; offset < filtered.length; offset += chunkSize) {
2520
+ if (signal?.aborted) {
2521
+ throw new DOMException("Hydrate aborted", "AbortError");
2522
+ }
2523
+ const chunk = filtered.slice(offset, offset + chunkSize);
2524
+ for (const event of chunk) {
2525
+ tree = reduceTree(tree, event, idx, reduceOpts);
2526
+ idx += 1;
2527
+ }
2528
+ const isLastChunk = offset + chunkSize >= filtered.length;
2529
+ set((current) => {
2530
+ let next = {
2531
+ ...current.state,
2532
+ loopTreesBySessionId: {
2533
+ ...current.state.loopTreesBySessionId,
2534
+ [loopSessionId]: tree
2535
+ }
2536
+ };
2537
+ if (isLastChunk) {
2538
+ next = touchCompletedLoopSession(next, loopSessionId);
2539
+ next = evictCompletedLoopOverflow(next, maxHydratedCompletedLoops);
2540
+ }
2541
+ next = finalizeState(next);
2542
+ return { state: next };
2543
+ });
2544
+ if (!isLastChunk) {
2545
+ await nextAnimationFrame();
2546
+ }
2547
+ }
2548
+ } catch {
2549
+ set((current) => {
2550
+ const { [loopSessionId]: _removed, ...restLoops } = current.state.loopTreesBySessionId;
2551
+ return {
2552
+ state: { ...current.state, loopTreesBySessionId: restLoops },
2553
+ hydratingLoopSessionIds: withoutHydrating(
2554
+ current.hydratingLoopSessionIds,
2555
+ loopSessionId
2556
+ )
2557
+ };
2558
+ });
2559
+ return;
2560
+ }
2561
+ set((current) => ({
2562
+ hydratingLoopSessionIds: withoutHydrating(
2563
+ current.hydratingLoopSessionIds,
2564
+ loopSessionId
2565
+ )
2566
+ }));
1860
2567
  }
1861
2568
  }));
1862
2569
  }
@@ -1865,7 +2572,7 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
1865
2572
  import { useMemo as useMemo7, useState as useState6 } from "react";
1866
2573
 
1867
2574
  // src/view/workflow/WorkflowAggregateContainer.tsx
1868
- import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState } from "react";
2575
+ import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState, memo } from "react";
1869
2576
 
1870
2577
  // src/view/workflow/WorkflowContainerLine.tsx
1871
2578
  import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -1998,6 +2705,17 @@ function WorkflowExpandTrigger({
1998
2705
  );
1999
2706
  }
2000
2707
 
2708
+ // src/view/workflow/WorkflowMarkdownPreview.tsx
2709
+ import { jsx as jsx7 } from "react/jsx-runtime";
2710
+ function WorkflowMarkdownPreview({
2711
+ content,
2712
+ testId,
2713
+ className = "lax-workflow-markdown-preview"
2714
+ }) {
2715
+ if (!content.trim()) return null;
2716
+ return /* @__PURE__ */ jsx7("div", { className, "data-testid": testId, children: /* @__PURE__ */ jsx7(RichMarkdown, { content, deferDiagrams: true }) });
2717
+ }
2718
+
2001
2719
  // src/view/workflow/workflowContainerLineUtils.ts
2002
2720
  function formatContainerScopeMetaLabel(scope) {
2003
2721
  return scope;
@@ -2122,15 +2840,37 @@ function mapAggregateUiStatus(node) {
2122
2840
  return mapContainerUiStatus(node.status);
2123
2841
  }
2124
2842
 
2843
+ // src/view/workflow/workflowContainerMemo.ts
2844
+ function displayVisualEqual(a, b) {
2845
+ if (a === b) return true;
2846
+ if (a === null || b === null) return false;
2847
+ if (a.progress_current !== b.progress_current) return false;
2848
+ if (a.status !== b.status) return false;
2849
+ return true;
2850
+ }
2851
+ function workflowContainerNodeVisualEqual(a, b) {
2852
+ if (a.container_id !== b.container_id) return false;
2853
+ if (a.status !== b.status) return false;
2854
+ if (a.is_open !== b.is_open) return false;
2855
+ if (a.loopSessionId !== b.loopSessionId) return false;
2856
+ if (a.title !== b.title) return false;
2857
+ if (a.subtitle !== b.subtitle) return false;
2858
+ if (a.content_blocks.length !== b.content_blocks.length) return false;
2859
+ if (!displayVisualEqual(a.display, b.display)) return false;
2860
+ return true;
2861
+ }
2862
+ function workflowContainerViewPropsEqual(prev, next) {
2863
+ return prev.depth === next.depth && prev.virtualized === next.virtualized && prev.virtualizeThreshold === next.virtualizeThreshold && prev.groupParallelTools === next.groupParallelTools;
2864
+ }
2865
+
2125
2866
  // src/view/workflow/WorkflowAggregateContainer.tsx
2126
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
2127
- function mergeTeaser(node) {
2867
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
2868
+ function mergeContentPreview(node) {
2128
2869
  const blocks = node.content_blocks;
2129
2870
  if (blocks.length === 0) return "";
2130
- const last = blocks[blocks.length - 1];
2131
- return last?.preview ?? "";
2871
+ return blocks.map((block) => block.preview?.trim() ?? "").filter(Boolean).join("\n\n");
2132
2872
  }
2133
- function WorkflowAggregateContainer({
2873
+ function WorkflowAggregateContainerInner({
2134
2874
  node,
2135
2875
  loopTree,
2136
2876
  siblingItems = [],
@@ -2142,8 +2882,8 @@ function WorkflowAggregateContainer({
2142
2882
  const isRunning = node.status === "running" || node.status === "retrying";
2143
2883
  const isWaiting = node.status === "pending" || node.status === "blocked";
2144
2884
  const isDone = node.status === "completed" || node.status === "failed";
2145
- const teaser = useMemo2(() => mergeTeaser(node), [node]);
2146
- const waitingTeaser = isWaiting ? teaser || node.subtitle || "" : "";
2885
+ const contentPreview = useMemo2(() => mergeContentPreview(node), [node]);
2886
+ const waitingTeaser = isWaiting ? contentPreview || node.subtitle || "" : "";
2147
2887
  const testId = `lax-workflow-aggregate-${node.scope_key}`;
2148
2888
  const uiStatus = mapAggregateUiStatus(node);
2149
2889
  const statusLabel = buildAggregateStatusLabel(node, siblingItems);
@@ -2156,6 +2896,14 @@ function WorkflowAggregateContainer({
2156
2896
  setExpanded((prev) => !prev);
2157
2897
  }, []);
2158
2898
  const shellToggle = isRunning ? toggleExpanded : isDone && expanded ? toggleExpanded : void 0;
2899
+ const markdownPreview = contentPreview ? /* @__PURE__ */ jsx8(
2900
+ WorkflowMarkdownPreview,
2901
+ {
2902
+ content: contentPreview,
2903
+ testId: `${testId}-markdown`,
2904
+ className: "lax-workflow-aggregate__markdown lax-workflow-markdown-preview"
2905
+ }
2906
+ ) : null;
2159
2907
  return /* @__PURE__ */ jsxs4(
2160
2908
  "div",
2161
2909
  {
@@ -2164,7 +2912,7 @@ function WorkflowAggregateContainer({
2164
2912
  "data-container-scope": "aggregate",
2165
2913
  style: { "--lax-workflow-depth": depth },
2166
2914
  children: [
2167
- /* @__PURE__ */ jsx7(
2915
+ /* @__PURE__ */ jsx8(
2168
2916
  WorkflowContainerLine,
2169
2917
  {
2170
2918
  title: node.title,
@@ -2180,10 +2928,16 @@ function WorkflowAggregateContainer({
2180
2928
  }
2181
2929
  ),
2182
2930
  isWaiting && waitingTeaser ? /* @__PURE__ */ jsxs4("div", { className: "lax-workflow-aggregate__waiting-teaser", "data-testid": `${testId}-waiting`, children: [
2183
- "\u23BF ",
2184
- waitingTeaser
2931
+ /* @__PURE__ */ jsx8("span", { className: "lax-workflow-aggregate__waiting-prefix", children: "\u23BF " }),
2932
+ /* @__PURE__ */ jsx8(
2933
+ WorkflowMarkdownPreview,
2934
+ {
2935
+ content: waitingTeaser,
2936
+ className: "lax-workflow-aggregate__markdown lax-workflow-markdown-preview"
2937
+ }
2938
+ )
2185
2939
  ] }) : null,
2186
- expanded && isRunning && loopTree ? /* @__PURE__ */ jsx7("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx7(
2940
+ expanded && isRunning && loopTree ? /* @__PURE__ */ jsx8("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx8(
2187
2941
  AgentLoopView,
2188
2942
  {
2189
2943
  tree: loopTree,
@@ -2193,18 +2947,28 @@ function WorkflowAggregateContainer({
2193
2947
  groupParallelTools
2194
2948
  }
2195
2949
  ) }) : null,
2196
- isDone && !expanded ? /* @__PURE__ */ jsx7(
2197
- WorkflowExpandTrigger,
2950
+ expanded && isRunning && !loopTree && contentPreview ? /* @__PURE__ */ jsx8(
2951
+ "div",
2198
2952
  {
2199
- summary: `${node.title} \xB7 done`,
2200
- teaser: teaser || void 0,
2201
- onToggle: () => setExpanded(true),
2202
- expanded: false,
2203
- testId: `${testId}-done`,
2204
- teaserTestId: `${testId}-teaser`
2953
+ className: "lax-workflow-stage-container__body lax-workflow-stage-container__content-preview",
2954
+ "data-testid": `${testId}-body`,
2955
+ children: markdownPreview
2205
2956
  }
2206
2957
  ) : null,
2207
- isDone && expanded && loopTree ? /* @__PURE__ */ jsx7("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx7(
2958
+ isDone && !expanded ? /* @__PURE__ */ jsxs4("div", { className: "lax-workflow-aggregate__done", "data-testid": `${testId}-done`, children: [
2959
+ /* @__PURE__ */ jsx8(
2960
+ WorkflowExpandTrigger,
2961
+ {
2962
+ summary: `${node.title} \xB7 done`,
2963
+ onToggle: () => setExpanded(true),
2964
+ expanded: false,
2965
+ expandHint: contentPreview ? "(click to expand)" : void 0,
2966
+ testId: `${testId}-done-trigger`
2967
+ }
2968
+ ),
2969
+ markdownPreview ? /* @__PURE__ */ jsx8("div", { className: "lax-workflow-aggregate__done-preview", "data-testid": `${testId}-teaser`, children: markdownPreview }) : null
2970
+ ] }) : null,
2971
+ isDone && expanded && loopTree ? /* @__PURE__ */ jsx8("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx8(
2208
2972
  AgentLoopView,
2209
2973
  {
2210
2974
  tree: loopTree,
@@ -2213,14 +2977,37 @@ function WorkflowAggregateContainer({
2213
2977
  virtualizeThreshold,
2214
2978
  groupParallelTools
2215
2979
  }
2216
- ) }) : null
2980
+ ) }) : null,
2981
+ isDone && expanded && !loopTree && contentPreview ? /* @__PURE__ */ jsx8(
2982
+ "div",
2983
+ {
2984
+ className: "lax-workflow-stage-container__body lax-workflow-stage-container__content-preview",
2985
+ "data-testid": `${testId}-body`,
2986
+ children: markdownPreview
2987
+ }
2988
+ ) : null
2217
2989
  ]
2218
2990
  }
2219
2991
  );
2220
2992
  }
2993
+ function workflowAggregateContainerPropsEqual(prev, next) {
2994
+ if (prev.loopTree !== next.loopTree) return false;
2995
+ if (!workflowContainerViewPropsEqual(prev, next)) return false;
2996
+ if (prev.siblingItems.length !== next.siblingItems.length) return false;
2997
+ for (let i = 0; i < prev.siblingItems.length; i += 1) {
2998
+ if (!workflowContainerNodeVisualEqual(prev.siblingItems[i], next.siblingItems[i])) {
2999
+ return false;
3000
+ }
3001
+ }
3002
+ return workflowContainerNodeVisualEqual(prev.node, next.node);
3003
+ }
3004
+ var WorkflowAggregateContainer = memo(
3005
+ WorkflowAggregateContainerInner,
3006
+ workflowAggregateContainerPropsEqual
3007
+ );
2221
3008
 
2222
3009
  // src/view/workflow/WorkflowParallelGroup.tsx
2223
- import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo4, useState as useState3 } from "react";
3010
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo4, useState as useState3, memo as memo3 } from "react";
2224
3011
  import { useStore as useStore3 } from "zustand";
2225
3012
 
2226
3013
  // src/core/workflow/workflowParallelGroupMetrics.ts
@@ -2287,7 +3074,7 @@ function formatParallelGroupSummary(metrics) {
2287
3074
  }
2288
3075
 
2289
3076
  // src/view/workflow/WorkflowStageContainer.tsx
2290
- import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3, useState as useState2 } from "react";
3077
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3, useState as useState2, memo as memo2 } from "react";
2291
3078
  import { useStore as useStore2 } from "zustand";
2292
3079
 
2293
3080
  // src/core/workflow/loopTreeUtils.ts
@@ -2297,7 +3084,7 @@ function hasTimelineNodes(tree) {
2297
3084
  }
2298
3085
 
2299
3086
  // src/view/workflow/workflowStageDoneBody.tsx
2300
- import { jsx as jsx8 } from "react/jsx-runtime";
3087
+ import { jsx as jsx9 } from "react/jsx-runtime";
2301
3088
  function WorkflowStageDoneBody({
2302
3089
  loopTree,
2303
3090
  testId,
@@ -2307,7 +3094,7 @@ function WorkflowStageDoneBody({
2307
3094
  bodyClassName = "lax-workflow-stage-container__body"
2308
3095
  }) {
2309
3096
  if (loopTree && hasTimelineNodes(loopTree)) {
2310
- return /* @__PURE__ */ jsx8("div", { className: bodyClassName, "data-testid": testId, children: /* @__PURE__ */ jsx8(
3097
+ return /* @__PURE__ */ jsx9("div", { className: bodyClassName, "data-testid": testId, children: /* @__PURE__ */ jsx9(
2311
3098
  AgentLoopView,
2312
3099
  {
2313
3100
  tree: loopTree,
@@ -2318,12 +3105,12 @@ function WorkflowStageDoneBody({
2318
3105
  }
2319
3106
  ) });
2320
3107
  }
2321
- return /* @__PURE__ */ jsx8(
3108
+ return /* @__PURE__ */ jsx9(
2322
3109
  "div",
2323
3110
  {
2324
3111
  className: `${bodyClassName} lax-workflow-stage-container__empty-hint`,
2325
3112
  "data-testid": testId,
2326
- children: /* @__PURE__ */ jsx8("span", { className: "lax-workflow-stage-container__empty-hint-text", children: "\uFF08\u6B64\u9636\u6BB5\u6682\u65E0 Agent \u65F6\u95F4\u7EBF\u5185\u5BB9\uFF09" })
3113
+ children: /* @__PURE__ */ jsx9("span", { className: "lax-workflow-stage-container__empty-hint-text", children: "\uFF08\u6B64\u9636\u6BB5\u6682\u65E0 Agent \u65F6\u95F4\u7EBF\u5185\u5BB9\uFF09" })
2327
3114
  }
2328
3115
  );
2329
3116
  }
@@ -2362,12 +3149,15 @@ function partitionWorkflowStages(stages, maxVisibleDoneCount) {
2362
3149
  }
2363
3150
  function isActiveWorkflowContainer(node, activeLoopSessionId, activeContainerIds) {
2364
3151
  if (activeContainerIds.includes(node.container_id)) return true;
3152
+ if (node.status === "completed" || node.status === "failed" || node.status === "skipped") {
3153
+ return false;
3154
+ }
2365
3155
  if (node.loopSessionId && node.loopSessionId === activeLoopSessionId) return true;
2366
3156
  return false;
2367
3157
  }
2368
3158
 
2369
3159
  // src/view/workflow/WorkflowStageContainer.tsx
2370
- import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
3160
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
2371
3161
  function containerTestId(node) {
2372
3162
  if (node.scope === "item") return `lax-workflow-parallel-${node.scope_key}`;
2373
3163
  if (node.scope === "stage") return `lax-workflow-stage-${node.scope_key}`;
@@ -2377,13 +3167,14 @@ function bodyTestId(node) {
2377
3167
  if (node.scope === "item") return `lax-workflow-parallel-body-${node.scope_key}`;
2378
3168
  return `lax-workflow-stage-body-${node.scope_key}`;
2379
3169
  }
2380
- function WorkflowStageContainer({
3170
+ function WorkflowStageContainerInner({
2381
3171
  node,
2382
3172
  loopTree,
2383
3173
  depth = 0,
2384
3174
  virtualized,
2385
3175
  virtualizeThreshold,
2386
- groupParallelTools
3176
+ groupParallelTools,
3177
+ suppressHostScopedLoop = false
2387
3178
  }) {
2388
3179
  const { verbose } = useSessionViewOptions();
2389
3180
  const scale = useWorkflowScaleOptions();
@@ -2446,13 +3237,13 @@ function WorkflowStageContainer({
2446
3237
  return () => window.removeEventListener("keydown", onKeyDown);
2447
3238
  }, [requestLoopHydration]);
2448
3239
  const displayTitle = node.title;
2449
- const usesScopedLoop = Boolean(node.loopSessionId);
3240
+ const usesScopedLoop = Boolean(node.loopSessionId) && !suppressHostScopedLoop;
2450
3241
  const runningLoopTree = loopTree ?? (usesScopedLoop && isRunning && isActive ? createWorkflowRunningLoopTree() : void 0);
2451
3242
  const staticContentTeaser = !usesScopedLoop && !loopTree && node.content_blocks.length > 0 ? node.content_blocks[node.content_blocks.length - 1].preview : "";
2452
3243
  const teaserText = doneSummary?.teaser || staticContentTeaser;
2453
3244
  const expandHint = doneSummary ? formatTeaserExpandHint(doneSummary.extraLines, verbose) : null;
2454
3245
  const doneLabel = doneSummary?.label ?? "done";
2455
- const statusLabel = isDone ? doneLabel : mapContainerStatusLabel(node.status);
3246
+ const statusLabel = isDone ? doneLabel : suppressHostScopedLoop && isRunning ? "running" : mapContainerStatusLabel(node.status);
2456
3247
  const testId = containerTestId(node);
2457
3248
  const isItem = node.scope === "item";
2458
3249
  const canToggleShell = isRunning && !isSkipped || isDone;
@@ -2470,7 +3261,7 @@ function WorkflowStageContainer({
2470
3261
  "--lax-workflow-indent-unit": workflowIndentUnit(depth)
2471
3262
  },
2472
3263
  children: [
2473
- /* @__PURE__ */ jsx9(
3264
+ /* @__PURE__ */ jsx10(
2474
3265
  WorkflowContainerLine,
2475
3266
  {
2476
3267
  title: displayTitle,
@@ -2488,7 +3279,7 @@ function WorkflowStageContainer({
2488
3279
  }
2489
3280
  ),
2490
3281
  isDone && !expanded && (teaserText || expandHint) ? /* @__PURE__ */ jsxs5("div", { className: "lax-workflow-stage-container__done-teaser", children: [
2491
- teaserText ? /* @__PURE__ */ jsx9(
3282
+ teaserText ? /* @__PURE__ */ jsx10(
2492
3283
  "span",
2493
3284
  {
2494
3285
  className: "lax-workflow-stage-container__done-teaser-line",
@@ -2496,14 +3287,14 @@ function WorkflowStageContainer({
2496
3287
  children: `\u23BF ${teaserText}`
2497
3288
  }
2498
3289
  ) : null,
2499
- expandHint ? /* @__PURE__ */ jsx9("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
3290
+ expandHint ? /* @__PURE__ */ jsx10("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
2500
3291
  ] }) : null,
2501
- expanded && isRunning && !usesScopedLoop && node.content_blocks.length > 0 ? /* @__PURE__ */ jsx9(
3292
+ expanded && isRunning && !usesScopedLoop && node.content_blocks.length > 0 ? /* @__PURE__ */ jsx10(
2502
3293
  "div",
2503
3294
  {
2504
3295
  className: "lax-workflow-stage-container__body lax-workflow-stage-container__content-preview",
2505
3296
  "data-testid": bodyTestId(node),
2506
- children: node.content_blocks.map((block, index) => /* @__PURE__ */ jsx9(
3297
+ children: node.content_blocks.map((block, index) => /* @__PURE__ */ jsx10(
2507
3298
  "div",
2508
3299
  {
2509
3300
  className: "lax-workflow-stage-container__teaser",
@@ -2513,12 +3304,12 @@ function WorkflowStageContainer({
2513
3304
  ))
2514
3305
  }
2515
3306
  ) : null,
2516
- expanded && isRunning && runningLoopTree ? /* @__PURE__ */ jsx9(
3307
+ expanded && isRunning && runningLoopTree ? /* @__PURE__ */ jsx10(
2517
3308
  "div",
2518
3309
  {
2519
3310
  className: isItem ? "lax-workflow-parallel-item__body lax-workflow-stage-container__body" : "lax-workflow-stage-container__body",
2520
3311
  "data-testid": bodyTestId(node),
2521
- children: /* @__PURE__ */ jsx9(
3312
+ children: /* @__PURE__ */ jsx10(
2522
3313
  AgentLoopView,
2523
3314
  {
2524
3315
  tree: runningLoopTree,
@@ -2530,7 +3321,7 @@ function WorkflowStageContainer({
2530
3321
  )
2531
3322
  }
2532
3323
  ) : null,
2533
- isDone && expanded ? loopTree ? /* @__PURE__ */ jsx9(
3324
+ isDone && expanded ? loopTree ? /* @__PURE__ */ jsx10(
2534
3325
  WorkflowStageDoneBody,
2535
3326
  {
2536
3327
  node,
@@ -2547,8 +3338,8 @@ function WorkflowStageContainer({
2547
3338
  className: "lax-workflow-stage-container__body lax-workflow-loop-replay-pending",
2548
3339
  "data-testid": bodyTestId(node),
2549
3340
  children: [
2550
- teaserText ? /* @__PURE__ */ jsx9("p", { className: "lax-workflow-stage-container__teaser", children: teaserText }) : null,
2551
- /* @__PURE__ */ jsx9("p", { className: "lax-workflow-loop-replay-pending__hint", children: replayHandler ? "Loading loop details\u2026" : "Loop details evicted from memory." })
3341
+ teaserText ? /* @__PURE__ */ jsx10("p", { className: "lax-workflow-stage-container__teaser", children: teaserText }) : null,
3342
+ /* @__PURE__ */ jsx10("p", { className: "lax-workflow-loop-replay-pending__hint", children: replayHandler ? "Loading loop details\u2026" : "Loop details evicted from memory." })
2552
3343
  ]
2553
3344
  }
2554
3345
  ) : null
@@ -2556,14 +3347,21 @@ function WorkflowStageContainer({
2556
3347
  }
2557
3348
  );
2558
3349
  }
3350
+ function workflowStageContainerPropsEqual(prev, next) {
3351
+ if (prev.suppressHostScopedLoop !== next.suppressHostScopedLoop) return false;
3352
+ if (prev.loopTree !== next.loopTree) return false;
3353
+ if (!workflowContainerViewPropsEqual(prev, next)) return false;
3354
+ return workflowContainerNodeVisualEqual(prev.node, next.node);
3355
+ }
3356
+ var WorkflowStageContainer = memo2(WorkflowStageContainerInner, workflowStageContainerPropsEqual);
2559
3357
 
2560
3358
  // src/view/workflow/WorkflowParallelGroup.tsx
2561
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
3359
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2562
3360
  function resolveLoopTree(node, loopTreesBySessionId) {
2563
3361
  if (!node.loopSessionId) return void 0;
2564
3362
  return loopTreesBySessionId[node.loopSessionId];
2565
3363
  }
2566
- function WorkflowParallelGroup({
3364
+ function WorkflowParallelGroupInner({
2567
3365
  groupKey,
2568
3366
  items,
2569
3367
  loopTreesBySessionId,
@@ -2579,12 +3377,13 @@ function WorkflowParallelGroup({
2579
3377
  const metrics = useMemo4(() => computeParallelGroupMetrics(items), [items]);
2580
3378
  const summary = formatParallelGroupSummary(metrics);
2581
3379
  const hasRunning = metrics.running > 0;
3380
+ const isNestedGroup = groupKey.includes(">");
2582
3381
  const hasActiveItem = useMemo4(
2583
3382
  () => items.some((item) => isActiveWorkflowContainer(item, activeLoopSessionId, activeContainerIds)),
2584
3383
  [items, activeLoopSessionId, activeContainerIds]
2585
3384
  );
2586
3385
  const allSettled = metrics.total > 0 && metrics.running === 0 && metrics.completed + metrics.failed >= metrics.total;
2587
- const shouldAutoExpandGroup = hasRunning && (!scale.onlyExpandActiveRunning || hasActiveItem);
3386
+ const shouldAutoExpandGroup = hasRunning && (isNestedGroup || !scale.onlyExpandActiveRunning || hasActiveItem);
2588
3387
  const [expanded, setExpanded] = useState3(shouldAutoExpandGroup);
2589
3388
  useEffect5(() => {
2590
3389
  if (shouldAutoExpandGroup) {
@@ -2612,7 +3411,7 @@ function WorkflowParallelGroup({
2612
3411
  }, []);
2613
3412
  const renderItem = (index) => {
2614
3413
  const item = items[index];
2615
- return /* @__PURE__ */ jsx10(
3414
+ return /* @__PURE__ */ jsx11(
2616
3415
  WorkflowStageContainer,
2617
3416
  {
2618
3417
  node: item,
@@ -2631,25 +3430,43 @@ function WorkflowParallelGroup({
2631
3430
  className: "lax-workflow-parallel-group",
2632
3431
  "data-testid": `lax-workflow-parallel-group-${groupKey}`,
2633
3432
  children: [
2634
- /* @__PURE__ */ jsx10(
3433
+ /* @__PURE__ */ jsx11(
2635
3434
  WorkflowExpandTrigger,
2636
3435
  {
2637
3436
  summary,
2638
- expandHint: !expanded && !hasRunning ? "(ctrl+o to expand)" : null,
3437
+ expandHint: !expanded && hasRunning ? "(click to expand agents)" : !expanded && !hasRunning ? "(ctrl+o to expand)" : null,
2639
3438
  onToggle: toggleExpanded,
2640
3439
  expanded,
2641
3440
  testId: `lax-workflow-parallel-group-summary-${groupKey}`,
2642
3441
  className: "lax-workflow-parallel-group__summary"
2643
3442
  }
2644
3443
  ),
2645
- expanded ? /* @__PURE__ */ jsx10("div", { className: "lax-workflow-parallel-list", "data-testid": "lax-workflow-parallel-list", children: items.map((_, index) => renderItem(index)) }) : null
3444
+ expanded ? /* @__PURE__ */ jsx11("div", { className: "lax-workflow-parallel-list", "data-testid": "lax-workflow-parallel-list", children: items.map((_, index) => renderItem(index)) }) : null
2646
3445
  ]
2647
3446
  }
2648
3447
  );
2649
3448
  }
3449
+ function workflowParallelGroupPropsEqual(prev, next) {
3450
+ if (prev.groupKey !== next.groupKey) return false;
3451
+ if (!workflowContainerViewPropsEqual(prev, next)) return false;
3452
+ if (prev.items.length !== next.items.length) return false;
3453
+ for (let i = 0; i < prev.items.length; i += 1) {
3454
+ const prevItem = prev.items[i];
3455
+ const nextItem = next.items[i];
3456
+ if (!workflowContainerNodeVisualEqual(prevItem, nextItem)) return false;
3457
+ const loopSessionId = prevItem.loopSessionId;
3458
+ if (loopSessionId) {
3459
+ if (prev.loopTreesBySessionId[loopSessionId] !== next.loopTreesBySessionId[loopSessionId]) {
3460
+ return false;
3461
+ }
3462
+ }
3463
+ }
3464
+ return true;
3465
+ }
3466
+ var WorkflowParallelGroup = memo3(WorkflowParallelGroupInner, workflowParallelGroupPropsEqual);
2650
3467
 
2651
3468
  // src/view/workflow/WorkflowRootContainer.tsx
2652
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
3469
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2653
3470
  function WorkflowRootContainer({
2654
3471
  node,
2655
3472
  children
@@ -2658,7 +3475,7 @@ function WorkflowRootContainer({
2658
3475
  const uiStatus = mapContainerUiStatus(node.status);
2659
3476
  const statusLabel = mapWorkflowRootStatusLabel(node.status);
2660
3477
  return /* @__PURE__ */ jsxs7("div", { className: "lax-workflow-root", "data-testid": "lax-workflow-root", children: [
2661
- /* @__PURE__ */ jsx11(
3478
+ /* @__PURE__ */ jsx12(
2662
3479
  WorkflowContainerLine,
2663
3480
  {
2664
3481
  variant: "root",
@@ -2673,13 +3490,13 @@ function WorkflowRootContainer({
2673
3490
  verbose
2674
3491
  }
2675
3492
  ),
2676
- children ? /* @__PURE__ */ jsx11("div", { className: "lax-workflow-container-tree", children }) : null
3493
+ children ? /* @__PURE__ */ jsx12("div", { className: "lax-workflow-container-tree", children }) : null
2677
3494
  ] });
2678
3495
  }
2679
3496
 
2680
3497
  // src/view/workflow/WorkflowRouteContainer.tsx
2681
3498
  import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo5, useState as useState4 } from "react";
2682
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
3499
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2683
3500
  function WorkflowRouteContainer({
2684
3501
  node,
2685
3502
  loopTree,
@@ -2725,7 +3542,7 @@ function WorkflowRouteContainer({
2725
3542
  "data-container-scope": "branch",
2726
3543
  style: depthStyle,
2727
3544
  children: [
2728
- /* @__PURE__ */ jsx12(
3545
+ /* @__PURE__ */ jsx13(
2729
3546
  WorkflowContainerLine,
2730
3547
  {
2731
3548
  title: node.title,
@@ -2740,7 +3557,7 @@ function WorkflowRouteContainer({
2740
3557
  descriptionTestId: `${testId}-desc`
2741
3558
  }
2742
3559
  ),
2743
- !isSkipped && expanded && isRunning && loopTree ? /* @__PURE__ */ jsx12("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx12(
3560
+ !isSkipped && expanded && isRunning && loopTree ? /* @__PURE__ */ jsx13("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx13(
2744
3561
  AgentLoopView,
2745
3562
  {
2746
3563
  tree: loopTree,
@@ -2750,7 +3567,7 @@ function WorkflowRouteContainer({
2750
3567
  groupParallelTools
2751
3568
  }
2752
3569
  ) }) : null,
2753
- !isSkipped && isDone && !expanded ? /* @__PURE__ */ jsx12(
3570
+ !isSkipped && isDone && !expanded ? /* @__PURE__ */ jsx13(
2754
3571
  WorkflowExpandTrigger,
2755
3572
  {
2756
3573
  summary: `${node.title} \xB7 ${doneLabel}`,
@@ -2762,7 +3579,7 @@ function WorkflowRouteContainer({
2762
3579
  teaserTestId: teaserText ? `${testId}-teaser` : void 0
2763
3580
  }
2764
3581
  ) : null,
2765
- !isSkipped && isDone && expanded ? /* @__PURE__ */ jsx12(
3582
+ !isSkipped && isDone && expanded ? /* @__PURE__ */ jsx13(
2766
3583
  WorkflowStageDoneBody,
2767
3584
  {
2768
3585
  node,
@@ -2780,7 +3597,7 @@ function WorkflowRouteContainer({
2780
3597
 
2781
3598
  // src/view/workflow/WorkflowSubworkflowContainer.tsx
2782
3599
  import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo6, useState as useState5 } from "react";
2783
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
3600
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
2784
3601
  function buildDoneSummary(node) {
2785
3602
  const display = node.display;
2786
3603
  if (display?.progress_total != null) {
@@ -2826,7 +3643,7 @@ function WorkflowSubworkflowContainer({
2826
3643
  "data-container-scope": "subworkflow",
2827
3644
  style: depthStyle,
2828
3645
  children: [
2829
- showShellLine ? /* @__PURE__ */ jsx13(
3646
+ showShellLine ? /* @__PURE__ */ jsx14(
2830
3647
  WorkflowContainerLine,
2831
3648
  {
2832
3649
  variant: "root",
@@ -2844,7 +3661,7 @@ function WorkflowSubworkflowContainer({
2844
3661
  verbose
2845
3662
  }
2846
3663
  ) : null,
2847
- expanded && children ? /* @__PURE__ */ jsx13(
3664
+ expanded && children ? /* @__PURE__ */ jsx14(
2848
3665
  "div",
2849
3666
  {
2850
3667
  className: "lax-workflow-subworkflow-boundary",
@@ -2853,15 +3670,16 @@ function WorkflowSubworkflowContainer({
2853
3670
  children
2854
3671
  }
2855
3672
  ) : null,
2856
- isDone && !expanded ? /* @__PURE__ */ jsx13(
3673
+ !expanded && children ? /* @__PURE__ */ jsx14(
2857
3674
  WorkflowExpandTrigger,
2858
3675
  {
2859
- summary: `${node.title} \xB7 ${buildDoneSummary(node)}`,
3676
+ summary: `${node.title} \xB7 ${isDone ? buildDoneSummary(node) : "running"}`,
2860
3677
  summaryTestId: `${testId}-title`,
2861
3678
  teaser: teaser || void 0,
3679
+ expandHint: isRunning ? "(click to expand subworkflow)" : void 0,
2862
3680
  onToggle: () => setExpanded(true),
2863
3681
  expanded: false,
2864
- testId: `${testId}-done`,
3682
+ testId: isDone ? `${testId}-done` : `${testId}-running-collapsed`,
2865
3683
  teaserTestId: teaser ? `${testId}-teaser` : void 0
2866
3684
  }
2867
3685
  ) : null
@@ -2871,7 +3689,7 @@ function WorkflowSubworkflowContainer({
2871
3689
  }
2872
3690
 
2873
3691
  // src/view/workflow/WorkflowChrome.tsx
2874
- import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
3692
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2875
3693
  function collectChildContainers(containersById, parentId) {
2876
3694
  return Object.values(containersById).filter((node) => node.parent_container_id === parentId).sort((a, b) => a.scope_key.localeCompare(b.scope_key));
2877
3695
  }
@@ -2885,11 +3703,11 @@ function collectSiblingItems(node, containersById) {
2885
3703
  (child) => child.scope === "item"
2886
3704
  );
2887
3705
  }
2888
- function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps) {
3706
+ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps, options) {
2889
3707
  const loopTree = resolveLoopTree2(node, loopTreesBySessionId);
2890
3708
  switch (node.scope) {
2891
3709
  case "aggregate":
2892
- return /* @__PURE__ */ jsx14(
3710
+ return /* @__PURE__ */ jsx15(
2893
3711
  WorkflowAggregateContainer,
2894
3712
  {
2895
3713
  node,
@@ -2901,7 +3719,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2901
3719
  node.container_id
2902
3720
  );
2903
3721
  case "branch":
2904
- return /* @__PURE__ */ jsx14(
3722
+ return /* @__PURE__ */ jsx15(
2905
3723
  WorkflowRouteContainer,
2906
3724
  {
2907
3725
  node,
@@ -2912,12 +3730,13 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2912
3730
  node.container_id
2913
3731
  );
2914
3732
  default:
2915
- return /* @__PURE__ */ jsx14(
3733
+ return /* @__PURE__ */ jsx15(
2916
3734
  WorkflowStageContainer,
2917
3735
  {
2918
3736
  node,
2919
3737
  loopTree,
2920
3738
  depth,
3739
+ suppressHostScopedLoop: options?.suppressHostScopedLoop,
2921
3740
  ...viewProps
2922
3741
  },
2923
3742
  node.container_id
@@ -2926,7 +3745,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2926
3745
  }
2927
3746
  function renderItemSiblings(items, groupKey, depth, containerTree, loopTreesBySessionId, viewProps) {
2928
3747
  if (items.length >= 2) {
2929
- return /* @__PURE__ */ jsx14(
3748
+ return /* @__PURE__ */ jsx15(
2930
3749
  WorkflowParallelGroup,
2931
3750
  {
2932
3751
  groupKey,
@@ -2942,6 +3761,11 @@ function renderItemSiblings(items, groupKey, depth, containerTree, loopTreesBySe
2942
3761
  }
2943
3762
  return null;
2944
3763
  }
3764
+ function stageHasSubworkflowChild(stageId, containerTree) {
3765
+ return collectChildContainers(containerTree.containersById, stageId).some(
3766
+ (node) => node.scope === "subworkflow"
3767
+ );
3768
+ }
2945
3769
  function renderContainerChildren(parentId, containerTree, loopTreesBySessionId, viewProps, depth) {
2946
3770
  const children = collectChildContainers(containerTree.containersById, parentId);
2947
3771
  const items = children.filter((node) => node.scope === "item");
@@ -2961,7 +3785,7 @@ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProp
2961
3785
  );
2962
3786
  const hasNested = collectChildContainers(containerTree.containersById, node.container_id).length > 0;
2963
3787
  if (node.scope === "subworkflow") {
2964
- return /* @__PURE__ */ jsx14(
3788
+ return /* @__PURE__ */ jsx15(
2965
3789
  WorkflowSubworkflowContainer,
2966
3790
  {
2967
3791
  node,
@@ -2972,14 +3796,22 @@ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProp
2972
3796
  );
2973
3797
  }
2974
3798
  if (node.scope === "stage") {
3799
+ const suppressHostScopedLoop = stageHasSubworkflowChild(node.container_id, containerTree);
2975
3800
  return /* @__PURE__ */ jsxs10(
2976
3801
  "div",
2977
3802
  {
2978
3803
  className: "lax-workflow-nested-block",
2979
3804
  "data-testid": `lax-workflow-nested-${node.scope}-${node.scope_key}`,
2980
3805
  children: [
2981
- renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps),
2982
- hasNested ? /* @__PURE__ */ jsx14("div", { className: "lax-workflow-container-children", children: nested }) : null
3806
+ renderLeafContainer(
3807
+ node,
3808
+ depth,
3809
+ containerTree,
3810
+ loopTreesBySessionId,
3811
+ viewProps,
3812
+ { suppressHostScopedLoop }
3813
+ ),
3814
+ hasNested ? /* @__PURE__ */ jsx15("div", { className: "lax-workflow-container-children", children: nested }) : null
2983
3815
  ]
2984
3816
  },
2985
3817
  node.container_id
@@ -3005,9 +3837,9 @@ function renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewP
3005
3837
  viewProps
3006
3838
  );
3007
3839
  if (as === "div") {
3008
- return /* @__PURE__ */ jsx14("div", { className, role: "listitem", children: content }, node.container_id);
3840
+ return /* @__PURE__ */ jsx15("div", { className, role: "listitem", children: content }, node.container_id);
3009
3841
  }
3010
- return /* @__PURE__ */ jsx14("li", { className, children: content }, node.container_id);
3842
+ return /* @__PURE__ */ jsx15("li", { className, children: content }, node.container_id);
3011
3843
  }
3012
3844
  function WorkflowStageListPanel({
3013
3845
  stages,
@@ -3028,7 +3860,7 @@ function WorkflowStageListPanel({
3028
3860
  partition.totalDoneCount - partition.visibleDoneCount
3029
3861
  );
3030
3862
  return /* @__PURE__ */ jsxs10(Fragment2, { children: [
3031
- /* @__PURE__ */ jsx14("ol", { className: "lax-workflow-stage-list", "data-testid": "lax-workflow-stage-list", children: partition.visible.map(
3863
+ /* @__PURE__ */ jsx15("ol", { className: "lax-workflow-stage-list", "data-testid": "lax-workflow-stage-list", children: partition.visible.map(
3032
3864
  (node) => renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewProps)
3033
3865
  ) }),
3034
3866
  partition.hiddenCount > 0 ? /* @__PURE__ */ jsxs10(
@@ -3047,7 +3879,7 @@ function WorkflowStageListPanel({
3047
3879
  ]
3048
3880
  }
3049
3881
  ) : null,
3050
- extraCompletedPages > 0 && partition.hiddenCount === 0 ? /* @__PURE__ */ jsx14(
3882
+ extraCompletedPages > 0 && partition.hiddenCount === 0 ? /* @__PURE__ */ jsx15(
3051
3883
  "button",
3052
3884
  {
3053
3885
  type: "button",
@@ -3083,7 +3915,7 @@ function renderWorkflowChildren(workflowNode, containerTree, loopTreesBySessionI
3083
3915
  (node) => node.scope !== "stage" && node.scope !== "item"
3084
3916
  );
3085
3917
  return /* @__PURE__ */ jsxs10(Fragment2, { children: [
3086
- stages.length > 0 ? /* @__PURE__ */ jsx14(
3918
+ stages.length > 0 ? /* @__PURE__ */ jsx15(
3087
3919
  WorkflowStageListPanel,
3088
3920
  {
3089
3921
  stages,
@@ -3108,7 +3940,7 @@ function WorkflowChrome({
3108
3940
  const viewProps = { virtualized, virtualizeThreshold, groupParallelTools };
3109
3941
  const rootNodes = containerTree.rootContainerIds.map((id) => containerTree.containersById[id]).filter((node) => node != null);
3110
3942
  if (rootNodes.length === 0) {
3111
- return /* @__PURE__ */ jsx14("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: /* @__PURE__ */ jsx14(
3943
+ return /* @__PURE__ */ jsx15("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: /* @__PURE__ */ jsx15(
3112
3944
  WorkflowContainerLine,
3113
3945
  {
3114
3946
  variant: "root",
@@ -3118,7 +3950,7 @@ function WorkflowChrome({
3118
3950
  }
3119
3951
  ) });
3120
3952
  }
3121
- return /* @__PURE__ */ jsx14("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: rootNodes.map((workflowNode) => /* @__PURE__ */ jsx14(WorkflowRootContainer, { node: workflowNode, children: renderWorkflowChildren(
3953
+ return /* @__PURE__ */ jsx15("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: rootNodes.map((workflowNode) => /* @__PURE__ */ jsx15(WorkflowRootContainer, { node: workflowNode, children: renderWorkflowChildren(
3122
3954
  workflowNode,
3123
3955
  containerTree,
3124
3956
  loopTreesBySessionId,
@@ -3199,11 +4031,11 @@ function loopSessionDisplayLabel(loopSessionId) {
3199
4031
  }
3200
4032
 
3201
4033
  // src/view/workflow/WorkflowTaskListFooter.tsx
3202
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
4034
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
3203
4035
  function MessageResponse({ children }) {
3204
4036
  return /* @__PURE__ */ jsxs11("div", { className: "lax-message-response", children: [
3205
- /* @__PURE__ */ jsx15("span", { className: "lax-message-response__marker", children: "\u23BF " }),
3206
- /* @__PURE__ */ jsx15("span", { className: "lax-message-response__content", children })
4037
+ /* @__PURE__ */ jsx16("span", { className: "lax-message-response__marker", children: "\u23BF " }),
4038
+ /* @__PURE__ */ jsx16("span", { className: "lax-message-response__content", children })
3207
4039
  ] });
3208
4040
  }
3209
4041
  function WorkflowTaskListFooter() {
@@ -3228,18 +4060,18 @@ function WorkflowTaskListFooter() {
3228
4060
  onClick: () => setExpanded((v) => !v),
3229
4061
  children: [
3230
4062
  label,
3231
- /* @__PURE__ */ jsx15("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
4063
+ /* @__PURE__ */ jsx16("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
3232
4064
  ]
3233
4065
  }
3234
4066
  ),
3235
- expanded ? /* @__PURE__ */ jsx15(MessageResponse, { children: /* @__PURE__ */ jsx15("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs11(
4067
+ expanded ? /* @__PURE__ */ jsx16(MessageResponse, { children: /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs11(
3236
4068
  "div",
3237
4069
  {
3238
4070
  className: "lax-workflow-task-list-group",
3239
4071
  "data-testid": `lax-workflow-task-group-${group.loopSessionId}`,
3240
4072
  children: [
3241
- /* @__PURE__ */ jsx15("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
3242
- /* @__PURE__ */ jsx15(TaskList, { tasks: group.tasks })
4073
+ /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
4074
+ /* @__PURE__ */ jsx16(TaskList, { tasks: group.tasks })
3243
4075
  ]
3244
4076
  },
3245
4077
  group.loopSessionId
@@ -3249,7 +4081,7 @@ function WorkflowTaskListFooter() {
3249
4081
 
3250
4082
  // src/view/workflow/WorkflowSession.tsx
3251
4083
  import { useStore as useStore5 } from "zustand";
3252
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4084
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
3253
4085
  function WorkflowSession({
3254
4086
  source,
3255
4087
  initialEvents,
@@ -3328,6 +4160,14 @@ function WorkflowSession({
3328
4160
  storeRef.current,
3329
4161
  (s) => s.state.loopTreesBySessionId
3330
4162
  );
4163
+ const sessionStatus = useStore5(
4164
+ storeRef.current,
4165
+ (s) => s.state.status
4166
+ );
4167
+ const internalErrors = useStore5(
4168
+ storeRef.current,
4169
+ (s) => s.state.internalErrors
4170
+ );
3331
4171
  useEffect8(() => {
3332
4172
  const store = storeRef.current;
3333
4173
  const controller = new AbortController();
@@ -3335,23 +4175,32 @@ function WorkflowSession({
3335
4175
  store.getState().applyEvent(event, ctx);
3336
4176
  }, controller.signal).catch((err) => {
3337
4177
  if (err instanceof SseTransportTerminalError) {
3338
- const { state } = store.getState();
3339
- if (state.status === "running") {
3340
- store.setState({ state: { ...state, status: "error" } });
3341
- }
4178
+ store.getState().markAsError(err);
4179
+ onError?.(err);
4180
+ } else if (err instanceof Error) {
4181
+ onError?.(err);
3342
4182
  }
3343
4183
  });
3344
4184
  return () => {
3345
4185
  controller.abort();
3346
4186
  };
3347
4187
  }, [source, onError]);
3348
- return /* @__PURE__ */ jsx16(WorkflowSessionStoreContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx16(WorkflowScaleContext.Provider, { value: scaleOptions, children: /* @__PURE__ */ jsx16(WorkflowLoopReplayProvider, { onRequestLoopReplay, children: /* @__PURE__ */ jsx16(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx16(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx16(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx16(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx16(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs12(
4188
+ return /* @__PURE__ */ jsx17(WorkflowSessionStoreContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx17(WorkflowScaleContext.Provider, { value: scaleOptions, children: /* @__PURE__ */ jsx17(WorkflowLoopReplayProvider, { onRequestLoopReplay, children: /* @__PURE__ */ jsx17(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx17(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx17(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx17(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx17(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs12(
3349
4189
  "div",
3350
4190
  {
3351
4191
  className: "lax-agent-session lax-workflow-session",
3352
4192
  "data-testid": "lax-workflow-session",
3353
4193
  children: [
3354
- /* @__PURE__ */ jsx16(
4194
+ sessionStatus === "error" || internalErrors.length > 0 ? /* @__PURE__ */ jsx17(
4195
+ "div",
4196
+ {
4197
+ className: "lax-workflow-error",
4198
+ "data-testid": "lax-workflow-error",
4199
+ role: "alert",
4200
+ children: internalErrors[internalErrors.length - 1]?.message ?? "Workflow stream error"
4201
+ }
4202
+ ) : null,
4203
+ /* @__PURE__ */ jsx17(
3355
4204
  WorkflowChrome,
3356
4205
  {
3357
4206
  containerTree,
@@ -3361,7 +4210,7 @@ function WorkflowSession({
3361
4210
  groupParallelTools
3362
4211
  }
3363
4212
  ),
3364
- /* @__PURE__ */ jsx16(WorkflowTaskListFooter, {}),
4213
+ /* @__PURE__ */ jsx17(WorkflowTaskListFooter, {}),
3365
4214
  children
3366
4215
  ]
3367
4216
  }
@@ -3405,7 +4254,7 @@ function buildTimelineEntries(rootIds, byId, options = {}) {
3405
4254
  }
3406
4255
 
3407
4256
  // src/view/nodes/SubAgentNode.tsx
3408
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
4257
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
3409
4258
  function SubAgentBlock({ nodeId }) {
3410
4259
  const node = useNodeTyped(nodeId, "subagent");
3411
4260
  const childIds = useChildren(nodeId);
@@ -3416,7 +4265,7 @@ function SubAgentBlock({ nodeId }) {
3416
4265
  "SubAgent: ",
3417
4266
  node.subagentId
3418
4267
  ] }),
3419
- /* @__PURE__ */ jsx17("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx17(TimelineItem, { nodeId: childId, registry }, childId)) })
4268
+ /* @__PURE__ */ jsx18("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx18(TimelineItem, { nodeId: childId, registry }, childId)) })
3420
4269
  ] });
3421
4270
  }
3422
4271