langchain_agentx_stream_ui 0.1.9 → 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.d.ts +10 -2
- package/dist/index.js +490 -101
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1169,17 +1169,24 @@ var WorkflowContainerStateMachine = class {
|
|
|
1169
1169
|
(node) => workflowPath === node.workflow_path || workflowPath.startsWith(`${node.workflow_path}>`)
|
|
1170
1170
|
);
|
|
1171
1171
|
if (prefixCandidates.length > 0) {
|
|
1172
|
-
const
|
|
1173
|
-
(
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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
|
+
}
|
|
1183
1190
|
}
|
|
1184
1191
|
}
|
|
1185
1192
|
const workflowCandidates = openNodes.filter((node) => node.scope === "workflow");
|
|
@@ -1277,6 +1284,167 @@ function loopTaskScopeForStructureOpen(eventType) {
|
|
|
1277
1284
|
return LOOP_TASK_OPEN_SCOPES[eventType];
|
|
1278
1285
|
}
|
|
1279
1286
|
|
|
1287
|
+
// src/core/workflowStructureEvents.ts
|
|
1288
|
+
var WORKFLOW_STRUCTURE_EVENT_PREFIXES = [
|
|
1289
|
+
"workflow-",
|
|
1290
|
+
"stage-",
|
|
1291
|
+
"parallel-",
|
|
1292
|
+
"route-",
|
|
1293
|
+
"subworkflow-"
|
|
1294
|
+
];
|
|
1295
|
+
var WORKFLOW_STRUCTURE_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1296
|
+
"workflow-start",
|
|
1297
|
+
"workflow-end",
|
|
1298
|
+
"workflow-failed",
|
|
1299
|
+
"subworkflow-start",
|
|
1300
|
+
"subworkflow-end",
|
|
1301
|
+
"stage-start",
|
|
1302
|
+
"stage-done",
|
|
1303
|
+
"stage-failed",
|
|
1304
|
+
"parallel-item-start",
|
|
1305
|
+
"parallel-item-done",
|
|
1306
|
+
"parallel-item-failed",
|
|
1307
|
+
"parallel-aggregate-start",
|
|
1308
|
+
"parallel-aggregate-done",
|
|
1309
|
+
"parallel-aggregate-failed",
|
|
1310
|
+
"route-branch-start",
|
|
1311
|
+
"route-branch-done",
|
|
1312
|
+
"route-branch-failed"
|
|
1313
|
+
]);
|
|
1314
|
+
var AGENT_LOOP_REPLAY_EVENT_TYPES = new Set(
|
|
1315
|
+
Object.keys(DEFAULT_EVENT_TIERS)
|
|
1316
|
+
);
|
|
1317
|
+
function isWorkflowStructureEventType(eventType) {
|
|
1318
|
+
if (WORKFLOW_STRUCTURE_EVENT_TYPES.has(eventType)) return true;
|
|
1319
|
+
return WORKFLOW_STRUCTURE_EVENT_PREFIXES.some((prefix) => eventType.startsWith(prefix));
|
|
1320
|
+
}
|
|
1321
|
+
function isAgentLoopReplayEventType(eventType) {
|
|
1322
|
+
return AGENT_LOOP_REPLAY_EVENT_TYPES.has(eventType);
|
|
1323
|
+
}
|
|
1324
|
+
function filterAgentLoopReplayEvents(events) {
|
|
1325
|
+
return events.filter(
|
|
1326
|
+
(event) => isAgentLoopReplayEventType(event.event_type) && !isWorkflowStructureEventType(event.event_type)
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
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
|
|
1353
|
+
function readString3(data, key) {
|
|
1354
|
+
const value = data[key];
|
|
1355
|
+
return typeof value === "string" ? value : void 0;
|
|
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
|
+
|
|
1280
1448
|
// src/core/workflow/embeddedSubworkflowUtils.ts
|
|
1281
1449
|
function parseLoopSessionParts(sessionId) {
|
|
1282
1450
|
const trimmed = sessionId.trim();
|
|
@@ -1378,7 +1546,20 @@ function completeOpenItemsUnderSubworkflow(tree, subworkflowId) {
|
|
|
1378
1546
|
function resolveLeafScope(taskKey) {
|
|
1379
1547
|
return taskKey === "aggregate" ? "aggregate" : "item";
|
|
1380
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
|
+
}
|
|
1381
1559
|
function ensureEmbeddedSubworkflowContainers(tree, loopSessionId) {
|
|
1560
|
+
if (!shouldUseEmbeddedSynthesis(tree)) {
|
|
1561
|
+
return tree;
|
|
1562
|
+
}
|
|
1382
1563
|
const root = resolveRootWorkflow(tree);
|
|
1383
1564
|
const parts = parseLoopSessionParts(loopSessionId);
|
|
1384
1565
|
if (!root || !parts || !isEmbeddedChildLoopSession(loopSessionId, root.workflowId)) {
|
|
@@ -1461,11 +1642,39 @@ function findContainerIdByLoopSession(tree, loopSessionId) {
|
|
|
1461
1642
|
}
|
|
1462
1643
|
return null;
|
|
1463
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
|
+
}
|
|
1464
1664
|
function resolveStructureOpenLoopSessionId(eventType, eventSessionId, derivedLoopSessionId, data) {
|
|
1465
1665
|
const rootWorkflowId = readWorkflowId(data) ?? "";
|
|
1466
1666
|
if ((eventType === "parallel-aggregate-start" || eventType === "parallel-aggregate-done" || eventType === "parallel-aggregate-failed") && eventSessionId && isEmbeddedChildLoopSession(eventSessionId, rootWorkflowId)) {
|
|
1467
1667
|
return eventSessionId;
|
|
1468
1668
|
}
|
|
1669
|
+
const parallelItemSessionId = resolveParallelItemLoopSessionId(
|
|
1670
|
+
eventType,
|
|
1671
|
+
eventSessionId,
|
|
1672
|
+
derivedLoopSessionId,
|
|
1673
|
+
data
|
|
1674
|
+
);
|
|
1675
|
+
if (parallelItemSessionId) {
|
|
1676
|
+
return parallelItemSessionId;
|
|
1677
|
+
}
|
|
1469
1678
|
return derivedLoopSessionId;
|
|
1470
1679
|
}
|
|
1471
1680
|
var EMBEDDED_AGGREGATE_STRUCTURE_EVENTS = /* @__PURE__ */ new Set([
|
|
@@ -1483,7 +1692,7 @@ function patchEmbeddedStructureEvent(event, rootWorkflowId) {
|
|
|
1483
1692
|
const parts = parseLoopSessionParts(event.session_id);
|
|
1484
1693
|
if (!parts) return event;
|
|
1485
1694
|
const data = event.data ?? {};
|
|
1486
|
-
const rootPath =
|
|
1695
|
+
const rootPath = readString4(data, "workflow_path") ?? rootWorkflowId;
|
|
1487
1696
|
const childPath = `${rootPath}>${parts.workflowId}`;
|
|
1488
1697
|
const workflowDepth = typeof data.workflow_depth === "number" ? data.workflow_depth + 1 : 1;
|
|
1489
1698
|
return {
|
|
@@ -1495,14 +1704,17 @@ function patchEmbeddedStructureEvent(event, rootWorkflowId) {
|
|
|
1495
1704
|
}
|
|
1496
1705
|
};
|
|
1497
1706
|
}
|
|
1498
|
-
function
|
|
1707
|
+
function readString4(data, key) {
|
|
1499
1708
|
const value = data[key];
|
|
1500
1709
|
return typeof value === "string" ? value : void 0;
|
|
1501
1710
|
}
|
|
1502
1711
|
function isDescendantOf(containersById, ancestorId, nodeId) {
|
|
1712
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1503
1713
|
let current = containersById[nodeId]?.parent_container_id ?? null;
|
|
1504
1714
|
while (current) {
|
|
1505
1715
|
if (current === ancestorId) return true;
|
|
1716
|
+
if (seen.has(current)) return false;
|
|
1717
|
+
seen.add(current);
|
|
1506
1718
|
current = containersById[current]?.parent_container_id ?? null;
|
|
1507
1719
|
}
|
|
1508
1720
|
return false;
|
|
@@ -1530,51 +1742,8 @@ function readWorkflowId(data) {
|
|
|
1530
1742
|
return readWorkflowIdFromData(data);
|
|
1531
1743
|
}
|
|
1532
1744
|
|
|
1533
|
-
// src/core/workflowStructureEvents.ts
|
|
1534
|
-
var WORKFLOW_STRUCTURE_EVENT_PREFIXES = [
|
|
1535
|
-
"workflow-",
|
|
1536
|
-
"stage-",
|
|
1537
|
-
"parallel-",
|
|
1538
|
-
"route-",
|
|
1539
|
-
"subworkflow-"
|
|
1540
|
-
];
|
|
1541
|
-
var WORKFLOW_STRUCTURE_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1542
|
-
"workflow-start",
|
|
1543
|
-
"workflow-end",
|
|
1544
|
-
"workflow-failed",
|
|
1545
|
-
"subworkflow-start",
|
|
1546
|
-
"subworkflow-end",
|
|
1547
|
-
"stage-start",
|
|
1548
|
-
"stage-done",
|
|
1549
|
-
"stage-failed",
|
|
1550
|
-
"parallel-item-start",
|
|
1551
|
-
"parallel-item-done",
|
|
1552
|
-
"parallel-item-failed",
|
|
1553
|
-
"parallel-aggregate-start",
|
|
1554
|
-
"parallel-aggregate-done",
|
|
1555
|
-
"parallel-aggregate-failed",
|
|
1556
|
-
"route-branch-start",
|
|
1557
|
-
"route-branch-done",
|
|
1558
|
-
"route-branch-failed"
|
|
1559
|
-
]);
|
|
1560
|
-
var AGENT_LOOP_REPLAY_EVENT_TYPES = new Set(
|
|
1561
|
-
Object.keys(DEFAULT_EVENT_TIERS)
|
|
1562
|
-
);
|
|
1563
|
-
function isWorkflowStructureEventType(eventType) {
|
|
1564
|
-
if (WORKFLOW_STRUCTURE_EVENT_TYPES.has(eventType)) return true;
|
|
1565
|
-
return WORKFLOW_STRUCTURE_EVENT_PREFIXES.some((prefix) => eventType.startsWith(prefix));
|
|
1566
|
-
}
|
|
1567
|
-
function isAgentLoopReplayEventType(eventType) {
|
|
1568
|
-
return AGENT_LOOP_REPLAY_EVENT_TYPES.has(eventType);
|
|
1569
|
-
}
|
|
1570
|
-
function filterAgentLoopReplayEvents(events) {
|
|
1571
|
-
return events.filter(
|
|
1572
|
-
(event) => isAgentLoopReplayEventType(event.event_type) && !isWorkflowStructureEventType(event.event_type)
|
|
1573
|
-
);
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
1745
|
// src/core/workflowReducer.ts
|
|
1577
|
-
function
|
|
1746
|
+
function readString5(data, key) {
|
|
1578
1747
|
const value = data[key];
|
|
1579
1748
|
return typeof value === "string" ? value : void 0;
|
|
1580
1749
|
}
|
|
@@ -1673,7 +1842,7 @@ function prebindLoopSessionFromStructureOpen(tree, event) {
|
|
|
1673
1842
|
loopSessionId = derivedLoopSessionId;
|
|
1674
1843
|
}
|
|
1675
1844
|
let nextTree = event.event_type === "parallel-aggregate-start" ? ensureEmbeddedSubworkflowContainers(tree, loopSessionId) : tree;
|
|
1676
|
-
const workflowPath =
|
|
1845
|
+
const workflowPath = readString5(data, "workflow_path") ?? "";
|
|
1677
1846
|
const scopeKey = resolveScopeKey(event.event_type, data);
|
|
1678
1847
|
const containerId = buildContainerId(workflowPath, scope, scopeKey);
|
|
1679
1848
|
let targetId = containerId;
|
|
@@ -1754,7 +1923,7 @@ function bindLoopSessionToContainerTree(tree, event, activeLoopSessionId) {
|
|
|
1754
1923
|
let nextTree = ensureEmbeddedSubworkflowContainers(tree, sessionId);
|
|
1755
1924
|
if (findContainerByLoopSession(nextTree, sessionId)) return nextTree;
|
|
1756
1925
|
const data = event.data ?? {};
|
|
1757
|
-
const workflowPath =
|
|
1926
|
+
const workflowPath = readString5(data, "workflow_path") ?? null;
|
|
1758
1927
|
const scopeKey = resolveContentScopeKey(data);
|
|
1759
1928
|
let targetId = findContainerByLoopSession(nextTree, sessionId);
|
|
1760
1929
|
if (!targetId && scopeKey) {
|
|
@@ -1765,12 +1934,17 @@ function bindLoopSessionToContainerTree(tree, event, activeLoopSessionId) {
|
|
|
1765
1934
|
);
|
|
1766
1935
|
}
|
|
1767
1936
|
if (!targetId) {
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
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;
|
|
1771
1944
|
targetId = containerId;
|
|
1772
1945
|
break;
|
|
1773
1946
|
}
|
|
1947
|
+
if (targetId) break;
|
|
1774
1948
|
}
|
|
1775
1949
|
}
|
|
1776
1950
|
if (!targetId) {
|
|
@@ -1783,6 +1957,9 @@ function bindLoopSessionToContainerTree(tree, event, activeLoopSessionId) {
|
|
|
1783
1957
|
if (!targetId) return nextTree;
|
|
1784
1958
|
const node = nextTree.containersById[targetId];
|
|
1785
1959
|
if (!node || node.loopSessionId) return nextTree;
|
|
1960
|
+
if (node.scope === "stage" && isNestedHostStageShell(nextTree, node)) {
|
|
1961
|
+
return nextTree;
|
|
1962
|
+
}
|
|
1786
1963
|
if (node.scope !== "stage" && node.scope !== "item" && node.scope !== "branch" && node.scope !== "aggregate") {
|
|
1787
1964
|
return nextTree;
|
|
1788
1965
|
}
|
|
@@ -1882,8 +2059,15 @@ function finalizeLoopTreeOnStructureClose(state, event) {
|
|
|
1882
2059
|
}
|
|
1883
2060
|
function applyStructureEvent(state, event) {
|
|
1884
2061
|
const rootWorkflowId = resolveRootWorkflowId(state.containerTree);
|
|
1885
|
-
const
|
|
2062
|
+
const useEmbeddedSynthesis = shouldUseEmbeddedSynthesis(state, event);
|
|
2063
|
+
const projectedEvent = rootWorkflowId != null && useEmbeddedSynthesis ? patchEmbeddedStructureEvent(event, rootWorkflowId) : event;
|
|
1886
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
|
+
}
|
|
1887
2071
|
const derivedLoopSessionId = deriveLoopSessionIdFromStructureOpen(projectedEvent);
|
|
1888
2072
|
const structureData = projectedEvent.data ?? {};
|
|
1889
2073
|
const structureLoopSessionId = derivedLoopSessionId ? resolveStructureOpenLoopSessionId(
|
|
@@ -1892,13 +2076,13 @@ function applyStructureEvent(state, event) {
|
|
|
1892
2076
|
derivedLoopSessionId,
|
|
1893
2077
|
structureData
|
|
1894
2078
|
) : null;
|
|
1895
|
-
if (structureLoopSessionId) {
|
|
2079
|
+
if (structureLoopSessionId && useEmbeddedSynthesis) {
|
|
1896
2080
|
containerTree = ensureEmbeddedSubworkflowContainers(containerTree, structureLoopSessionId);
|
|
1897
2081
|
}
|
|
1898
2082
|
containerTree = prebindLoopSessionFromStructureOpen(containerTree, projectedEvent);
|
|
1899
2083
|
if (projectedEvent.event_type === "stage-done" || projectedEvent.event_type === "stage-failed") {
|
|
1900
|
-
const stageKey =
|
|
1901
|
-
const workflowPath =
|
|
2084
|
+
const stageKey = readString5(structureData, "stage_key");
|
|
2085
|
+
const workflowPath = readString5(structureData, "workflow_path") ?? "";
|
|
1902
2086
|
if (stageKey) {
|
|
1903
2087
|
const stageId = buildContainerId(workflowPath, "stage", stageKey);
|
|
1904
2088
|
if (containerTree.containersById[stageId]) {
|
|
@@ -2154,6 +2338,18 @@ function evictCompletedLoopOverflow(state, maxHydratedCompletedLoops) {
|
|
|
2154
2338
|
}
|
|
2155
2339
|
|
|
2156
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
|
+
}
|
|
2157
2353
|
function safeReduceWorkflow(state, event, eventIndex, options) {
|
|
2158
2354
|
try {
|
|
2159
2355
|
return { state: reduceWorkflowSession(state, event, eventIndex, options) };
|
|
@@ -2174,6 +2370,39 @@ function safeReduceWorkflow(state, event, eventIndex, options) {
|
|
|
2174
2370
|
};
|
|
2175
2371
|
}
|
|
2176
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
|
+
}
|
|
2177
2406
|
function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionState(), storeOptions) {
|
|
2178
2407
|
const reduceOpts = {
|
|
2179
2408
|
tierOverrides: storeOptions?.tierOverrides
|
|
@@ -2190,6 +2419,7 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
|
|
|
2190
2419
|
return createStore2((set, get) => ({
|
|
2191
2420
|
state: initialState,
|
|
2192
2421
|
eventCount: initialEventCount,
|
|
2422
|
+
hydratingLoopSessionIds: {},
|
|
2193
2423
|
applyEvent(event, ctx) {
|
|
2194
2424
|
const sseEventId = ctx?.sseEventId;
|
|
2195
2425
|
if (shouldSkipDuplicateEvent(seenEventIds, sseEventId)) return;
|
|
@@ -2216,26 +2446,20 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
|
|
|
2216
2446
|
seenEventIds.clear();
|
|
2217
2447
|
set({
|
|
2218
2448
|
state: createEmptyWorkflowSessionState(),
|
|
2219
|
-
eventCount: 0
|
|
2449
|
+
eventCount: 0,
|
|
2450
|
+
hydratingLoopSessionIds: {}
|
|
2220
2451
|
});
|
|
2221
2452
|
},
|
|
2222
2453
|
markAsError(error) {
|
|
2223
|
-
const { state } = get();
|
|
2454
|
+
const { state, eventCount } = get();
|
|
2224
2455
|
const message = error?.message ?? "Workflow stream error";
|
|
2225
2456
|
set({
|
|
2226
|
-
state:
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
eventIndex: get().eventCount,
|
|
2233
|
-
eventType: "stream-error",
|
|
2234
|
-
message,
|
|
2235
|
-
stack: error?.stack
|
|
2236
|
-
}
|
|
2237
|
-
]
|
|
2238
|
-
}
|
|
2457
|
+
state: finalizeWorkflowSessionOnStreamError(
|
|
2458
|
+
state,
|
|
2459
|
+
message,
|
|
2460
|
+
eventCount,
|
|
2461
|
+
error?.stack
|
|
2462
|
+
)
|
|
2239
2463
|
});
|
|
2240
2464
|
},
|
|
2241
2465
|
/**
|
|
@@ -2275,6 +2499,71 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
|
|
|
2275
2499
|
next = finalizeState(next);
|
|
2276
2500
|
return { state: next };
|
|
2277
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
|
+
}));
|
|
2278
2567
|
}
|
|
2279
2568
|
}));
|
|
2280
2569
|
}
|
|
@@ -2283,7 +2572,7 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
|
|
|
2283
2572
|
import { useMemo as useMemo7, useState as useState6 } from "react";
|
|
2284
2573
|
|
|
2285
2574
|
// src/view/workflow/WorkflowAggregateContainer.tsx
|
|
2286
|
-
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState } from "react";
|
|
2575
|
+
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState, memo } from "react";
|
|
2287
2576
|
|
|
2288
2577
|
// src/view/workflow/WorkflowContainerLine.tsx
|
|
2289
2578
|
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -2551,6 +2840,29 @@ function mapAggregateUiStatus(node) {
|
|
|
2551
2840
|
return mapContainerUiStatus(node.status);
|
|
2552
2841
|
}
|
|
2553
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
|
+
|
|
2554
2866
|
// src/view/workflow/WorkflowAggregateContainer.tsx
|
|
2555
2867
|
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2556
2868
|
function mergeContentPreview(node) {
|
|
@@ -2558,7 +2870,7 @@ function mergeContentPreview(node) {
|
|
|
2558
2870
|
if (blocks.length === 0) return "";
|
|
2559
2871
|
return blocks.map((block) => block.preview?.trim() ?? "").filter(Boolean).join("\n\n");
|
|
2560
2872
|
}
|
|
2561
|
-
function
|
|
2873
|
+
function WorkflowAggregateContainerInner({
|
|
2562
2874
|
node,
|
|
2563
2875
|
loopTree,
|
|
2564
2876
|
siblingItems = [],
|
|
@@ -2678,9 +2990,24 @@ function WorkflowAggregateContainer({
|
|
|
2678
2990
|
}
|
|
2679
2991
|
);
|
|
2680
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
|
+
);
|
|
2681
3008
|
|
|
2682
3009
|
// src/view/workflow/WorkflowParallelGroup.tsx
|
|
2683
|
-
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";
|
|
2684
3011
|
import { useStore as useStore3 } from "zustand";
|
|
2685
3012
|
|
|
2686
3013
|
// src/core/workflow/workflowParallelGroupMetrics.ts
|
|
@@ -2747,7 +3074,7 @@ function formatParallelGroupSummary(metrics) {
|
|
|
2747
3074
|
}
|
|
2748
3075
|
|
|
2749
3076
|
// src/view/workflow/WorkflowStageContainer.tsx
|
|
2750
|
-
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";
|
|
2751
3078
|
import { useStore as useStore2 } from "zustand";
|
|
2752
3079
|
|
|
2753
3080
|
// src/core/workflow/loopTreeUtils.ts
|
|
@@ -2822,6 +3149,9 @@ function partitionWorkflowStages(stages, maxVisibleDoneCount) {
|
|
|
2822
3149
|
}
|
|
2823
3150
|
function isActiveWorkflowContainer(node, activeLoopSessionId, activeContainerIds) {
|
|
2824
3151
|
if (activeContainerIds.includes(node.container_id)) return true;
|
|
3152
|
+
if (node.status === "completed" || node.status === "failed" || node.status === "skipped") {
|
|
3153
|
+
return false;
|
|
3154
|
+
}
|
|
2825
3155
|
if (node.loopSessionId && node.loopSessionId === activeLoopSessionId) return true;
|
|
2826
3156
|
return false;
|
|
2827
3157
|
}
|
|
@@ -2837,13 +3167,14 @@ function bodyTestId(node) {
|
|
|
2837
3167
|
if (node.scope === "item") return `lax-workflow-parallel-body-${node.scope_key}`;
|
|
2838
3168
|
return `lax-workflow-stage-body-${node.scope_key}`;
|
|
2839
3169
|
}
|
|
2840
|
-
function
|
|
3170
|
+
function WorkflowStageContainerInner({
|
|
2841
3171
|
node,
|
|
2842
3172
|
loopTree,
|
|
2843
3173
|
depth = 0,
|
|
2844
3174
|
virtualized,
|
|
2845
3175
|
virtualizeThreshold,
|
|
2846
|
-
groupParallelTools
|
|
3176
|
+
groupParallelTools,
|
|
3177
|
+
suppressHostScopedLoop = false
|
|
2847
3178
|
}) {
|
|
2848
3179
|
const { verbose } = useSessionViewOptions();
|
|
2849
3180
|
const scale = useWorkflowScaleOptions();
|
|
@@ -2906,13 +3237,13 @@ function WorkflowStageContainer({
|
|
|
2906
3237
|
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2907
3238
|
}, [requestLoopHydration]);
|
|
2908
3239
|
const displayTitle = node.title;
|
|
2909
|
-
const usesScopedLoop = Boolean(node.loopSessionId);
|
|
3240
|
+
const usesScopedLoop = Boolean(node.loopSessionId) && !suppressHostScopedLoop;
|
|
2910
3241
|
const runningLoopTree = loopTree ?? (usesScopedLoop && isRunning && isActive ? createWorkflowRunningLoopTree() : void 0);
|
|
2911
3242
|
const staticContentTeaser = !usesScopedLoop && !loopTree && node.content_blocks.length > 0 ? node.content_blocks[node.content_blocks.length - 1].preview : "";
|
|
2912
3243
|
const teaserText = doneSummary?.teaser || staticContentTeaser;
|
|
2913
3244
|
const expandHint = doneSummary ? formatTeaserExpandHint(doneSummary.extraLines, verbose) : null;
|
|
2914
3245
|
const doneLabel = doneSummary?.label ?? "done";
|
|
2915
|
-
const statusLabel = isDone ? doneLabel : mapContainerStatusLabel(node.status);
|
|
3246
|
+
const statusLabel = isDone ? doneLabel : suppressHostScopedLoop && isRunning ? "running" : mapContainerStatusLabel(node.status);
|
|
2916
3247
|
const testId = containerTestId(node);
|
|
2917
3248
|
const isItem = node.scope === "item";
|
|
2918
3249
|
const canToggleShell = isRunning && !isSkipped || isDone;
|
|
@@ -3016,6 +3347,13 @@ function WorkflowStageContainer({
|
|
|
3016
3347
|
}
|
|
3017
3348
|
);
|
|
3018
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);
|
|
3019
3357
|
|
|
3020
3358
|
// src/view/workflow/WorkflowParallelGroup.tsx
|
|
3021
3359
|
import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
@@ -3023,7 +3361,7 @@ function resolveLoopTree(node, loopTreesBySessionId) {
|
|
|
3023
3361
|
if (!node.loopSessionId) return void 0;
|
|
3024
3362
|
return loopTreesBySessionId[node.loopSessionId];
|
|
3025
3363
|
}
|
|
3026
|
-
function
|
|
3364
|
+
function WorkflowParallelGroupInner({
|
|
3027
3365
|
groupKey,
|
|
3028
3366
|
items,
|
|
3029
3367
|
loopTreesBySessionId,
|
|
@@ -3039,12 +3377,13 @@ function WorkflowParallelGroup({
|
|
|
3039
3377
|
const metrics = useMemo4(() => computeParallelGroupMetrics(items), [items]);
|
|
3040
3378
|
const summary = formatParallelGroupSummary(metrics);
|
|
3041
3379
|
const hasRunning = metrics.running > 0;
|
|
3380
|
+
const isNestedGroup = groupKey.includes(">");
|
|
3042
3381
|
const hasActiveItem = useMemo4(
|
|
3043
3382
|
() => items.some((item) => isActiveWorkflowContainer(item, activeLoopSessionId, activeContainerIds)),
|
|
3044
3383
|
[items, activeLoopSessionId, activeContainerIds]
|
|
3045
3384
|
);
|
|
3046
3385
|
const allSettled = metrics.total > 0 && metrics.running === 0 && metrics.completed + metrics.failed >= metrics.total;
|
|
3047
|
-
const shouldAutoExpandGroup = hasRunning && (!scale.onlyExpandActiveRunning || hasActiveItem);
|
|
3386
|
+
const shouldAutoExpandGroup = hasRunning && (isNestedGroup || !scale.onlyExpandActiveRunning || hasActiveItem);
|
|
3048
3387
|
const [expanded, setExpanded] = useState3(shouldAutoExpandGroup);
|
|
3049
3388
|
useEffect5(() => {
|
|
3050
3389
|
if (shouldAutoExpandGroup) {
|
|
@@ -3095,7 +3434,7 @@ function WorkflowParallelGroup({
|
|
|
3095
3434
|
WorkflowExpandTrigger,
|
|
3096
3435
|
{
|
|
3097
3436
|
summary,
|
|
3098
|
-
expandHint: !expanded && !hasRunning ? "(ctrl+o to expand)" : null,
|
|
3437
|
+
expandHint: !expanded && hasRunning ? "(click to expand agents)" : !expanded && !hasRunning ? "(ctrl+o to expand)" : null,
|
|
3099
3438
|
onToggle: toggleExpanded,
|
|
3100
3439
|
expanded,
|
|
3101
3440
|
testId: `lax-workflow-parallel-group-summary-${groupKey}`,
|
|
@@ -3107,6 +3446,24 @@ function WorkflowParallelGroup({
|
|
|
3107
3446
|
}
|
|
3108
3447
|
);
|
|
3109
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);
|
|
3110
3467
|
|
|
3111
3468
|
// src/view/workflow/WorkflowRootContainer.tsx
|
|
3112
3469
|
import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
@@ -3313,15 +3670,16 @@ function WorkflowSubworkflowContainer({
|
|
|
3313
3670
|
children
|
|
3314
3671
|
}
|
|
3315
3672
|
) : null,
|
|
3316
|
-
|
|
3673
|
+
!expanded && children ? /* @__PURE__ */ jsx14(
|
|
3317
3674
|
WorkflowExpandTrigger,
|
|
3318
3675
|
{
|
|
3319
|
-
summary: `${node.title} \xB7 ${buildDoneSummary(node)}`,
|
|
3676
|
+
summary: `${node.title} \xB7 ${isDone ? buildDoneSummary(node) : "running"}`,
|
|
3320
3677
|
summaryTestId: `${testId}-title`,
|
|
3321
3678
|
teaser: teaser || void 0,
|
|
3679
|
+
expandHint: isRunning ? "(click to expand subworkflow)" : void 0,
|
|
3322
3680
|
onToggle: () => setExpanded(true),
|
|
3323
3681
|
expanded: false,
|
|
3324
|
-
testId: `${testId}-done`,
|
|
3682
|
+
testId: isDone ? `${testId}-done` : `${testId}-running-collapsed`,
|
|
3325
3683
|
teaserTestId: teaser ? `${testId}-teaser` : void 0
|
|
3326
3684
|
}
|
|
3327
3685
|
) : null
|
|
@@ -3345,7 +3703,7 @@ function collectSiblingItems(node, containersById) {
|
|
|
3345
3703
|
(child) => child.scope === "item"
|
|
3346
3704
|
);
|
|
3347
3705
|
}
|
|
3348
|
-
function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps) {
|
|
3706
|
+
function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps, options) {
|
|
3349
3707
|
const loopTree = resolveLoopTree2(node, loopTreesBySessionId);
|
|
3350
3708
|
switch (node.scope) {
|
|
3351
3709
|
case "aggregate":
|
|
@@ -3378,6 +3736,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
|
|
|
3378
3736
|
node,
|
|
3379
3737
|
loopTree,
|
|
3380
3738
|
depth,
|
|
3739
|
+
suppressHostScopedLoop: options?.suppressHostScopedLoop,
|
|
3381
3740
|
...viewProps
|
|
3382
3741
|
},
|
|
3383
3742
|
node.container_id
|
|
@@ -3402,6 +3761,11 @@ function renderItemSiblings(items, groupKey, depth, containerTree, loopTreesBySe
|
|
|
3402
3761
|
}
|
|
3403
3762
|
return null;
|
|
3404
3763
|
}
|
|
3764
|
+
function stageHasSubworkflowChild(stageId, containerTree) {
|
|
3765
|
+
return collectChildContainers(containerTree.containersById, stageId).some(
|
|
3766
|
+
(node) => node.scope === "subworkflow"
|
|
3767
|
+
);
|
|
3768
|
+
}
|
|
3405
3769
|
function renderContainerChildren(parentId, containerTree, loopTreesBySessionId, viewProps, depth) {
|
|
3406
3770
|
const children = collectChildContainers(containerTree.containersById, parentId);
|
|
3407
3771
|
const items = children.filter((node) => node.scope === "item");
|
|
@@ -3432,13 +3796,21 @@ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProp
|
|
|
3432
3796
|
);
|
|
3433
3797
|
}
|
|
3434
3798
|
if (node.scope === "stage") {
|
|
3799
|
+
const suppressHostScopedLoop = stageHasSubworkflowChild(node.container_id, containerTree);
|
|
3435
3800
|
return /* @__PURE__ */ jsxs10(
|
|
3436
3801
|
"div",
|
|
3437
3802
|
{
|
|
3438
3803
|
className: "lax-workflow-nested-block",
|
|
3439
3804
|
"data-testid": `lax-workflow-nested-${node.scope}-${node.scope_key}`,
|
|
3440
3805
|
children: [
|
|
3441
|
-
renderLeafContainer(
|
|
3806
|
+
renderLeafContainer(
|
|
3807
|
+
node,
|
|
3808
|
+
depth,
|
|
3809
|
+
containerTree,
|
|
3810
|
+
loopTreesBySessionId,
|
|
3811
|
+
viewProps,
|
|
3812
|
+
{ suppressHostScopedLoop }
|
|
3813
|
+
),
|
|
3442
3814
|
hasNested ? /* @__PURE__ */ jsx15("div", { className: "lax-workflow-container-children", children: nested }) : null
|
|
3443
3815
|
]
|
|
3444
3816
|
},
|
|
@@ -3788,6 +4160,14 @@ function WorkflowSession({
|
|
|
3788
4160
|
storeRef.current,
|
|
3789
4161
|
(s) => s.state.loopTreesBySessionId
|
|
3790
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
|
+
);
|
|
3791
4171
|
useEffect8(() => {
|
|
3792
4172
|
const store = storeRef.current;
|
|
3793
4173
|
const controller = new AbortController();
|
|
@@ -3795,10 +4175,10 @@ function WorkflowSession({
|
|
|
3795
4175
|
store.getState().applyEvent(event, ctx);
|
|
3796
4176
|
}, controller.signal).catch((err) => {
|
|
3797
4177
|
if (err instanceof SseTransportTerminalError) {
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
4178
|
+
store.getState().markAsError(err);
|
|
4179
|
+
onError?.(err);
|
|
4180
|
+
} else if (err instanceof Error) {
|
|
4181
|
+
onError?.(err);
|
|
3802
4182
|
}
|
|
3803
4183
|
});
|
|
3804
4184
|
return () => {
|
|
@@ -3811,6 +4191,15 @@ function WorkflowSession({
|
|
|
3811
4191
|
className: "lax-agent-session lax-workflow-session",
|
|
3812
4192
|
"data-testid": "lax-workflow-session",
|
|
3813
4193
|
children: [
|
|
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,
|
|
3814
4203
|
/* @__PURE__ */ jsx17(
|
|
3815
4204
|
WorkflowChrome,
|
|
3816
4205
|
{
|