langchain_agentx_stream_ui 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  createMultiSessionStore,
20
20
  markEventIdSeen,
21
21
  shouldSkipDuplicateEvent
22
- } from "./chunk-R35BQYAY.js";
22
+ } from "./chunk-A4W6B7C3.js";
23
23
  import {
24
24
  DefaultToolBody,
25
25
  InteractionBusContext,
@@ -107,7 +107,7 @@ import {
107
107
  useSessionStatus,
108
108
  useSessionViewOptions,
109
109
  useTimeline
110
- } from "./chunk-Q4HRD7EM.js";
110
+ } from "./chunk-3B4WC67E.js";
111
111
  import {
112
112
  Agent,
113
113
  AgentToolBody,
@@ -653,7 +653,7 @@ function AgentSession({
653
653
  }
654
654
 
655
655
  // src/view/workflow/WorkflowSession.tsx
656
- import { useEffect as useEffect8, useMemo as useMemo9, useRef as useRef4 } from "react";
656
+ import { useEffect as useEffect8, useMemo as useMemo9, useRef as useRef3 } from "react";
657
657
 
658
658
  // src/core/context/WorkflowSessionContext.tsx
659
659
  import { createContext, useContext } from "react";
@@ -693,10 +693,11 @@ import { createContext as createContext3, useContext as useContext3 } from "reac
693
693
  // src/view/workflow/workflowScaleOptions.ts
694
694
  var DEFAULT_WORKFLOW_SCALE_OPTIONS = {
695
695
  stageVirtualizeThreshold: 40,
696
- stagePageSize: 25,
696
+ completedStagePageSize: 25,
697
697
  evictCompletedLoops: true,
698
698
  onlyExpandActiveRunning: true,
699
- parallelItemVirtualizeThreshold: 40
699
+ parallelItemVirtualizeThreshold: 40,
700
+ maxHydratedCompletedLoops: 4
700
701
  };
701
702
 
702
703
  // src/core/context/WorkflowScaleContext.tsx
@@ -749,6 +750,7 @@ function createEmptyWorkflowSessionState() {
749
750
  workflowProgress: createEmptyWorkflowProgress(),
750
751
  loopTreesBySessionId: {},
751
752
  stageDoneSummariesByContainerId: {},
753
+ hotCompletedLoopSessionIds: [],
752
754
  activeLoopSessionId: null,
753
755
  openScopeStack: [],
754
756
  internalErrors: []
@@ -1247,6 +1249,13 @@ function loopTaskScopeForStructureOpen(eventType) {
1247
1249
  }
1248
1250
 
1249
1251
  // src/core/workflowStructureEvents.ts
1252
+ var WORKFLOW_STRUCTURE_EVENT_PREFIXES = [
1253
+ "workflow-",
1254
+ "stage-",
1255
+ "parallel-",
1256
+ "route-",
1257
+ "subworkflow-"
1258
+ ];
1250
1259
  var WORKFLOW_STRUCTURE_EVENT_TYPES = /* @__PURE__ */ new Set([
1251
1260
  "workflow-start",
1252
1261
  "workflow-end",
@@ -1266,8 +1275,20 @@ var WORKFLOW_STRUCTURE_EVENT_TYPES = /* @__PURE__ */ new Set([
1266
1275
  "route-branch-done",
1267
1276
  "route-branch-failed"
1268
1277
  ]);
1278
+ var AGENT_LOOP_REPLAY_EVENT_TYPES = new Set(
1279
+ Object.keys(DEFAULT_EVENT_TIERS)
1280
+ );
1269
1281
  function isWorkflowStructureEventType(eventType) {
1270
- return WORKFLOW_STRUCTURE_EVENT_TYPES.has(eventType);
1282
+ if (WORKFLOW_STRUCTURE_EVENT_TYPES.has(eventType)) return true;
1283
+ return WORKFLOW_STRUCTURE_EVENT_PREFIXES.some((prefix) => eventType.startsWith(prefix));
1284
+ }
1285
+ function isAgentLoopReplayEventType(eventType) {
1286
+ return AGENT_LOOP_REPLAY_EVENT_TYPES.has(eventType);
1287
+ }
1288
+ function filterAgentLoopReplayEvents(events) {
1289
+ return events.filter(
1290
+ (event) => isAgentLoopReplayEventType(event.event_type) && !isWorkflowStructureEventType(event.event_type)
1291
+ );
1271
1292
  }
1272
1293
 
1273
1294
  // src/core/workflowReducer.ts
@@ -1419,16 +1440,17 @@ function findContainerByLoopSession(tree, loopSessionId) {
1419
1440
  return null;
1420
1441
  }
1421
1442
  var WORKFLOW_DEFAULT_LOOP_SESSION_ID = "workflow-loop-default";
1422
- function resolveAgentLoopSessionId(event) {
1443
+ function resolveAgentLoopSessionId(event, activeLoopSessionId) {
1423
1444
  if (event.session_id) return event.session_id;
1424
1445
  if (isWorkflowStructureEventType(event.event_type)) return null;
1425
1446
  if (event.event_type === "task-list-snapshot") return null;
1426
1447
  const derived = deriveLoopSessionIdFromAgentEvent(event);
1427
1448
  if (derived) return derived;
1449
+ if (activeLoopSessionId) return activeLoopSessionId;
1428
1450
  return WORKFLOW_DEFAULT_LOOP_SESSION_ID;
1429
1451
  }
1430
- function bindLoopSessionToContainerTree(tree, event) {
1431
- const sessionId = resolveAgentLoopSessionId(event);
1452
+ function bindLoopSessionToContainerTree(tree, event, activeLoopSessionId) {
1453
+ const sessionId = resolveAgentLoopSessionId(event, activeLoopSessionId);
1432
1454
  if (!sessionId) return tree;
1433
1455
  if (findContainerByLoopSession(tree, sessionId)) return tree;
1434
1456
  const data = event.data ?? {};
@@ -1534,8 +1556,12 @@ function reduceWorkflowSession(state, event, eventIndex, options) {
1534
1556
  if (isWorkflowStructureEventType(event.event_type)) {
1535
1557
  return applyStructureEvent(state, event);
1536
1558
  }
1537
- const loopSessionId = resolveAgentLoopSessionId(event) ?? WORKFLOW_DEFAULT_LOOP_SESSION_ID;
1538
- let containerTree = bindLoopSessionToContainerTree(state.containerTree, event);
1559
+ const loopSessionId = resolveAgentLoopSessionId(event, state.activeLoopSessionId) ?? WORKFLOW_DEFAULT_LOOP_SESSION_ID;
1560
+ let containerTree = bindLoopSessionToContainerTree(
1561
+ state.containerTree,
1562
+ event,
1563
+ state.activeLoopSessionId
1564
+ );
1539
1565
  containerTree = applyStructureToContainerTree(containerTree, event);
1540
1566
  let next = ensureLoopTree({ ...state, containerTree }, loopSessionId);
1541
1567
  const loopTree = next.loopTreesBySessionId[loopSessionId];
@@ -1640,6 +1666,9 @@ function collectProtectedLoopSessionIds(state, pinnedLoopSessionIds) {
1640
1666
  }
1641
1667
  function evictInactiveLoopTrees(state, pinnedLoopSessionIds) {
1642
1668
  const protectedIds = collectProtectedLoopSessionIds(state, pinnedLoopSessionIds);
1669
+ for (const id of state.hotCompletedLoopSessionIds) {
1670
+ protectedIds.add(id);
1671
+ }
1643
1672
  const byLoop = containersByLoopSessionId(state.containerTree);
1644
1673
  const nextLoops = { ...state.loopTreesBySessionId };
1645
1674
  const nextSummaries = {
@@ -1662,6 +1691,45 @@ function evictInactiveLoopTrees(state, pinnedLoopSessionIds) {
1662
1691
  stageDoneSummariesByContainerId: nextSummaries
1663
1692
  };
1664
1693
  }
1694
+ function touchCompletedLoopSession(state, loopSessionId) {
1695
+ const ids = state.hotCompletedLoopSessionIds.filter((id) => id !== loopSessionId);
1696
+ ids.push(loopSessionId);
1697
+ return {
1698
+ ...state,
1699
+ hotCompletedLoopSessionIds: ids
1700
+ };
1701
+ }
1702
+ function evictCompletedLoopOverflow(state, maxHydratedCompletedLoops) {
1703
+ if (state.hotCompletedLoopSessionIds.length <= maxHydratedCompletedLoops) {
1704
+ return state;
1705
+ }
1706
+ const overflow = state.hotCompletedLoopSessionIds.length - maxHydratedCompletedLoops;
1707
+ const evictedIds = new Set(state.hotCompletedLoopSessionIds.slice(0, overflow));
1708
+ const trimmedIds = state.hotCompletedLoopSessionIds.slice(overflow);
1709
+ const byLoop = containersByLoopSessionId(state.containerTree);
1710
+ const nextLoops = { ...state.loopTreesBySessionId };
1711
+ const nextSummaries = {
1712
+ ...state.stageDoneSummariesByContainerId
1713
+ };
1714
+ for (const id of evictedIds) {
1715
+ const tree = nextLoops[id];
1716
+ if (!tree) continue;
1717
+ const container = byLoop.get(id);
1718
+ if (container) {
1719
+ const summary = buildStageDoneSummary(tree);
1720
+ if (summary) {
1721
+ nextSummaries[container.container_id] = summary;
1722
+ }
1723
+ }
1724
+ delete nextLoops[id];
1725
+ }
1726
+ return {
1727
+ ...state,
1728
+ loopTreesBySessionId: nextLoops,
1729
+ stageDoneSummariesByContainerId: nextSummaries,
1730
+ hotCompletedLoopSessionIds: trimmedIds
1731
+ };
1732
+ }
1665
1733
 
1666
1734
  // src/core/workflowSessionStore.ts
1667
1735
  function safeReduceWorkflow(state, event, eventIndex, options) {
@@ -1691,6 +1759,7 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
1691
1759
  const initialEventCount = storeOptions?.initialEventCount ?? 0;
1692
1760
  const seenEventIds = new Set(storeOptions?.initialSeenEventIds);
1693
1761
  const evictCompletedLoops = storeOptions?.evictCompletedLoops ?? true;
1762
+ const maxHydratedCompletedLoops = storeOptions?.maxHydratedCompletedLoops ?? 4;
1694
1763
  const pinnedLoopSessionIds = {};
1695
1764
  const finalizeState = (reduced) => {
1696
1765
  if (!evictCompletedLoops) return reduced;
@@ -1747,29 +1816,42 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
1747
1816
  }
1748
1817
  });
1749
1818
  },
1819
+ /**
1820
+ * P2:将 completed loop 加入 LRU 热缓存并淘汰溢出。
1821
+ * running 保护由 collectProtectedLoopSessionIds 按状态自动覆盖,不再依赖永久 pin。
1822
+ */
1750
1823
  pinLoopSession(loopSessionId) {
1751
- pinnedLoopSessionIds[loopSessionId] = true;
1752
- set((current) => ({
1753
- state: finalizeState(current.state)
1754
- }));
1824
+ set((current) => {
1825
+ let next = touchCompletedLoopSession(current.state, loopSessionId);
1826
+ next = evictCompletedLoopOverflow(next, maxHydratedCompletedLoops);
1827
+ next = finalizeState(next);
1828
+ return { state: next };
1829
+ });
1755
1830
  },
1831
+ /**
1832
+ * P2:宿主 replay 后灌入 loop 树,touch LRU 并淘汰溢出。
1833
+ * completed loop 受 hotCompletedLoopSessionIds 保护,不再永久 pin。
1834
+ */
1756
1835
  hydrateLoopSession(loopSessionId, events) {
1757
- pinnedLoopSessionIds[loopSessionId] = true;
1758
1836
  const { state, eventCount } = get();
1759
1837
  let tree = state.loopTreesBySessionId[loopSessionId] ?? createEmptyTree();
1760
1838
  let idx = eventCount;
1761
- for (const event of events) {
1839
+ for (const event of filterAgentLoopReplayEvents(events)) {
1762
1840
  tree = reduceTree(tree, event, idx, reduceOpts);
1763
1841
  idx += 1;
1764
1842
  }
1765
- set({
1766
- state: finalizeState({
1767
- ...state,
1843
+ set((current) => {
1844
+ let next = {
1845
+ ...current.state,
1768
1846
  loopTreesBySessionId: {
1769
- ...state.loopTreesBySessionId,
1847
+ ...current.state.loopTreesBySessionId,
1770
1848
  [loopSessionId]: tree
1771
1849
  }
1772
- })
1850
+ };
1851
+ next = touchCompletedLoopSession(next, loopSessionId);
1852
+ next = evictCompletedLoopOverflow(next, maxHydratedCompletedLoops);
1853
+ next = finalizeState(next);
1854
+ return { state: next };
1773
1855
  });
1774
1856
  }
1775
1857
  }));
@@ -2246,10 +2328,7 @@ function WorkflowStageDoneBody({
2246
2328
  function stageSortKey(node) {
2247
2329
  return node.display?.progress_current ?? 0;
2248
2330
  }
2249
- function partitionWorkflowStages(stages, pageSize, showAll) {
2250
- if (showAll || stages.length <= pageSize) {
2251
- return { visible: stages, hiddenCount: 0, total: stages.length };
2252
- }
2331
+ function partitionWorkflowStages(stages, maxVisibleDoneCount) {
2253
2332
  const sorted = [...stages].sort(
2254
2333
  (a, b) => stageSortKey(a) - stageSortKey(b) || a.scope_key.localeCompare(b.scope_key)
2255
2334
  );
@@ -2264,12 +2343,18 @@ function partitionWorkflowStages(stages, pageSize, showAll) {
2264
2343
  always.push(stage);
2265
2344
  }
2266
2345
  }
2267
- const recentDone = done.slice(Math.max(0, done.length - pageSize));
2268
- const visible = [...always, ...recentDone].sort(
2346
+ const visibleDone = done.slice(Math.max(0, done.length - maxVisibleDoneCount));
2347
+ const visible = [...always, ...visibleDone].sort(
2269
2348
  (a, b) => stageSortKey(a) - stageSortKey(b) || a.scope_key.localeCompare(b.scope_key)
2270
2349
  );
2271
2350
  const hiddenCount = stages.length - visible.length;
2272
- return { visible, hiddenCount, total: stages.length };
2351
+ return {
2352
+ visible,
2353
+ hiddenCount,
2354
+ total: stages.length,
2355
+ visibleDoneCount: visibleDone.length,
2356
+ totalDoneCount: done.length
2357
+ };
2273
2358
  }
2274
2359
  function isActiveWorkflowContainer(node, activeLoopSessionId, activeContainerIds) {
2275
2360
  if (activeContainerIds.includes(node.container_id)) return true;
@@ -2468,66 +2553,8 @@ function WorkflowStageContainer({
2468
2553
  );
2469
2554
  }
2470
2555
 
2471
- // src/view/workflow/WorkflowVirtualRowList.tsx
2472
- import { useRef as useRef3 } from "react";
2473
- import { useVirtualizer } from "@tanstack/react-virtual";
2474
- import { jsx as jsx10 } from "react/jsx-runtime";
2475
- var DEFAULT_STAGE_ROW_ESTIMATE = 56;
2476
- function WorkflowVirtualRowList({
2477
- count,
2478
- estimateSize = DEFAULT_STAGE_ROW_ESTIMATE,
2479
- testId,
2480
- className,
2481
- renderRow
2482
- }) {
2483
- const parentRef = useRef3(null);
2484
- const virtualizer = useVirtualizer({
2485
- count,
2486
- getScrollElement: () => parentRef.current,
2487
- estimateSize: () => estimateSize,
2488
- overscan: 8
2489
- });
2490
- if (count === 0) return null;
2491
- return /* @__PURE__ */ jsx10(
2492
- "div",
2493
- {
2494
- ref: parentRef,
2495
- className: className ? `lax-workflow-virtual-list ${className}` : "lax-workflow-virtual-list",
2496
- "data-testid": testId,
2497
- children: /* @__PURE__ */ jsx10(
2498
- "div",
2499
- {
2500
- className: "lax-workflow-virtual-list__inner",
2501
- style: {
2502
- height: `${virtualizer.getTotalSize()}px`,
2503
- position: "relative",
2504
- width: "100%"
2505
- },
2506
- children: virtualizer.getVirtualItems().map((virtualRow) => /* @__PURE__ */ jsx10(
2507
- "div",
2508
- {
2509
- className: "lax-workflow-virtual-list__item",
2510
- "data-index": virtualRow.index,
2511
- ref: virtualizer.measureElement,
2512
- style: {
2513
- position: "absolute",
2514
- top: 0,
2515
- left: 0,
2516
- width: "100%",
2517
- transform: `translateY(${virtualRow.start}px)`
2518
- },
2519
- children: renderRow(virtualRow.index)
2520
- },
2521
- virtualRow.key
2522
- ))
2523
- }
2524
- )
2525
- }
2526
- );
2527
- }
2528
-
2529
2556
  // src/view/workflow/WorkflowParallelGroup.tsx
2530
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2557
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
2531
2558
  function resolveLoopTree(node, loopTreesBySessionId) {
2532
2559
  if (!node.loopSessionId) return void 0;
2533
2560
  return loopTreesBySessionId[node.loopSessionId];
@@ -2579,10 +2606,9 @@ function WorkflowParallelGroup({
2579
2606
  window.addEventListener("keydown", onKeyDown);
2580
2607
  return () => window.removeEventListener("keydown", onKeyDown);
2581
2608
  }, []);
2582
- const useVirtualItems = items.length >= scale.parallelItemVirtualizeThreshold;
2583
2609
  const renderItem = (index) => {
2584
2610
  const item = items[index];
2585
- return /* @__PURE__ */ jsx11(
2611
+ return /* @__PURE__ */ jsx10(
2586
2612
  WorkflowStageContainer,
2587
2613
  {
2588
2614
  node: item,
@@ -2601,7 +2627,7 @@ function WorkflowParallelGroup({
2601
2627
  className: "lax-workflow-parallel-group",
2602
2628
  "data-testid": `lax-workflow-parallel-group-${groupKey}`,
2603
2629
  children: [
2604
- /* @__PURE__ */ jsx11(
2630
+ /* @__PURE__ */ jsx10(
2605
2631
  WorkflowExpandTrigger,
2606
2632
  {
2607
2633
  summary,
@@ -2612,22 +2638,14 @@ function WorkflowParallelGroup({
2612
2638
  className: "lax-workflow-parallel-group__summary"
2613
2639
  }
2614
2640
  ),
2615
- expanded ? useVirtualItems ? /* @__PURE__ */ jsx11(
2616
- WorkflowVirtualRowList,
2617
- {
2618
- count: items.length,
2619
- testId: "lax-workflow-parallel-virtual-list",
2620
- className: "lax-workflow-parallel-list lax-workflow-parallel-list--virtual",
2621
- renderRow: renderItem
2622
- }
2623
- ) : /* @__PURE__ */ jsx11("div", { className: "lax-workflow-parallel-list", "data-testid": "lax-workflow-parallel-list", children: items.map((_, index) => renderItem(index)) }) : null
2641
+ expanded ? /* @__PURE__ */ jsx10("div", { className: "lax-workflow-parallel-list", "data-testid": "lax-workflow-parallel-list", children: items.map((_, index) => renderItem(index)) }) : null
2624
2642
  ]
2625
2643
  }
2626
2644
  );
2627
2645
  }
2628
2646
 
2629
2647
  // src/view/workflow/WorkflowRootContainer.tsx
2630
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2648
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2631
2649
  function WorkflowRootContainer({
2632
2650
  node,
2633
2651
  children
@@ -2636,7 +2654,7 @@ function WorkflowRootContainer({
2636
2654
  const uiStatus = mapContainerUiStatus(node.status);
2637
2655
  const statusLabel = mapWorkflowRootStatusLabel(node.status);
2638
2656
  return /* @__PURE__ */ jsxs7("div", { className: "lax-workflow-root", "data-testid": "lax-workflow-root", children: [
2639
- /* @__PURE__ */ jsx12(
2657
+ /* @__PURE__ */ jsx11(
2640
2658
  WorkflowContainerLine,
2641
2659
  {
2642
2660
  variant: "root",
@@ -2651,13 +2669,13 @@ function WorkflowRootContainer({
2651
2669
  verbose
2652
2670
  }
2653
2671
  ),
2654
- children ? /* @__PURE__ */ jsx12("div", { className: "lax-workflow-container-tree", children }) : null
2672
+ children ? /* @__PURE__ */ jsx11("div", { className: "lax-workflow-container-tree", children }) : null
2655
2673
  ] });
2656
2674
  }
2657
2675
 
2658
2676
  // src/view/workflow/WorkflowRouteContainer.tsx
2659
2677
  import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo5, useState as useState4 } from "react";
2660
- import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2678
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2661
2679
  function WorkflowRouteContainer({
2662
2680
  node,
2663
2681
  loopTree,
@@ -2703,7 +2721,7 @@ function WorkflowRouteContainer({
2703
2721
  "data-container-scope": "branch",
2704
2722
  style: depthStyle,
2705
2723
  children: [
2706
- /* @__PURE__ */ jsx13(
2724
+ /* @__PURE__ */ jsx12(
2707
2725
  WorkflowContainerLine,
2708
2726
  {
2709
2727
  title: node.title,
@@ -2718,7 +2736,7 @@ function WorkflowRouteContainer({
2718
2736
  descriptionTestId: `${testId}-desc`
2719
2737
  }
2720
2738
  ),
2721
- !isSkipped && expanded && isRunning && loopTree ? /* @__PURE__ */ jsx13("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx13(
2739
+ !isSkipped && expanded && isRunning && loopTree ? /* @__PURE__ */ jsx12("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx12(
2722
2740
  AgentLoopView,
2723
2741
  {
2724
2742
  tree: loopTree,
@@ -2728,7 +2746,7 @@ function WorkflowRouteContainer({
2728
2746
  groupParallelTools
2729
2747
  }
2730
2748
  ) }) : null,
2731
- !isSkipped && isDone && !expanded ? /* @__PURE__ */ jsx13(
2749
+ !isSkipped && isDone && !expanded ? /* @__PURE__ */ jsx12(
2732
2750
  WorkflowExpandTrigger,
2733
2751
  {
2734
2752
  summary: `${node.title} \xB7 ${doneLabel}`,
@@ -2740,7 +2758,7 @@ function WorkflowRouteContainer({
2740
2758
  teaserTestId: teaserText ? `${testId}-teaser` : void 0
2741
2759
  }
2742
2760
  ) : null,
2743
- !isSkipped && isDone && expanded ? /* @__PURE__ */ jsx13(
2761
+ !isSkipped && isDone && expanded ? /* @__PURE__ */ jsx12(
2744
2762
  WorkflowStageDoneBody,
2745
2763
  {
2746
2764
  node,
@@ -2758,7 +2776,7 @@ function WorkflowRouteContainer({
2758
2776
 
2759
2777
  // src/view/workflow/WorkflowSubworkflowContainer.tsx
2760
2778
  import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo6, useState as useState5 } from "react";
2761
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
2779
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
2762
2780
  function buildDoneSummary(node) {
2763
2781
  const display = node.display;
2764
2782
  if (display?.progress_total != null) {
@@ -2804,7 +2822,7 @@ function WorkflowSubworkflowContainer({
2804
2822
  "data-container-scope": "subworkflow",
2805
2823
  style: depthStyle,
2806
2824
  children: [
2807
- showShellLine ? /* @__PURE__ */ jsx14(
2825
+ showShellLine ? /* @__PURE__ */ jsx13(
2808
2826
  WorkflowContainerLine,
2809
2827
  {
2810
2828
  variant: "root",
@@ -2822,7 +2840,7 @@ function WorkflowSubworkflowContainer({
2822
2840
  verbose
2823
2841
  }
2824
2842
  ) : null,
2825
- expanded && children ? /* @__PURE__ */ jsx14(
2843
+ expanded && children ? /* @__PURE__ */ jsx13(
2826
2844
  "div",
2827
2845
  {
2828
2846
  className: "lax-workflow-subworkflow-boundary",
@@ -2831,7 +2849,7 @@ function WorkflowSubworkflowContainer({
2831
2849
  children
2832
2850
  }
2833
2851
  ) : null,
2834
- isDone && !expanded ? /* @__PURE__ */ jsx14(
2852
+ isDone && !expanded ? /* @__PURE__ */ jsx13(
2835
2853
  WorkflowExpandTrigger,
2836
2854
  {
2837
2855
  summary: `${node.title} \xB7 ${buildDoneSummary(node)}`,
@@ -2849,7 +2867,7 @@ function WorkflowSubworkflowContainer({
2849
2867
  }
2850
2868
 
2851
2869
  // src/view/workflow/WorkflowChrome.tsx
2852
- import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2870
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
2853
2871
  function collectChildContainers(containersById, parentId) {
2854
2872
  return Object.values(containersById).filter((node) => node.parent_container_id === parentId).sort((a, b) => a.scope_key.localeCompare(b.scope_key));
2855
2873
  }
@@ -2867,7 +2885,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2867
2885
  const loopTree = resolveLoopTree2(node, loopTreesBySessionId);
2868
2886
  switch (node.scope) {
2869
2887
  case "aggregate":
2870
- return /* @__PURE__ */ jsx15(
2888
+ return /* @__PURE__ */ jsx14(
2871
2889
  WorkflowAggregateContainer,
2872
2890
  {
2873
2891
  node,
@@ -2879,7 +2897,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2879
2897
  node.container_id
2880
2898
  );
2881
2899
  case "branch":
2882
- return /* @__PURE__ */ jsx15(
2900
+ return /* @__PURE__ */ jsx14(
2883
2901
  WorkflowRouteContainer,
2884
2902
  {
2885
2903
  node,
@@ -2890,7 +2908,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2890
2908
  node.container_id
2891
2909
  );
2892
2910
  default:
2893
- return /* @__PURE__ */ jsx15(
2911
+ return /* @__PURE__ */ jsx14(
2894
2912
  WorkflowStageContainer,
2895
2913
  {
2896
2914
  node,
@@ -2904,7 +2922,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2904
2922
  }
2905
2923
  function renderItemSiblings(items, groupKey, depth, containerTree, loopTreesBySessionId, viewProps) {
2906
2924
  if (items.length >= 2) {
2907
- return /* @__PURE__ */ jsx15(
2925
+ return /* @__PURE__ */ jsx14(
2908
2926
  WorkflowParallelGroup,
2909
2927
  {
2910
2928
  groupKey,
@@ -2939,7 +2957,7 @@ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProp
2939
2957
  );
2940
2958
  const hasNested = collectChildContainers(containerTree.containersById, node.container_id).length > 0;
2941
2959
  if (node.scope === "subworkflow") {
2942
- return /* @__PURE__ */ jsx15(
2960
+ return /* @__PURE__ */ jsx14(
2943
2961
  WorkflowSubworkflowContainer,
2944
2962
  {
2945
2963
  node,
@@ -2957,7 +2975,7 @@ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProp
2957
2975
  "data-testid": `lax-workflow-nested-${node.scope}-${node.scope_key}`,
2958
2976
  children: [
2959
2977
  renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps),
2960
- hasNested ? /* @__PURE__ */ jsx15("div", { className: "lax-workflow-container-children", children: nested }) : null
2978
+ hasNested ? /* @__PURE__ */ jsx14("div", { className: "lax-workflow-container-children", children: nested }) : null
2961
2979
  ]
2962
2980
  },
2963
2981
  node.container_id
@@ -2983,9 +3001,9 @@ function renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewP
2983
3001
  viewProps
2984
3002
  );
2985
3003
  if (as === "div") {
2986
- return /* @__PURE__ */ jsx15("div", { className, role: "listitem", children: content }, node.container_id);
3004
+ return /* @__PURE__ */ jsx14("div", { className, role: "listitem", children: content }, node.container_id);
2987
3005
  }
2988
- return /* @__PURE__ */ jsx15("li", { className, children: content }, node.container_id);
3006
+ return /* @__PURE__ */ jsx14("li", { className, children: content }, node.container_id);
2989
3007
  }
2990
3008
  function WorkflowStageListPanel({
2991
3009
  stages,
@@ -2994,30 +3012,19 @@ function WorkflowStageListPanel({
2994
3012
  viewProps
2995
3013
  }) {
2996
3014
  const scale = useWorkflowScaleOptions();
2997
- const [showAllStages, setShowAllStages] = useState6(false);
3015
+ const [extraCompletedPages, setExtraCompletedPages] = useState6(0);
3016
+ const maxVisibleDoneCount = scale.completedStagePageSize + extraCompletedPages * scale.completedStagePageSize;
2998
3017
  const partition = useMemo7(
2999
- () => partitionWorkflowStages(stages, scale.stagePageSize, showAllStages),
3000
- [stages, scale.stagePageSize, showAllStages]
3018
+ () => partitionWorkflowStages(stages, maxVisibleDoneCount),
3019
+ [stages, maxVisibleDoneCount]
3001
3020
  );
3002
- const useVirtual = partition.visible.length >= scale.stageVirtualizeThreshold;
3003
3021
  if (partition.visible.length === 0) return null;
3004
- const renderRow = (index) => renderWorkflowStageRow(
3005
- partition.visible[index],
3006
- containerTree,
3007
- loopTreesBySessionId,
3008
- viewProps,
3009
- "div"
3022
+ const showMoreCount = Math.min(
3023
+ scale.completedStagePageSize,
3024
+ partition.totalDoneCount - partition.visibleDoneCount
3010
3025
  );
3011
3026
  return /* @__PURE__ */ jsxs10(Fragment2, { children: [
3012
- useVirtual ? /* @__PURE__ */ jsx15("div", { className: "lax-workflow-stage-list-virtual-host", role: "list", children: /* @__PURE__ */ jsx15(
3013
- WorkflowVirtualRowList,
3014
- {
3015
- count: partition.visible.length,
3016
- testId: "lax-workflow-stage-virtual-list",
3017
- className: "lax-workflow-stage-list lax-workflow-stage-list--virtual",
3018
- renderRow
3019
- }
3020
- ) }) : /* @__PURE__ */ jsx15("ol", { className: "lax-workflow-stage-list", "data-testid": "lax-workflow-stage-list", children: partition.visible.map(
3027
+ /* @__PURE__ */ jsx14("ol", { className: "lax-workflow-stage-list", "data-testid": "lax-workflow-stage-list", children: partition.visible.map(
3021
3028
  (node) => renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewProps)
3022
3029
  ) }),
3023
3030
  partition.hiddenCount > 0 ? /* @__PURE__ */ jsxs10(
@@ -3025,25 +3032,38 @@ function WorkflowStageListPanel({
3025
3032
  {
3026
3033
  type: "button",
3027
3034
  className: "lax-workflow-stage-list__show-all",
3028
- "data-testid": "lax-workflow-stage-show-all",
3029
- onClick: () => setShowAllStages(true),
3035
+ "data-testid": "lax-workflow-stage-show-more",
3036
+ onClick: () => setExtraCompletedPages((p) => p + 1),
3030
3037
  children: [
3031
3038
  "+",
3032
3039
  partition.hiddenCount,
3033
- " stages collapsed \xB7 Show all (",
3034
- partition.total,
3035
- ")"
3040
+ " completed stages hidden \xB7 Show ",
3041
+ showMoreCount,
3042
+ " more"
3036
3043
  ]
3037
3044
  }
3038
3045
  ) : null,
3039
- showAllStages && partition.hiddenCount === 0 && stages.length > scale.stagePageSize ? /* @__PURE__ */ jsx15(
3046
+ extraCompletedPages > 0 && partition.hiddenCount === 0 ? /* @__PURE__ */ jsx14(
3040
3047
  "button",
3041
3048
  {
3042
3049
  type: "button",
3043
3050
  className: "lax-workflow-stage-list__show-all",
3044
3051
  "data-testid": "lax-workflow-stage-show-less",
3045
- onClick: () => setShowAllStages(false),
3046
- children: "Show fewer stages"
3052
+ onClick: () => setExtraCompletedPages(0),
3053
+ children: "Show fewer completed stages"
3054
+ }
3055
+ ) : null,
3056
+ partition.hiddenCount > 0 ? /* @__PURE__ */ jsxs10(
3057
+ "p",
3058
+ {
3059
+ className: "lax-workflow-stage-list__hidden-hint",
3060
+ "data-testid": "lax-workflow-stage-hidden-hint",
3061
+ children: [
3062
+ partition.hiddenCount,
3063
+ " completed stage",
3064
+ partition.hiddenCount !== 1 ? "s" : "",
3065
+ " hidden \u2014 running, pending, and failed stages are always visible."
3066
+ ]
3047
3067
  }
3048
3068
  ) : null
3049
3069
  ] });
@@ -3059,7 +3079,7 @@ function renderWorkflowChildren(workflowNode, containerTree, loopTreesBySessionI
3059
3079
  (node) => node.scope !== "stage" && node.scope !== "item"
3060
3080
  );
3061
3081
  return /* @__PURE__ */ jsxs10(Fragment2, { children: [
3062
- stages.length > 0 ? /* @__PURE__ */ jsx15(
3082
+ stages.length > 0 ? /* @__PURE__ */ jsx14(
3063
3083
  WorkflowStageListPanel,
3064
3084
  {
3065
3085
  stages,
@@ -3084,7 +3104,7 @@ function WorkflowChrome({
3084
3104
  const viewProps = { virtualized, virtualizeThreshold, groupParallelTools };
3085
3105
  const rootNodes = containerTree.rootContainerIds.map((id) => containerTree.containersById[id]).filter((node) => node != null);
3086
3106
  if (rootNodes.length === 0) {
3087
- return /* @__PURE__ */ jsx15("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: /* @__PURE__ */ jsx15(
3107
+ return /* @__PURE__ */ jsx14("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: /* @__PURE__ */ jsx14(
3088
3108
  WorkflowContainerLine,
3089
3109
  {
3090
3110
  variant: "root",
@@ -3094,7 +3114,7 @@ function WorkflowChrome({
3094
3114
  }
3095
3115
  ) });
3096
3116
  }
3097
- return /* @__PURE__ */ jsx15("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: rootNodes.map((workflowNode) => /* @__PURE__ */ jsx15(WorkflowRootContainer, { node: workflowNode, children: renderWorkflowChildren(
3117
+ return /* @__PURE__ */ jsx14("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: rootNodes.map((workflowNode) => /* @__PURE__ */ jsx14(WorkflowRootContainer, { node: workflowNode, children: renderWorkflowChildren(
3098
3118
  workflowNode,
3099
3119
  containerTree,
3100
3120
  loopTreesBySessionId,
@@ -3175,11 +3195,11 @@ function loopSessionDisplayLabel(loopSessionId) {
3175
3195
  }
3176
3196
 
3177
3197
  // src/view/workflow/WorkflowTaskListFooter.tsx
3178
- import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
3198
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
3179
3199
  function MessageResponse({ children }) {
3180
3200
  return /* @__PURE__ */ jsxs11("div", { className: "lax-message-response", children: [
3181
- /* @__PURE__ */ jsx16("span", { className: "lax-message-response__marker", children: "\u23BF " }),
3182
- /* @__PURE__ */ jsx16("span", { className: "lax-message-response__content", children })
3201
+ /* @__PURE__ */ jsx15("span", { className: "lax-message-response__marker", children: "\u23BF " }),
3202
+ /* @__PURE__ */ jsx15("span", { className: "lax-message-response__content", children })
3183
3203
  ] });
3184
3204
  }
3185
3205
  function WorkflowTaskListFooter() {
@@ -3204,18 +3224,18 @@ function WorkflowTaskListFooter() {
3204
3224
  onClick: () => setExpanded((v) => !v),
3205
3225
  children: [
3206
3226
  label,
3207
- /* @__PURE__ */ jsx16("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
3227
+ /* @__PURE__ */ jsx15("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
3208
3228
  ]
3209
3229
  }
3210
3230
  ),
3211
- expanded ? /* @__PURE__ */ jsx16(MessageResponse, { children: /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs11(
3231
+ expanded ? /* @__PURE__ */ jsx15(MessageResponse, { children: /* @__PURE__ */ jsx15("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs11(
3212
3232
  "div",
3213
3233
  {
3214
3234
  className: "lax-workflow-task-list-group",
3215
3235
  "data-testid": `lax-workflow-task-group-${group.loopSessionId}`,
3216
3236
  children: [
3217
- /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
3218
- /* @__PURE__ */ jsx16(TaskList, { tasks: group.tasks })
3237
+ /* @__PURE__ */ jsx15("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
3238
+ /* @__PURE__ */ jsx15(TaskList, { tasks: group.tasks })
3219
3239
  ]
3220
3240
  },
3221
3241
  group.loopSessionId
@@ -3225,7 +3245,7 @@ function WorkflowTaskListFooter() {
3225
3245
 
3226
3246
  // src/view/workflow/WorkflowSession.tsx
3227
3247
  import { useStore as useStore5 } from "zustand";
3228
- import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
3248
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3229
3249
  function WorkflowSession({
3230
3250
  source,
3231
3251
  initialEvents,
@@ -3249,13 +3269,14 @@ function WorkflowSession({
3249
3269
  onRequestLoopReplay,
3250
3270
  children
3251
3271
  }) {
3252
- const storeRef = useRef4(null);
3272
+ const storeRef = useRef3(null);
3253
3273
  if (storeRef.current === null) {
3254
3274
  const initialState = initialEvents && initialEvents.length > 0 ? reduceWorkflowEvents(initialEvents, { tierOverrides }) : void 0;
3255
3275
  storeRef.current = createWorkflowSessionStore(initialState, {
3256
3276
  tierOverrides,
3257
3277
  initialEventCount: initialEvents?.length ?? 0,
3258
- evictCompletedLoops: workflowScale?.evictCompletedLoops ?? DEFAULT_WORKFLOW_SCALE_OPTIONS.evictCompletedLoops
3278
+ evictCompletedLoops: workflowScale?.evictCompletedLoops ?? DEFAULT_WORKFLOW_SCALE_OPTIONS.evictCompletedLoops,
3279
+ maxHydratedCompletedLoops: workflowScale?.maxHydratedCompletedLoops ?? DEFAULT_WORKFLOW_SCALE_OPTIONS.maxHydratedCompletedLoops
3259
3280
  });
3260
3281
  }
3261
3282
  const scaleOptions = useMemo9(
@@ -3320,13 +3341,13 @@ function WorkflowSession({
3320
3341
  controller.abort();
3321
3342
  };
3322
3343
  }, [source, onError]);
3323
- 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(
3344
+ return /* @__PURE__ */ jsx16(WorkflowSessionStoreContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx16(WorkflowScaleContext.Provider, { value: scaleOptions, children: /* @__PURE__ */ jsx16(WorkflowLoopReplayProvider, { onRequestLoopReplay, children: /* @__PURE__ */ jsx16(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx16(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx16(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx16(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx16(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs12(
3324
3345
  "div",
3325
3346
  {
3326
3347
  className: "lax-agent-session lax-workflow-session",
3327
3348
  "data-testid": "lax-workflow-session",
3328
3349
  children: [
3329
- /* @__PURE__ */ jsx17(
3350
+ /* @__PURE__ */ jsx16(
3330
3351
  WorkflowChrome,
3331
3352
  {
3332
3353
  containerTree,
@@ -3336,7 +3357,7 @@ function WorkflowSession({
3336
3357
  groupParallelTools
3337
3358
  }
3338
3359
  ),
3339
- /* @__PURE__ */ jsx17(WorkflowTaskListFooter, {}),
3360
+ /* @__PURE__ */ jsx16(WorkflowTaskListFooter, {}),
3340
3361
  children
3341
3362
  ]
3342
3363
  }
@@ -3380,7 +3401,7 @@ function buildTimelineEntries(rootIds, byId, options = {}) {
3380
3401
  }
3381
3402
 
3382
3403
  // src/view/nodes/SubAgentNode.tsx
3383
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
3404
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3384
3405
  function SubAgentBlock({ nodeId }) {
3385
3406
  const node = useNodeTyped(nodeId, "subagent");
3386
3407
  const childIds = useChildren(nodeId);
@@ -3391,7 +3412,7 @@ function SubAgentBlock({ nodeId }) {
3391
3412
  "SubAgent: ",
3392
3413
  node.subagentId
3393
3414
  ] }),
3394
- /* @__PURE__ */ jsx18("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx18(TimelineItem, { nodeId: childId, registry }, childId)) })
3415
+ /* @__PURE__ */ jsx17("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx17(TimelineItem, { nodeId: childId, registry }, childId)) })
3395
3416
  ] });
3396
3417
  }
3397
3418
 
@@ -3492,6 +3513,7 @@ export {
3492
3513
  TruncatedContent,
3493
3514
  UnknownCard,
3494
3515
  VirtualTimeline,
3516
+ WORKFLOW_STRUCTURE_EVENT_PREFIXES,
3495
3517
  WORKFLOW_STRUCTURE_EVENT_TYPES,
3496
3518
  WebFetch,
3497
3519
  WebFetchToolBody,
@@ -3531,6 +3553,7 @@ export {
3531
3553
  createWorkflowSessionStore,
3532
3554
  displayPath,
3533
3555
  entryKey,
3556
+ filterAgentLoopReplayEvents,
3534
3557
  formatAgentTitle,
3535
3558
  formatDiffFromStrings,
3536
3559
  formatExploreSummaryText,
@@ -3544,6 +3567,7 @@ export {
3544
3567
  getChildIds,
3545
3568
  getMainStageIds,
3546
3569
  getNoopInteractionBus,
3570
+ isAgentLoopReplayEventType,
3547
3571
  isGitOperationCommand,
3548
3572
  isGroupableToolName,
3549
3573
  isMcpToolName,