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/{chunk-WPW3TQQF.js → chunk-2T26L3VT.js} +2 -2
- package/dist/{chunk-LBJFJF22.js → chunk-ZNXAQ4ZZ.js} +7 -1
- package/dist/chunk-ZNXAQ4ZZ.js.map +1 -0
- package/dist/default.css +16 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +1011 -162
- package/dist/index.js.map +1 -1
- package/dist/multi-session.js +2 -2
- package/dist/virtual.js +1 -1
- package/package.json +8 -8
- package/dist/chunk-LBJFJF22.js.map +0 -1
- /package/dist/{chunk-WPW3TQQF.js.map → chunk-2T26L3VT.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
fixDiagramSvg,
|
|
24
24
|
markEventIdSeen,
|
|
25
25
|
shouldSkipDuplicateEvent
|
|
26
|
-
} from "./chunk-
|
|
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-
|
|
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
|
|
1149
|
-
(
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
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/
|
|
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
|
|
1826
|
+
const derivedLoopSessionId = deriveLoopSessionIdFromStructureOpen(event);
|
|
1380
1827
|
const scope = loopTaskScopeForStructureOpen(event.event_type);
|
|
1381
|
-
if (!
|
|
1828
|
+
if (!derivedLoopSessionId || !scope) return tree;
|
|
1382
1829
|
const data = event.data ?? {};
|
|
1383
|
-
|
|
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
|
-
|
|
1387
|
-
|
|
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
|
|
1856
|
+
return nextTree;
|
|
1390
1857
|
}
|
|
1391
1858
|
return {
|
|
1392
|
-
...
|
|
1859
|
+
...nextTree,
|
|
1393
1860
|
containersById: {
|
|
1394
|
-
...
|
|
1395
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
1923
|
+
let nextTree = ensureEmbeddedSubworkflowContainers(tree, sessionId);
|
|
1924
|
+
if (findContainerByLoopSession(nextTree, sessionId)) return nextTree;
|
|
1460
1925
|
const data = event.data ?? {};
|
|
1461
|
-
const workflowPath =
|
|
1926
|
+
const workflowPath = readString5(data, "workflow_path") ?? null;
|
|
1462
1927
|
const scopeKey = resolveContentScopeKey(data);
|
|
1463
|
-
let targetId =
|
|
1464
|
-
if (scopeKey) {
|
|
1928
|
+
let targetId = findContainerByLoopSession(nextTree, sessionId);
|
|
1929
|
+
if (!targetId && scopeKey) {
|
|
1465
1930
|
targetId = resolveContentTargetFromSnapshot(
|
|
1466
|
-
containerTreeToSnapshot(
|
|
1931
|
+
containerTreeToSnapshot(nextTree),
|
|
1467
1932
|
workflowPath,
|
|
1468
1933
|
scopeKey
|
|
1469
1934
|
);
|
|
1470
1935
|
}
|
|
1471
1936
|
if (!targetId) {
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
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(
|
|
1952
|
+
containerTreeToSnapshot(nextTree),
|
|
1483
1953
|
workflowPath,
|
|
1484
1954
|
null
|
|
1485
1955
|
);
|
|
1486
1956
|
}
|
|
1487
|
-
if (!targetId) return
|
|
1488
|
-
const node =
|
|
1489
|
-
if (!node || node.loopSessionId) return
|
|
1490
|
-
if (node.scope
|
|
1491
|
-
return
|
|
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
|
-
...
|
|
1967
|
+
...nextTree,
|
|
1495
1968
|
containersById: {
|
|
1496
|
-
...
|
|
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
|
-
|
|
1503
|
-
|
|
1504
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
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
|
|
2127
|
-
function
|
|
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
|
-
|
|
2131
|
-
return last?.preview ?? "";
|
|
2871
|
+
return blocks.map((block) => block.preview?.trim() ?? "").filter(Boolean).join("\n\n");
|
|
2132
2872
|
}
|
|
2133
|
-
function
|
|
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
|
|
2146
|
-
const waitingTeaser = isWaiting ?
|
|
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__ */
|
|
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
|
-
|
|
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__ */
|
|
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
|
-
|
|
2197
|
-
|
|
2950
|
+
expanded && isRunning && !loopTree && contentPreview ? /* @__PURE__ */ jsx8(
|
|
2951
|
+
"div",
|
|
2198
2952
|
{
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
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
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2551
|
-
/* @__PURE__ */
|
|
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
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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
|
-
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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(
|
|
2982
|
-
|
|
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__ */
|
|
3840
|
+
return /* @__PURE__ */ jsx15("div", { className, role: "listitem", children: content }, node.container_id);
|
|
3009
3841
|
}
|
|
3010
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
3206
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3242
|
-
/* @__PURE__ */
|
|
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
|
|
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
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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
|
|