langchain_agentx_stream_ui 0.1.1 → 0.1.2

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: []
@@ -1640,6 +1642,9 @@ function collectProtectedLoopSessionIds(state, pinnedLoopSessionIds) {
1640
1642
  }
1641
1643
  function evictInactiveLoopTrees(state, pinnedLoopSessionIds) {
1642
1644
  const protectedIds = collectProtectedLoopSessionIds(state, pinnedLoopSessionIds);
1645
+ for (const id of state.hotCompletedLoopSessionIds) {
1646
+ protectedIds.add(id);
1647
+ }
1643
1648
  const byLoop = containersByLoopSessionId(state.containerTree);
1644
1649
  const nextLoops = { ...state.loopTreesBySessionId };
1645
1650
  const nextSummaries = {
@@ -1662,6 +1667,45 @@ function evictInactiveLoopTrees(state, pinnedLoopSessionIds) {
1662
1667
  stageDoneSummariesByContainerId: nextSummaries
1663
1668
  };
1664
1669
  }
1670
+ function touchCompletedLoopSession(state, loopSessionId) {
1671
+ const ids = state.hotCompletedLoopSessionIds.filter((id) => id !== loopSessionId);
1672
+ ids.push(loopSessionId);
1673
+ return {
1674
+ ...state,
1675
+ hotCompletedLoopSessionIds: ids
1676
+ };
1677
+ }
1678
+ function evictCompletedLoopOverflow(state, maxHydratedCompletedLoops) {
1679
+ if (state.hotCompletedLoopSessionIds.length <= maxHydratedCompletedLoops) {
1680
+ return state;
1681
+ }
1682
+ const overflow = state.hotCompletedLoopSessionIds.length - maxHydratedCompletedLoops;
1683
+ const evictedIds = new Set(state.hotCompletedLoopSessionIds.slice(0, overflow));
1684
+ const trimmedIds = state.hotCompletedLoopSessionIds.slice(overflow);
1685
+ const byLoop = containersByLoopSessionId(state.containerTree);
1686
+ const nextLoops = { ...state.loopTreesBySessionId };
1687
+ const nextSummaries = {
1688
+ ...state.stageDoneSummariesByContainerId
1689
+ };
1690
+ for (const id of evictedIds) {
1691
+ const tree = nextLoops[id];
1692
+ if (!tree) continue;
1693
+ const container = byLoop.get(id);
1694
+ if (container) {
1695
+ const summary = buildStageDoneSummary(tree);
1696
+ if (summary) {
1697
+ nextSummaries[container.container_id] = summary;
1698
+ }
1699
+ }
1700
+ delete nextLoops[id];
1701
+ }
1702
+ return {
1703
+ ...state,
1704
+ loopTreesBySessionId: nextLoops,
1705
+ stageDoneSummariesByContainerId: nextSummaries,
1706
+ hotCompletedLoopSessionIds: trimmedIds
1707
+ };
1708
+ }
1665
1709
 
1666
1710
  // src/core/workflowSessionStore.ts
1667
1711
  function safeReduceWorkflow(state, event, eventIndex, options) {
@@ -1691,6 +1735,7 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
1691
1735
  const initialEventCount = storeOptions?.initialEventCount ?? 0;
1692
1736
  const seenEventIds = new Set(storeOptions?.initialSeenEventIds);
1693
1737
  const evictCompletedLoops = storeOptions?.evictCompletedLoops ?? true;
1738
+ const maxHydratedCompletedLoops = storeOptions?.maxHydratedCompletedLoops ?? 4;
1694
1739
  const pinnedLoopSessionIds = {};
1695
1740
  const finalizeState = (reduced) => {
1696
1741
  if (!evictCompletedLoops) return reduced;
@@ -1747,14 +1792,23 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
1747
1792
  }
1748
1793
  });
1749
1794
  },
1795
+ /**
1796
+ * P2:将 completed loop 加入 LRU 热缓存并淘汰溢出。
1797
+ * running 保护由 collectProtectedLoopSessionIds 按状态自动覆盖,不再依赖永久 pin。
1798
+ */
1750
1799
  pinLoopSession(loopSessionId) {
1751
- pinnedLoopSessionIds[loopSessionId] = true;
1752
- set((current) => ({
1753
- state: finalizeState(current.state)
1754
- }));
1800
+ set((current) => {
1801
+ let next = touchCompletedLoopSession(current.state, loopSessionId);
1802
+ next = evictCompletedLoopOverflow(next, maxHydratedCompletedLoops);
1803
+ next = finalizeState(next);
1804
+ return { state: next };
1805
+ });
1755
1806
  },
1807
+ /**
1808
+ * P2:宿主 replay 后灌入 loop 树,touch LRU 并淘汰溢出。
1809
+ * completed loop 受 hotCompletedLoopSessionIds 保护,不再永久 pin。
1810
+ */
1756
1811
  hydrateLoopSession(loopSessionId, events) {
1757
- pinnedLoopSessionIds[loopSessionId] = true;
1758
1812
  const { state, eventCount } = get();
1759
1813
  let tree = state.loopTreesBySessionId[loopSessionId] ?? createEmptyTree();
1760
1814
  let idx = eventCount;
@@ -1762,14 +1816,18 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
1762
1816
  tree = reduceTree(tree, event, idx, reduceOpts);
1763
1817
  idx += 1;
1764
1818
  }
1765
- set({
1766
- state: finalizeState({
1767
- ...state,
1819
+ set((current) => {
1820
+ let next = {
1821
+ ...current.state,
1768
1822
  loopTreesBySessionId: {
1769
- ...state.loopTreesBySessionId,
1823
+ ...current.state.loopTreesBySessionId,
1770
1824
  [loopSessionId]: tree
1771
1825
  }
1772
- })
1826
+ };
1827
+ next = touchCompletedLoopSession(next, loopSessionId);
1828
+ next = evictCompletedLoopOverflow(next, maxHydratedCompletedLoops);
1829
+ next = finalizeState(next);
1830
+ return { state: next };
1773
1831
  });
1774
1832
  }
1775
1833
  }));
@@ -2246,10 +2304,7 @@ function WorkflowStageDoneBody({
2246
2304
  function stageSortKey(node) {
2247
2305
  return node.display?.progress_current ?? 0;
2248
2306
  }
2249
- function partitionWorkflowStages(stages, pageSize, showAll) {
2250
- if (showAll || stages.length <= pageSize) {
2251
- return { visible: stages, hiddenCount: 0, total: stages.length };
2252
- }
2307
+ function partitionWorkflowStages(stages, maxVisibleDoneCount) {
2253
2308
  const sorted = [...stages].sort(
2254
2309
  (a, b) => stageSortKey(a) - stageSortKey(b) || a.scope_key.localeCompare(b.scope_key)
2255
2310
  );
@@ -2264,12 +2319,18 @@ function partitionWorkflowStages(stages, pageSize, showAll) {
2264
2319
  always.push(stage);
2265
2320
  }
2266
2321
  }
2267
- const recentDone = done.slice(Math.max(0, done.length - pageSize));
2268
- const visible = [...always, ...recentDone].sort(
2322
+ const visibleDone = done.slice(Math.max(0, done.length - maxVisibleDoneCount));
2323
+ const visible = [...always, ...visibleDone].sort(
2269
2324
  (a, b) => stageSortKey(a) - stageSortKey(b) || a.scope_key.localeCompare(b.scope_key)
2270
2325
  );
2271
2326
  const hiddenCount = stages.length - visible.length;
2272
- return { visible, hiddenCount, total: stages.length };
2327
+ return {
2328
+ visible,
2329
+ hiddenCount,
2330
+ total: stages.length,
2331
+ visibleDoneCount: visibleDone.length,
2332
+ totalDoneCount: done.length
2333
+ };
2273
2334
  }
2274
2335
  function isActiveWorkflowContainer(node, activeLoopSessionId, activeContainerIds) {
2275
2336
  if (activeContainerIds.includes(node.container_id)) return true;
@@ -2468,66 +2529,8 @@ function WorkflowStageContainer({
2468
2529
  );
2469
2530
  }
2470
2531
 
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
2532
  // src/view/workflow/WorkflowParallelGroup.tsx
2530
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2533
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
2531
2534
  function resolveLoopTree(node, loopTreesBySessionId) {
2532
2535
  if (!node.loopSessionId) return void 0;
2533
2536
  return loopTreesBySessionId[node.loopSessionId];
@@ -2579,10 +2582,9 @@ function WorkflowParallelGroup({
2579
2582
  window.addEventListener("keydown", onKeyDown);
2580
2583
  return () => window.removeEventListener("keydown", onKeyDown);
2581
2584
  }, []);
2582
- const useVirtualItems = items.length >= scale.parallelItemVirtualizeThreshold;
2583
2585
  const renderItem = (index) => {
2584
2586
  const item = items[index];
2585
- return /* @__PURE__ */ jsx11(
2587
+ return /* @__PURE__ */ jsx10(
2586
2588
  WorkflowStageContainer,
2587
2589
  {
2588
2590
  node: item,
@@ -2601,7 +2603,7 @@ function WorkflowParallelGroup({
2601
2603
  className: "lax-workflow-parallel-group",
2602
2604
  "data-testid": `lax-workflow-parallel-group-${groupKey}`,
2603
2605
  children: [
2604
- /* @__PURE__ */ jsx11(
2606
+ /* @__PURE__ */ jsx10(
2605
2607
  WorkflowExpandTrigger,
2606
2608
  {
2607
2609
  summary,
@@ -2612,22 +2614,14 @@ function WorkflowParallelGroup({
2612
2614
  className: "lax-workflow-parallel-group__summary"
2613
2615
  }
2614
2616
  ),
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
2617
+ expanded ? /* @__PURE__ */ jsx10("div", { className: "lax-workflow-parallel-list", "data-testid": "lax-workflow-parallel-list", children: items.map((_, index) => renderItem(index)) }) : null
2624
2618
  ]
2625
2619
  }
2626
2620
  );
2627
2621
  }
2628
2622
 
2629
2623
  // src/view/workflow/WorkflowRootContainer.tsx
2630
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2624
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2631
2625
  function WorkflowRootContainer({
2632
2626
  node,
2633
2627
  children
@@ -2636,7 +2630,7 @@ function WorkflowRootContainer({
2636
2630
  const uiStatus = mapContainerUiStatus(node.status);
2637
2631
  const statusLabel = mapWorkflowRootStatusLabel(node.status);
2638
2632
  return /* @__PURE__ */ jsxs7("div", { className: "lax-workflow-root", "data-testid": "lax-workflow-root", children: [
2639
- /* @__PURE__ */ jsx12(
2633
+ /* @__PURE__ */ jsx11(
2640
2634
  WorkflowContainerLine,
2641
2635
  {
2642
2636
  variant: "root",
@@ -2651,13 +2645,13 @@ function WorkflowRootContainer({
2651
2645
  verbose
2652
2646
  }
2653
2647
  ),
2654
- children ? /* @__PURE__ */ jsx12("div", { className: "lax-workflow-container-tree", children }) : null
2648
+ children ? /* @__PURE__ */ jsx11("div", { className: "lax-workflow-container-tree", children }) : null
2655
2649
  ] });
2656
2650
  }
2657
2651
 
2658
2652
  // src/view/workflow/WorkflowRouteContainer.tsx
2659
2653
  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";
2654
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2661
2655
  function WorkflowRouteContainer({
2662
2656
  node,
2663
2657
  loopTree,
@@ -2703,7 +2697,7 @@ function WorkflowRouteContainer({
2703
2697
  "data-container-scope": "branch",
2704
2698
  style: depthStyle,
2705
2699
  children: [
2706
- /* @__PURE__ */ jsx13(
2700
+ /* @__PURE__ */ jsx12(
2707
2701
  WorkflowContainerLine,
2708
2702
  {
2709
2703
  title: node.title,
@@ -2718,7 +2712,7 @@ function WorkflowRouteContainer({
2718
2712
  descriptionTestId: `${testId}-desc`
2719
2713
  }
2720
2714
  ),
2721
- !isSkipped && expanded && isRunning && loopTree ? /* @__PURE__ */ jsx13("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx13(
2715
+ !isSkipped && expanded && isRunning && loopTree ? /* @__PURE__ */ jsx12("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx12(
2722
2716
  AgentLoopView,
2723
2717
  {
2724
2718
  tree: loopTree,
@@ -2728,7 +2722,7 @@ function WorkflowRouteContainer({
2728
2722
  groupParallelTools
2729
2723
  }
2730
2724
  ) }) : null,
2731
- !isSkipped && isDone && !expanded ? /* @__PURE__ */ jsx13(
2725
+ !isSkipped && isDone && !expanded ? /* @__PURE__ */ jsx12(
2732
2726
  WorkflowExpandTrigger,
2733
2727
  {
2734
2728
  summary: `${node.title} \xB7 ${doneLabel}`,
@@ -2740,7 +2734,7 @@ function WorkflowRouteContainer({
2740
2734
  teaserTestId: teaserText ? `${testId}-teaser` : void 0
2741
2735
  }
2742
2736
  ) : null,
2743
- !isSkipped && isDone && expanded ? /* @__PURE__ */ jsx13(
2737
+ !isSkipped && isDone && expanded ? /* @__PURE__ */ jsx12(
2744
2738
  WorkflowStageDoneBody,
2745
2739
  {
2746
2740
  node,
@@ -2758,7 +2752,7 @@ function WorkflowRouteContainer({
2758
2752
 
2759
2753
  // src/view/workflow/WorkflowSubworkflowContainer.tsx
2760
2754
  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";
2755
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
2762
2756
  function buildDoneSummary(node) {
2763
2757
  const display = node.display;
2764
2758
  if (display?.progress_total != null) {
@@ -2804,7 +2798,7 @@ function WorkflowSubworkflowContainer({
2804
2798
  "data-container-scope": "subworkflow",
2805
2799
  style: depthStyle,
2806
2800
  children: [
2807
- showShellLine ? /* @__PURE__ */ jsx14(
2801
+ showShellLine ? /* @__PURE__ */ jsx13(
2808
2802
  WorkflowContainerLine,
2809
2803
  {
2810
2804
  variant: "root",
@@ -2822,7 +2816,7 @@ function WorkflowSubworkflowContainer({
2822
2816
  verbose
2823
2817
  }
2824
2818
  ) : null,
2825
- expanded && children ? /* @__PURE__ */ jsx14(
2819
+ expanded && children ? /* @__PURE__ */ jsx13(
2826
2820
  "div",
2827
2821
  {
2828
2822
  className: "lax-workflow-subworkflow-boundary",
@@ -2831,7 +2825,7 @@ function WorkflowSubworkflowContainer({
2831
2825
  children
2832
2826
  }
2833
2827
  ) : null,
2834
- isDone && !expanded ? /* @__PURE__ */ jsx14(
2828
+ isDone && !expanded ? /* @__PURE__ */ jsx13(
2835
2829
  WorkflowExpandTrigger,
2836
2830
  {
2837
2831
  summary: `${node.title} \xB7 ${buildDoneSummary(node)}`,
@@ -2849,7 +2843,7 @@ function WorkflowSubworkflowContainer({
2849
2843
  }
2850
2844
 
2851
2845
  // src/view/workflow/WorkflowChrome.tsx
2852
- import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2846
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
2853
2847
  function collectChildContainers(containersById, parentId) {
2854
2848
  return Object.values(containersById).filter((node) => node.parent_container_id === parentId).sort((a, b) => a.scope_key.localeCompare(b.scope_key));
2855
2849
  }
@@ -2867,7 +2861,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2867
2861
  const loopTree = resolveLoopTree2(node, loopTreesBySessionId);
2868
2862
  switch (node.scope) {
2869
2863
  case "aggregate":
2870
- return /* @__PURE__ */ jsx15(
2864
+ return /* @__PURE__ */ jsx14(
2871
2865
  WorkflowAggregateContainer,
2872
2866
  {
2873
2867
  node,
@@ -2879,7 +2873,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2879
2873
  node.container_id
2880
2874
  );
2881
2875
  case "branch":
2882
- return /* @__PURE__ */ jsx15(
2876
+ return /* @__PURE__ */ jsx14(
2883
2877
  WorkflowRouteContainer,
2884
2878
  {
2885
2879
  node,
@@ -2890,7 +2884,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2890
2884
  node.container_id
2891
2885
  );
2892
2886
  default:
2893
- return /* @__PURE__ */ jsx15(
2887
+ return /* @__PURE__ */ jsx14(
2894
2888
  WorkflowStageContainer,
2895
2889
  {
2896
2890
  node,
@@ -2904,7 +2898,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
2904
2898
  }
2905
2899
  function renderItemSiblings(items, groupKey, depth, containerTree, loopTreesBySessionId, viewProps) {
2906
2900
  if (items.length >= 2) {
2907
- return /* @__PURE__ */ jsx15(
2901
+ return /* @__PURE__ */ jsx14(
2908
2902
  WorkflowParallelGroup,
2909
2903
  {
2910
2904
  groupKey,
@@ -2939,7 +2933,7 @@ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProp
2939
2933
  );
2940
2934
  const hasNested = collectChildContainers(containerTree.containersById, node.container_id).length > 0;
2941
2935
  if (node.scope === "subworkflow") {
2942
- return /* @__PURE__ */ jsx15(
2936
+ return /* @__PURE__ */ jsx14(
2943
2937
  WorkflowSubworkflowContainer,
2944
2938
  {
2945
2939
  node,
@@ -2957,7 +2951,7 @@ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProp
2957
2951
  "data-testid": `lax-workflow-nested-${node.scope}-${node.scope_key}`,
2958
2952
  children: [
2959
2953
  renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps),
2960
- hasNested ? /* @__PURE__ */ jsx15("div", { className: "lax-workflow-container-children", children: nested }) : null
2954
+ hasNested ? /* @__PURE__ */ jsx14("div", { className: "lax-workflow-container-children", children: nested }) : null
2961
2955
  ]
2962
2956
  },
2963
2957
  node.container_id
@@ -2983,9 +2977,9 @@ function renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewP
2983
2977
  viewProps
2984
2978
  );
2985
2979
  if (as === "div") {
2986
- return /* @__PURE__ */ jsx15("div", { className, role: "listitem", children: content }, node.container_id);
2980
+ return /* @__PURE__ */ jsx14("div", { className, role: "listitem", children: content }, node.container_id);
2987
2981
  }
2988
- return /* @__PURE__ */ jsx15("li", { className, children: content }, node.container_id);
2982
+ return /* @__PURE__ */ jsx14("li", { className, children: content }, node.container_id);
2989
2983
  }
2990
2984
  function WorkflowStageListPanel({
2991
2985
  stages,
@@ -2994,30 +2988,19 @@ function WorkflowStageListPanel({
2994
2988
  viewProps
2995
2989
  }) {
2996
2990
  const scale = useWorkflowScaleOptions();
2997
- const [showAllStages, setShowAllStages] = useState6(false);
2991
+ const [extraCompletedPages, setExtraCompletedPages] = useState6(0);
2992
+ const maxVisibleDoneCount = scale.completedStagePageSize + extraCompletedPages * scale.completedStagePageSize;
2998
2993
  const partition = useMemo7(
2999
- () => partitionWorkflowStages(stages, scale.stagePageSize, showAllStages),
3000
- [stages, scale.stagePageSize, showAllStages]
2994
+ () => partitionWorkflowStages(stages, maxVisibleDoneCount),
2995
+ [stages, maxVisibleDoneCount]
3001
2996
  );
3002
- const useVirtual = partition.visible.length >= scale.stageVirtualizeThreshold;
3003
2997
  if (partition.visible.length === 0) return null;
3004
- const renderRow = (index) => renderWorkflowStageRow(
3005
- partition.visible[index],
3006
- containerTree,
3007
- loopTreesBySessionId,
3008
- viewProps,
3009
- "div"
2998
+ const showMoreCount = Math.min(
2999
+ scale.completedStagePageSize,
3000
+ partition.totalDoneCount - partition.visibleDoneCount
3010
3001
  );
3011
3002
  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(
3003
+ /* @__PURE__ */ jsx14("ol", { className: "lax-workflow-stage-list", "data-testid": "lax-workflow-stage-list", children: partition.visible.map(
3021
3004
  (node) => renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewProps)
3022
3005
  ) }),
3023
3006
  partition.hiddenCount > 0 ? /* @__PURE__ */ jsxs10(
@@ -3025,25 +3008,38 @@ function WorkflowStageListPanel({
3025
3008
  {
3026
3009
  type: "button",
3027
3010
  className: "lax-workflow-stage-list__show-all",
3028
- "data-testid": "lax-workflow-stage-show-all",
3029
- onClick: () => setShowAllStages(true),
3011
+ "data-testid": "lax-workflow-stage-show-more",
3012
+ onClick: () => setExtraCompletedPages((p) => p + 1),
3030
3013
  children: [
3031
3014
  "+",
3032
3015
  partition.hiddenCount,
3033
- " stages collapsed \xB7 Show all (",
3034
- partition.total,
3035
- ")"
3016
+ " completed stages hidden \xB7 Show ",
3017
+ showMoreCount,
3018
+ " more"
3036
3019
  ]
3037
3020
  }
3038
3021
  ) : null,
3039
- showAllStages && partition.hiddenCount === 0 && stages.length > scale.stagePageSize ? /* @__PURE__ */ jsx15(
3022
+ extraCompletedPages > 0 && partition.hiddenCount === 0 ? /* @__PURE__ */ jsx14(
3040
3023
  "button",
3041
3024
  {
3042
3025
  type: "button",
3043
3026
  className: "lax-workflow-stage-list__show-all",
3044
3027
  "data-testid": "lax-workflow-stage-show-less",
3045
- onClick: () => setShowAllStages(false),
3046
- children: "Show fewer stages"
3028
+ onClick: () => setExtraCompletedPages(0),
3029
+ children: "Show fewer completed stages"
3030
+ }
3031
+ ) : null,
3032
+ partition.hiddenCount > 0 ? /* @__PURE__ */ jsxs10(
3033
+ "p",
3034
+ {
3035
+ className: "lax-workflow-stage-list__hidden-hint",
3036
+ "data-testid": "lax-workflow-stage-hidden-hint",
3037
+ children: [
3038
+ partition.hiddenCount,
3039
+ " completed stage",
3040
+ partition.hiddenCount !== 1 ? "s" : "",
3041
+ " hidden \u2014 running, pending, and failed stages are always visible."
3042
+ ]
3047
3043
  }
3048
3044
  ) : null
3049
3045
  ] });
@@ -3059,7 +3055,7 @@ function renderWorkflowChildren(workflowNode, containerTree, loopTreesBySessionI
3059
3055
  (node) => node.scope !== "stage" && node.scope !== "item"
3060
3056
  );
3061
3057
  return /* @__PURE__ */ jsxs10(Fragment2, { children: [
3062
- stages.length > 0 ? /* @__PURE__ */ jsx15(
3058
+ stages.length > 0 ? /* @__PURE__ */ jsx14(
3063
3059
  WorkflowStageListPanel,
3064
3060
  {
3065
3061
  stages,
@@ -3084,7 +3080,7 @@ function WorkflowChrome({
3084
3080
  const viewProps = { virtualized, virtualizeThreshold, groupParallelTools };
3085
3081
  const rootNodes = containerTree.rootContainerIds.map((id) => containerTree.containersById[id]).filter((node) => node != null);
3086
3082
  if (rootNodes.length === 0) {
3087
- return /* @__PURE__ */ jsx15("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: /* @__PURE__ */ jsx15(
3083
+ return /* @__PURE__ */ jsx14("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: /* @__PURE__ */ jsx14(
3088
3084
  WorkflowContainerLine,
3089
3085
  {
3090
3086
  variant: "root",
@@ -3094,7 +3090,7 @@ function WorkflowChrome({
3094
3090
  }
3095
3091
  ) });
3096
3092
  }
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(
3093
+ 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
3094
  workflowNode,
3099
3095
  containerTree,
3100
3096
  loopTreesBySessionId,
@@ -3175,11 +3171,11 @@ function loopSessionDisplayLabel(loopSessionId) {
3175
3171
  }
3176
3172
 
3177
3173
  // src/view/workflow/WorkflowTaskListFooter.tsx
3178
- import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
3174
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
3179
3175
  function MessageResponse({ children }) {
3180
3176
  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 })
3177
+ /* @__PURE__ */ jsx15("span", { className: "lax-message-response__marker", children: "\u23BF " }),
3178
+ /* @__PURE__ */ jsx15("span", { className: "lax-message-response__content", children })
3183
3179
  ] });
3184
3180
  }
3185
3181
  function WorkflowTaskListFooter() {
@@ -3204,18 +3200,18 @@ function WorkflowTaskListFooter() {
3204
3200
  onClick: () => setExpanded((v) => !v),
3205
3201
  children: [
3206
3202
  label,
3207
- /* @__PURE__ */ jsx16("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
3203
+ /* @__PURE__ */ jsx15("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
3208
3204
  ]
3209
3205
  }
3210
3206
  ),
3211
- expanded ? /* @__PURE__ */ jsx16(MessageResponse, { children: /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs11(
3207
+ expanded ? /* @__PURE__ */ jsx15(MessageResponse, { children: /* @__PURE__ */ jsx15("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs11(
3212
3208
  "div",
3213
3209
  {
3214
3210
  className: "lax-workflow-task-list-group",
3215
3211
  "data-testid": `lax-workflow-task-group-${group.loopSessionId}`,
3216
3212
  children: [
3217
- /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
3218
- /* @__PURE__ */ jsx16(TaskList, { tasks: group.tasks })
3213
+ /* @__PURE__ */ jsx15("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
3214
+ /* @__PURE__ */ jsx15(TaskList, { tasks: group.tasks })
3219
3215
  ]
3220
3216
  },
3221
3217
  group.loopSessionId
@@ -3225,7 +3221,7 @@ function WorkflowTaskListFooter() {
3225
3221
 
3226
3222
  // src/view/workflow/WorkflowSession.tsx
3227
3223
  import { useStore as useStore5 } from "zustand";
3228
- import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
3224
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3229
3225
  function WorkflowSession({
3230
3226
  source,
3231
3227
  initialEvents,
@@ -3249,13 +3245,14 @@ function WorkflowSession({
3249
3245
  onRequestLoopReplay,
3250
3246
  children
3251
3247
  }) {
3252
- const storeRef = useRef4(null);
3248
+ const storeRef = useRef3(null);
3253
3249
  if (storeRef.current === null) {
3254
3250
  const initialState = initialEvents && initialEvents.length > 0 ? reduceWorkflowEvents(initialEvents, { tierOverrides }) : void 0;
3255
3251
  storeRef.current = createWorkflowSessionStore(initialState, {
3256
3252
  tierOverrides,
3257
3253
  initialEventCount: initialEvents?.length ?? 0,
3258
- evictCompletedLoops: workflowScale?.evictCompletedLoops ?? DEFAULT_WORKFLOW_SCALE_OPTIONS.evictCompletedLoops
3254
+ evictCompletedLoops: workflowScale?.evictCompletedLoops ?? DEFAULT_WORKFLOW_SCALE_OPTIONS.evictCompletedLoops,
3255
+ maxHydratedCompletedLoops: workflowScale?.maxHydratedCompletedLoops ?? DEFAULT_WORKFLOW_SCALE_OPTIONS.maxHydratedCompletedLoops
3259
3256
  });
3260
3257
  }
3261
3258
  const scaleOptions = useMemo9(
@@ -3320,13 +3317,13 @@ function WorkflowSession({
3320
3317
  controller.abort();
3321
3318
  };
3322
3319
  }, [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(
3320
+ 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
3321
  "div",
3325
3322
  {
3326
3323
  className: "lax-agent-session lax-workflow-session",
3327
3324
  "data-testid": "lax-workflow-session",
3328
3325
  children: [
3329
- /* @__PURE__ */ jsx17(
3326
+ /* @__PURE__ */ jsx16(
3330
3327
  WorkflowChrome,
3331
3328
  {
3332
3329
  containerTree,
@@ -3336,7 +3333,7 @@ function WorkflowSession({
3336
3333
  groupParallelTools
3337
3334
  }
3338
3335
  ),
3339
- /* @__PURE__ */ jsx17(WorkflowTaskListFooter, {}),
3336
+ /* @__PURE__ */ jsx16(WorkflowTaskListFooter, {}),
3340
3337
  children
3341
3338
  ]
3342
3339
  }
@@ -3380,7 +3377,7 @@ function buildTimelineEntries(rootIds, byId, options = {}) {
3380
3377
  }
3381
3378
 
3382
3379
  // src/view/nodes/SubAgentNode.tsx
3383
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
3380
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3384
3381
  function SubAgentBlock({ nodeId }) {
3385
3382
  const node = useNodeTyped(nodeId, "subagent");
3386
3383
  const childIds = useChildren(nodeId);
@@ -3391,7 +3388,7 @@ function SubAgentBlock({ nodeId }) {
3391
3388
  "SubAgent: ",
3392
3389
  node.subagentId
3393
3390
  ] }),
3394
- /* @__PURE__ */ jsx18("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx18(TimelineItem, { nodeId: childId, registry }, childId)) })
3391
+ /* @__PURE__ */ jsx17("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx17(TimelineItem, { nodeId: childId, registry }, childId)) })
3395
3392
  ] });
3396
3393
  }
3397
3394