langchain_agentx_stream_ui 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -864,11 +864,67 @@ function AgentSession({
864
864
  }
865
865
 
866
866
  // src/view/workflow/WorkflowSession.tsx
867
- import { useEffect as useEffect8, useMemo as useMemo9, useRef as useRef3 } from "react";
867
+ import { useEffect as useEffect10, useMemo as useMemo9, useRef as useRef4 } from "react";
868
868
 
869
869
  // src/core/context/WorkflowSessionContext.tsx
870
870
  import { createContext, useContext } from "react";
871
871
  import { useStore } from "zustand";
872
+
873
+ // src/core/workflow/workflowAuthoritySelectors.ts
874
+ var AUTHORITY_TERMINAL = /* @__PURE__ */ new Set([
875
+ "done",
876
+ "error",
877
+ "canceled",
878
+ "partial_success"
879
+ ]);
880
+ function isAuthorityTerminalStatus(status) {
881
+ return status != null && AUTHORITY_TERMINAL.has(status);
882
+ }
883
+ function isAuthorityTerminal(state) {
884
+ return isAuthorityTerminalStatus(state.authority?.status);
885
+ }
886
+ function shouldShowGlobalSpinner(state) {
887
+ const auth = state.authority?.status;
888
+ if (auth === "canceled") return false;
889
+ if (isAuthorityTerminalStatus(auth)) return false;
890
+ if (state.display.awaitingAuthority) return false;
891
+ if (auth == null || auth === "pending" || auth === "running") {
892
+ return true;
893
+ }
894
+ const proj = state.display.projectionStatus;
895
+ return proj === "running" || proj === "connecting";
896
+ }
897
+ function resolveWorkflowBanner(state) {
898
+ if (state.display.authoritySubscribeFailed) {
899
+ return { kind: "sync_failed", message: "\u72B6\u6001\u540C\u6B65\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" };
900
+ }
901
+ if (state.display.authoritySyncFailed) {
902
+ return { kind: "sync_failed", message: "\u540C\u6B65\u72B6\u6001\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0" };
903
+ }
904
+ if (state.display.awaitingAuthority) {
905
+ return { kind: "awaiting_authority", message: "\u6536\u5C3E\u4E2D\u2026" };
906
+ }
907
+ const auth = state.authority?.status;
908
+ if (auth === "partial_success") {
909
+ const forced = state.display.terminalKind === "forced_closeout";
910
+ return {
911
+ kind: "partial_success",
912
+ message: forced ? "\u90E8\u5206\u5B8C\u6210\uFF08\u90E8\u5206\u5BB9\u5668\u672A\u6B63\u5E38\u7ED3\u675F\uFF09" : "\u90E8\u5206\u5B8C\u6210\uFF08\u6240\u6709\u5BB9\u5668\u6B63\u5E38\u7ED3\u675F\uFF09"
913
+ };
914
+ }
915
+ if (state.display.terminalKind === "forced_closeout" && auth === "done") {
916
+ return { kind: "forced_closeout", message: "Workflow \u5DF2\u7ED3\u675F\uFF08\u90E8\u5206\u5BB9\u5668\u672A\u6B63\u5E38\u5173\u95ED\uFF09" };
917
+ }
918
+ return { kind: "none", message: "" };
919
+ }
920
+ function getDisplayProjectionStatus(state) {
921
+ return state.display.projectionStatus;
922
+ }
923
+ function applyAuthoritySnapshotToState(state, snapshot) {
924
+ return { ...state, authority: snapshot };
925
+ }
926
+
927
+ // src/core/context/WorkflowSessionContext.tsx
872
928
  var WorkflowSessionStoreContext = createContext(null);
873
929
  function useWorkflowSessionStoreApi() {
874
930
  const store = useContext(WorkflowSessionStoreContext);
@@ -879,7 +935,7 @@ function useWorkflowSessionStoreApi() {
879
935
  }
880
936
  function useWorkflowSessionStatus() {
881
937
  const store = useWorkflowSessionStoreApi();
882
- return useStore(store, (s) => s.state.status);
938
+ return useStore(store, (s) => getDisplayProjectionStatus(s.state));
883
939
  }
884
940
 
885
941
  // src/core/context/WorkflowLoopReplayContext.tsx
@@ -944,6 +1000,17 @@ function containerTreeToSnapshot(tree) {
944
1000
  }
945
1001
 
946
1002
  // src/types/workflowSession.ts
1003
+ function createEmptyWorkflowDisplayProjection() {
1004
+ return {
1005
+ streamEnded: false,
1006
+ projectionStatus: "connecting",
1007
+ terminalKind: null,
1008
+ openChildIdentities: [],
1009
+ awaitingAuthority: false,
1010
+ authoritySyncFailed: false,
1011
+ authoritySubscribeFailed: false
1012
+ };
1013
+ }
947
1014
  function createEmptyWorkflowProgress() {
948
1015
  return {
949
1016
  workflowId: null,
@@ -955,9 +1022,8 @@ function createEmptyWorkflowProgress() {
955
1022
  }
956
1023
  function createEmptyWorkflowSessionState() {
957
1024
  return {
958
- status: "connecting",
959
- terminalKind: null,
960
- openChildIdentities: [],
1025
+ authority: null,
1026
+ display: createEmptyWorkflowDisplayProjection(),
961
1027
  meta: { workflowRunId: null, startedAt: null, lastEventId: null },
962
1028
  containerTree: createEmptyWorkflowContainerTreeState(),
963
1029
  workflowProgress: createEmptyWorkflowProgress(),
@@ -2150,7 +2216,10 @@ function readOpenChildIdentities(data) {
2150
2216
  if (!Array.isArray(raw)) return [];
2151
2217
  return raw.filter((item) => typeof item === "string");
2152
2218
  }
2153
- function isWorkflowSessionTerminal(status) {
2219
+ function projectionStatusFromTerminalKind(kind, isWorkflowFailedEvent) {
2220
+ return sessionStatusFromTerminalKind(kind, isWorkflowFailedEvent);
2221
+ }
2222
+ function isDisplayProjectionTerminal(status) {
2154
2223
  return status === "done" || status === "done_with_warning" || status === "error";
2155
2224
  }
2156
2225
 
@@ -2523,18 +2592,16 @@ function applyStructureEvent(state, event) {
2523
2592
  containerTree,
2524
2593
  state.workflowProgress
2525
2594
  );
2526
- let status = nextState.status;
2527
- if (workflowProgress.status === "done") status = "done";
2528
- else if (workflowProgress.status === "error") status = "error";
2529
- else if (status === "connecting") status = "running";
2530
- let terminalKind = state.terminalKind;
2531
- let openChildIdentities = state.openChildIdentities;
2595
+ let projectionStatus = nextState.display.projectionStatus;
2596
+ if (projectionStatus === "connecting") projectionStatus = "running";
2597
+ let terminalKind = state.display.terminalKind;
2598
+ let openChildIdentities = state.display.openChildIdentities;
2532
2599
  if (projectedEvent.event_type === "workflow-end" || projectedEvent.event_type === "workflow-failed") {
2533
2600
  const resolved = resolveWorkflowTerminalKind(
2534
2601
  structureData,
2535
2602
  projectedEvent.event_type
2536
2603
  );
2537
- status = sessionStatusFromTerminalKind(
2604
+ projectionStatus = projectionStatusFromTerminalKind(
2538
2605
  resolved,
2539
2606
  projectedEvent.event_type === "workflow-failed"
2540
2607
  );
@@ -2543,9 +2610,12 @@ function applyStructureEvent(state, event) {
2543
2610
  }
2544
2611
  const base = {
2545
2612
  ...nextState,
2546
- status,
2547
- terminalKind,
2548
- openChildIdentities,
2613
+ display: {
2614
+ ...nextState.display,
2615
+ projectionStatus,
2616
+ terminalKind,
2617
+ openChildIdentities
2618
+ },
2549
2619
  containerTree,
2550
2620
  workflowProgress
2551
2621
  };
@@ -2613,11 +2683,9 @@ function reduceWorkflowSession(state, event, eventIndex, options) {
2613
2683
  next.containerTree,
2614
2684
  next.workflowProgress
2615
2685
  );
2616
- let status = next.status;
2617
- if (workflowProgress.status === "done") status = "done";
2618
- else if (workflowProgress.status === "error") status = "error";
2619
- else if (reducedLoop.status === "error") status = "error";
2620
- else if (status === "connecting") status = "running";
2686
+ let projectionStatus = next.display.projectionStatus;
2687
+ if (reducedLoop.status === "error") projectionStatus = "error";
2688
+ else if (projectionStatus === "connecting") projectionStatus = "running";
2621
2689
  return {
2622
2690
  ...next,
2623
2691
  workflowProgress,
@@ -2626,61 +2694,38 @@ function reduceWorkflowSession(state, event, eventIndex, options) {
2626
2694
  [loopSessionId]: reducedLoop
2627
2695
  },
2628
2696
  activeLoopSessionId: loopSessionId,
2629
- status
2697
+ display: {
2698
+ ...next.display,
2699
+ projectionStatus
2700
+ }
2630
2701
  };
2631
2702
  }
2632
- function finalizeWorkflowSessionOnStreamComplete(state) {
2633
- if (isWorkflowSessionTerminal(state.status)) {
2634
- return { ...state, activeLoopSessionId: null };
2635
- }
2636
- const tree = state.containerTree;
2637
- const rootId = tree.rootContainerIds[0];
2638
- if (!rootId) {
2639
- return { ...state, status: "done", activeLoopSessionId: null };
2640
- }
2641
- const root = tree.containersById[rootId];
2642
- if (!root) {
2643
- return { ...state, status: "done", activeLoopSessionId: null };
2703
+ function finalizeWorkflowDisplayOnStreamEnded(state) {
2704
+ if (isDisplayProjectionTerminal(state.display.projectionStatus)) {
2705
+ return {
2706
+ ...state,
2707
+ display: { ...state.display, streamEnded: true },
2708
+ activeLoopSessionId: null
2709
+ };
2644
2710
  }
2645
- const topLevelStages = Object.values(tree.containersById).filter(
2646
- (node) => node.scope === "stage" && node.workflow_path === root.workflow_path && node.workflow_depth === root.workflow_depth
2647
- );
2648
- const progressTotal = root.display?.progress_total ?? (topLevelStages.reduce(
2649
- (max, s) => Math.max(max, s.display?.progress_total ?? 0),
2650
- 0
2651
- ) || topLevelStages.length);
2652
- const settledStageCount = topLevelStages.filter(
2653
- (s) => s.status === "completed" || s.status === "failed" || s.status === "skipped"
2654
- ).length;
2655
- const progressCurrent = Math.max(
2656
- root.display?.progress_current ?? 0,
2657
- settledStageCount
2658
- );
2659
- const rootParallelItems = Object.values(tree.containersById).filter(
2660
- (node) => node.scope === "item" && node.workflow_path === root.workflow_path && node.workflow_depth === root.workflow_depth
2661
- );
2662
- const rootParallelSettled = topLevelStages.length === 0 && rootParallelItems.length > 0 && rootParallelItems.every(
2663
- (item) => item.status === "completed" || item.status === "failed" || item.status === "skipped"
2664
- ) && !Object.values(tree.containersById).some(
2665
- (node) => node.container_id !== rootId && node.workflow_path === root.workflow_path && node.workflow_depth === root.workflow_depth && (node.status === "running" || node.status === "retrying" || node.status === "pending")
2666
- );
2667
- const workflowVisuallyComplete = root.status === "completed" || root.status === "failed" || progressTotal > 0 && progressCurrent >= progressTotal || rootParallelSettled;
2668
- if (!workflowVisuallyComplete) {
2669
- return state;
2711
+ const withStreamEnded = {
2712
+ ...state,
2713
+ display: { ...state.display, streamEnded: true },
2714
+ activeLoopSessionId: null
2715
+ };
2716
+ if (isAuthorityTerminalStatus(state.authority?.status)) {
2717
+ return forceSettleRunningContainersForDisplay(withStreamEnded);
2670
2718
  }
2719
+ return withStreamEnded;
2720
+ }
2721
+ function forceSettleRunningContainersForDisplay(state) {
2722
+ const tree = state.containerTree;
2671
2723
  let containersById = { ...tree.containersById };
2672
2724
  for (const [id, node] of Object.entries(containersById)) {
2673
2725
  if (node.status === "running" || node.status === "pending") {
2674
2726
  containersById[id] = { ...node, status: "completed", is_open: false };
2675
2727
  }
2676
2728
  }
2677
- const settledRoot = containersById[rootId] ?? root;
2678
- if (settledRoot.status !== "completed" && settledRoot.status !== "failed") {
2679
- containersById = {
2680
- ...containersById,
2681
- [rootId]: { ...settledRoot, status: "completed", is_open: false }
2682
- };
2683
- }
2684
2729
  const loopTreesBySessionId = { ...state.loopTreesBySessionId };
2685
2730
  for (const [loopSessionId, loopTree] of Object.entries(loopTreesBySessionId)) {
2686
2731
  if (loopTree.status === "running" || loopTree.status === "connecting") {
@@ -2694,15 +2739,9 @@ function finalizeWorkflowSessionOnStreamComplete(state) {
2694
2739
  (id) => containersById[id]?.is_open
2695
2740
  )
2696
2741
  };
2697
- const workflowProgress = syncWorkflowProgressFromContainerTree(
2698
- containerTree,
2699
- state.workflowProgress
2700
- );
2701
2742
  return {
2702
2743
  ...state,
2703
- status: workflowProgress.status === "error" ? "error" : "done",
2704
2744
  containerTree,
2705
- workflowProgress,
2706
2745
  loopTreesBySessionId,
2707
2746
  activeLoopSessionId: null
2708
2747
  };
@@ -2764,7 +2803,10 @@ function finalizeWorkflowSessionOnStreamError(state, message, eventIndex, stack)
2764
2803
  }
2765
2804
  return {
2766
2805
  ...state,
2767
- status: "error",
2806
+ display: {
2807
+ ...state.display,
2808
+ projectionStatus: "error"
2809
+ },
2768
2810
  containerTree: {
2769
2811
  ...state.containerTree,
2770
2812
  containersById,
@@ -2809,7 +2851,10 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
2809
2851
  state: {
2810
2852
  ...evicted,
2811
2853
  meta: nextMeta,
2812
- status: evicted.status === "connecting" ? "running" : evicted.status
2854
+ display: {
2855
+ ...evicted.display,
2856
+ projectionStatus: evicted.display.projectionStatus === "connecting" ? "running" : evicted.display.projectionStatus
2857
+ }
2813
2858
  },
2814
2859
  eventCount: eventCount + 1
2815
2860
  });
@@ -2839,13 +2884,59 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
2839
2884
  )
2840
2885
  });
2841
2886
  },
2842
- markStreamComplete() {
2887
+ markStreamEnded() {
2843
2888
  set((current) => ({
2844
2889
  state: finalizeState(
2845
- finalizeWorkflowSessionOnStreamComplete(current.state)
2890
+ finalizeWorkflowDisplayOnStreamEnded(current.state)
2846
2891
  )
2847
2892
  }));
2848
2893
  },
2894
+ applyAuthoritySnapshot(snapshot) {
2895
+ set((current) => ({
2896
+ state: {
2897
+ ...current.state,
2898
+ authority: snapshot,
2899
+ display: {
2900
+ ...current.state.display,
2901
+ awaitingAuthority: false,
2902
+ authoritySyncFailed: false
2903
+ }
2904
+ }
2905
+ }));
2906
+ },
2907
+ setAwaitingAuthority(value) {
2908
+ set((current) => ({
2909
+ state: {
2910
+ ...current.state,
2911
+ display: {
2912
+ ...current.state.display,
2913
+ awaitingAuthority: value
2914
+ }
2915
+ }
2916
+ }));
2917
+ },
2918
+ setAuthoritySyncFailed(value) {
2919
+ set((current) => ({
2920
+ state: {
2921
+ ...current.state,
2922
+ display: {
2923
+ ...current.state.display,
2924
+ authoritySyncFailed: value
2925
+ }
2926
+ }
2927
+ }));
2928
+ },
2929
+ setAuthoritySubscribeFailed(value) {
2930
+ set((current) => ({
2931
+ state: {
2932
+ ...current.state,
2933
+ display: {
2934
+ ...current.state.display,
2935
+ authoritySubscribeFailed: value
2936
+ }
2937
+ }
2938
+ }));
2939
+ },
2849
2940
  /**
2850
2941
  * P2:将 completed loop 加入 LRU 热缓存并淘汰溢出。
2851
2942
  * running 保护由 collectProtectedLoopSessionIds 按状态自动覆盖,不再依赖永久 pin。
@@ -2952,11 +3043,93 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
2952
3043
  }));
2953
3044
  }
2954
3045
 
3046
+ // src/core/workflow/useWorkflowAuthorityBinding.ts
3047
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
3048
+
3049
+ // src/core/workflow/normalizeAuthoritySnapshot.ts
3050
+ var KNOWN = /* @__PURE__ */ new Set([
3051
+ "pending",
3052
+ "running",
3053
+ "done",
3054
+ "error",
3055
+ "canceled",
3056
+ "partial_success"
3057
+ ]);
3058
+ function normalizeAuthoritySnapshot(snapshot) {
3059
+ if (KNOWN.has(snapshot.status)) {
3060
+ return snapshot;
3061
+ }
3062
+ if (import.meta.env?.DEV) {
3063
+ console.warn(
3064
+ `[langchain_agentx_stream_ui] unknown authority.status "${String(snapshot.status)}"; defaulting to running`
3065
+ );
3066
+ }
3067
+ return { ...snapshot, status: "running" };
3068
+ }
3069
+
3070
+ // src/core/workflow/useWorkflowAuthorityBinding.ts
3071
+ function useWorkflowAuthorityBinding(store, authoritySource, callbacks) {
3072
+ const onAuthorityTerminalRef = useRef3(callbacks.onAuthorityTerminal);
3073
+ onAuthorityTerminalRef.current = callbacks.onAuthorityTerminal;
3074
+ const terminalFiredRef = useRef3(false);
3075
+ useEffect3(() => {
3076
+ terminalFiredRef.current = false;
3077
+ const apply = (raw) => {
3078
+ const snapshot = normalizeAuthoritySnapshot(raw);
3079
+ store.getState().applyAuthoritySnapshot(snapshot);
3080
+ if (!terminalFiredRef.current && isAuthorityTerminalStatus(snapshot.status)) {
3081
+ terminalFiredRef.current = true;
3082
+ onAuthorityTerminalRef.current?.(snapshot);
3083
+ }
3084
+ };
3085
+ const initial = authoritySource.getSnapshot();
3086
+ if (initial) {
3087
+ apply(initial);
3088
+ }
3089
+ let unsubscribe;
3090
+ try {
3091
+ unsubscribe = authoritySource.subscribe(apply);
3092
+ } catch (err) {
3093
+ if (import.meta.env?.DEV) {
3094
+ console.error("[langchain_agentx_stream_ui] authoritySource.subscribe failed:", err);
3095
+ }
3096
+ store.getState().setAuthoritySubscribeFailed(true);
3097
+ }
3098
+ return () => {
3099
+ unsubscribe?.();
3100
+ };
3101
+ }, [store, authoritySource]);
3102
+ }
3103
+
3104
+ // src/core/workflow/useWorkflowAwaitingAuthorityTimers.ts
3105
+ import { useEffect as useEffect4 } from "react";
3106
+ var AWAITING_MS = 3e4;
3107
+ var SYNC_FAILED_MS = 12e4;
3108
+ function useWorkflowAwaitingAuthorityTimers(store, streamEnded, authorityStatus) {
3109
+ useEffect4(() => {
3110
+ if (!streamEnded || authorityStatus !== "running") {
3111
+ store.getState().setAwaitingAuthority(false);
3112
+ store.getState().setAuthoritySyncFailed(false);
3113
+ return;
3114
+ }
3115
+ const tAwait = window.setTimeout(() => {
3116
+ store.getState().setAwaitingAuthority(true);
3117
+ }, AWAITING_MS);
3118
+ const tFail = window.setTimeout(() => {
3119
+ store.getState().setAuthoritySyncFailed(true);
3120
+ }, SYNC_FAILED_MS);
3121
+ return () => {
3122
+ window.clearTimeout(tAwait);
3123
+ window.clearTimeout(tFail);
3124
+ };
3125
+ }, [store, streamEnded, authorityStatus]);
3126
+ }
3127
+
2955
3128
  // src/view/workflow/WorkflowChrome.tsx
2956
3129
  import { useMemo as useMemo7, useState as useState6 } from "react";
2957
3130
 
2958
3131
  // src/view/workflow/WorkflowAggregateContainer.tsx
2959
- import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState, memo } from "react";
3132
+ import { useCallback, useEffect as useEffect5, useMemo as useMemo2, useState, memo } from "react";
2960
3133
 
2961
3134
  // src/view/workflow/WorkflowContainerLine.tsx
2962
3135
  import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -3366,7 +3539,7 @@ function WorkflowAggregateContainerInner({
3366
3539
  const uiStatus = mapAggregateUiStatus(node);
3367
3540
  const statusLabel = buildAggregateStatusLabel(node, siblingItems);
3368
3541
  const [expanded, setExpanded] = useState(isRunning);
3369
- useEffect3(() => {
3542
+ useEffect5(() => {
3370
3543
  if (isRunning) setExpanded(true);
3371
3544
  if (isDone) setExpanded(false);
3372
3545
  }, [isRunning, isDone]);
@@ -3485,7 +3658,7 @@ var WorkflowAggregateContainer = memo(
3485
3658
  );
3486
3659
 
3487
3660
  // src/view/workflow/WorkflowParallelGroup.tsx
3488
- import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo4, useState as useState3, memo as memo3 } from "react";
3661
+ import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo4, useState as useState3, memo as memo3 } from "react";
3489
3662
 
3490
3663
  // src/core/workflow/workflowParallelGroupMetrics.ts
3491
3664
  var ACTIVE_ITEM_STATUSES = /* @__PURE__ */ new Set([
@@ -3579,7 +3752,7 @@ function formatParallelGroupSummary(metrics) {
3579
3752
  }
3580
3753
 
3581
3754
  // src/view/workflow/WorkflowStageContainer.tsx
3582
- import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3, useState as useState2, memo as memo2 } from "react";
3755
+ import { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo3, useState as useState2, memo as memo2 } from "react";
3583
3756
  import { useStore as useStore2 } from "zustand";
3584
3757
 
3585
3758
  // src/core/workflow/loopTreeUtils.ts
@@ -3666,7 +3839,7 @@ function WorkflowStageContainerInner({
3666
3839
  const doneSummary = doneSummaryFromTree ?? (isDone ? cachedSummary : null);
3667
3840
  const shouldAutoExpandRunning = effectivelyRunning && !isSkipped;
3668
3841
  const [expanded, setExpanded] = useState2(shouldAutoExpandRunning);
3669
- useEffect4(() => {
3842
+ useEffect6(() => {
3670
3843
  if (shouldAutoExpandRunning) {
3671
3844
  setExpanded(true);
3672
3845
  return;
@@ -3691,7 +3864,7 @@ function WorkflowStageContainerInner({
3691
3864
  return next;
3692
3865
  });
3693
3866
  }, [isDone, node.loopSessionId, loopTree, requestLoopHydration]);
3694
- useEffect4(() => {
3867
+ useEffect6(() => {
3695
3868
  const onKeyDown = (event) => {
3696
3869
  if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "o") return;
3697
3870
  setExpanded(true);
@@ -3844,7 +4017,7 @@ function WorkflowParallelGroupInner({
3844
4017
  );
3845
4018
  const allSettled = metrics.total > 0 && metrics.running === 0 && metrics.completed + metrics.failed >= metrics.total;
3846
4019
  const [expanded, setExpanded] = useState3(hasActiveWork);
3847
- useEffect5(() => {
4020
+ useEffect7(() => {
3848
4021
  if (hasActiveWork) {
3849
4022
  setExpanded(true);
3850
4023
  return;
@@ -3856,7 +4029,7 @@ function WorkflowParallelGroupInner({
3856
4029
  const toggleExpanded = useCallback3(() => {
3857
4030
  setExpanded((prev) => !prev);
3858
4031
  }, []);
3859
- useEffect5(() => {
4032
+ useEffect7(() => {
3860
4033
  const onKeyDown = (event) => {
3861
4034
  if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "o") return;
3862
4035
  setExpanded(true);
@@ -3956,7 +4129,7 @@ function WorkflowRootContainer({
3956
4129
  }
3957
4130
 
3958
4131
  // src/view/workflow/WorkflowRouteContainer.tsx
3959
- import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo5, useState as useState4 } from "react";
4132
+ import { useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo5, useState as useState4 } from "react";
3960
4133
  import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
3961
4134
  function WorkflowRouteContainer({
3962
4135
  node,
@@ -3981,7 +4154,7 @@ function WorkflowRouteContainer({
3981
4154
  const expandHint = doneSummary ? formatTeaserExpandHint(doneSummary.extraLines, verbose) : null;
3982
4155
  const doneLabel = doneSummary?.label ?? "done";
3983
4156
  const [expanded, setExpanded] = useState4(isRunning);
3984
- useEffect6(() => {
4157
+ useEffect8(() => {
3985
4158
  if (isSkipped) return;
3986
4159
  if (isRunning) setExpanded(true);
3987
4160
  if (isDone) setExpanded(false);
@@ -4057,7 +4230,7 @@ function WorkflowRouteContainer({
4057
4230
  }
4058
4231
 
4059
4232
  // src/view/workflow/WorkflowSubworkflowContainer.tsx
4060
- import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo6, useState as useState5 } from "react";
4233
+ import { useCallback as useCallback5, useEffect as useEffect9, useMemo as useMemo6, useState as useState5 } from "react";
4061
4234
  import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
4062
4235
  function buildDoneSummary(node) {
4063
4236
  const display = node.display;
@@ -4088,7 +4261,7 @@ function WorkflowSubworkflowContainer({
4088
4261
  return node.content_blocks[node.content_blocks.length - 1]?.preview ?? "";
4089
4262
  }, [node.content_blocks]);
4090
4263
  const [expanded, setExpanded] = useState5(effectivelyRunning);
4091
- useEffect7(() => {
4264
+ useEffect9(() => {
4092
4265
  if (effectivelyRunning) setExpanded(true);
4093
4266
  else if (effectivelyDone) setExpanded(false);
4094
4267
  }, [effectivelyRunning, effectivelyDone]);
@@ -4440,9 +4613,94 @@ function WorkflowChrome({
4440
4613
  )) });
4441
4614
  }
4442
4615
 
4616
+ // src/view/workflow/WorkflowGlobalSpinner.tsx
4617
+ import { useStore as useStore3 } from "zustand";
4618
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
4619
+ function WorkflowGlobalSpinner() {
4620
+ const store = useWorkflowSessionStoreApi();
4621
+ const show = useStore3(store, (s) => shouldShowGlobalSpinner(s.state));
4622
+ if (!show) return null;
4623
+ return /* @__PURE__ */ jsx16(
4624
+ "div",
4625
+ {
4626
+ className: "lax-workflow-global-spinner lax-spinner-container",
4627
+ "data-testid": "lax-workflow-global-spinner",
4628
+ role: "status",
4629
+ children: /* @__PURE__ */ jsxs11("span", { className: "lax-spinner lax-workflow-global-spinner__inner", children: [
4630
+ /* @__PURE__ */ jsx16("span", { className: "lax-spinner-frame", children: "\u280B" }),
4631
+ /* @__PURE__ */ jsx16("span", { className: "lax-spinner-verb lax-spinner-verb--shimmer", children: "Workflow" })
4632
+ ] })
4633
+ }
4634
+ );
4635
+ }
4636
+
4637
+ // src/view/workflow/WorkflowSessionBanners.tsx
4638
+ import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
4639
+ function WorkflowSessionBanners({ state }) {
4640
+ const banner = resolveWorkflowBanner(state);
4641
+ const { display } = state;
4642
+ if (banner.kind === "none" && display.projectionStatus !== "error" && display.projectionStatus !== "done_with_warning" && state.internalErrors.length === 0) {
4643
+ return null;
4644
+ }
4645
+ return /* @__PURE__ */ jsxs12(Fragment3, { children: [
4646
+ banner.kind === "partial_success" ? /* @__PURE__ */ jsx17(
4647
+ "div",
4648
+ {
4649
+ className: "lax-workflow-banner lax-workflow-banner--partial-success",
4650
+ "data-testid": "lax-workflow-partial-success-banner",
4651
+ role: "status",
4652
+ children: banner.message
4653
+ }
4654
+ ) : null,
4655
+ banner.kind === "forced_closeout" || display.projectionStatus === "done_with_warning" ? /* @__PURE__ */ jsxs12(
4656
+ "div",
4657
+ {
4658
+ className: "lax-workflow-closeout-warning",
4659
+ "data-testid": "lax-workflow-closeout-warning",
4660
+ role: "status",
4661
+ children: [
4662
+ /* @__PURE__ */ jsx17("strong", { children: "Workflow forced closeout" }),
4663
+ /* @__PURE__ */ jsxs12("span", { children: [
4664
+ " ",
4665
+ "\u2014 stream ended with unresolved child scopes; not a healthy completion."
4666
+ ] }),
4667
+ display.openChildIdentities.length > 0 ? /* @__PURE__ */ jsx17("ul", { className: "lax-workflow-closeout-warning__scopes", children: display.openChildIdentities.map((identity) => /* @__PURE__ */ jsx17("li", { children: identity }, identity)) }) : null
4668
+ ]
4669
+ }
4670
+ ) : null,
4671
+ banner.kind === "awaiting_authority" ? /* @__PURE__ */ jsx17(
4672
+ "div",
4673
+ {
4674
+ className: "lax-workflow-banner lax-workflow-banner--awaiting",
4675
+ "data-testid": "lax-workflow-awaiting-authority-banner",
4676
+ role: "status",
4677
+ children: banner.message
4678
+ }
4679
+ ) : null,
4680
+ banner.kind === "sync_failed" ? /* @__PURE__ */ jsx17(
4681
+ "div",
4682
+ {
4683
+ className: "lax-workflow-banner lax-workflow-banner--sync-failed",
4684
+ "data-testid": "lax-workflow-authority-sync-failed-banner",
4685
+ role: "alert",
4686
+ children: banner.message
4687
+ }
4688
+ ) : null,
4689
+ display.projectionStatus === "error" || state.internalErrors.length > 0 ? /* @__PURE__ */ jsx17(
4690
+ "div",
4691
+ {
4692
+ className: "lax-workflow-error",
4693
+ "data-testid": "lax-workflow-error",
4694
+ role: "alert",
4695
+ children: state.internalErrors[state.internalErrors.length - 1]?.message ?? "Workflow stream error"
4696
+ }
4697
+ ) : null
4698
+ ] });
4699
+ }
4700
+
4443
4701
  // src/view/workflow/WorkflowTaskListFooter.tsx
4444
4702
  import { useMemo as useMemo8, useState as useState7 } from "react";
4445
- import { useStore as useStore3 } from "zustand";
4703
+ import { useStore as useStore4 } from "zustand";
4446
4704
 
4447
4705
  // src/view/workflow/workflowTaskListMerge.ts
4448
4706
  var STATUS_ORDER = {
@@ -4483,7 +4741,7 @@ function deriveWorkflowTaskFooterState(state) {
4483
4741
  tasks: mergeWorkflowLoopTasks(state.loopTreesBySessionId),
4484
4742
  taskPhase: deriveWorkflowTaskPhase(trees),
4485
4743
  hasEverShownTaskList: trees.some((tree) => tree.hasEverShownTaskList),
4486
- sessionStatus: state.status
4744
+ sessionStatus: getDisplayProjectionStatus(state)
4487
4745
  };
4488
4746
  }
4489
4747
  function shouldShowWorkflowTaskListFooter(state) {
@@ -4513,16 +4771,16 @@ function loopSessionDisplayLabel(loopSessionId) {
4513
4771
  }
4514
4772
 
4515
4773
  // src/view/workflow/WorkflowTaskListFooter.tsx
4516
- import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
4774
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
4517
4775
  function MessageResponse({ children }) {
4518
- return /* @__PURE__ */ jsxs11("div", { className: "lax-message-response", children: [
4519
- /* @__PURE__ */ jsx16("span", { className: "lax-message-response__marker", children: "\u23BF " }),
4520
- /* @__PURE__ */ jsx16("span", { className: "lax-message-response__content", children })
4776
+ return /* @__PURE__ */ jsxs13("div", { className: "lax-message-response", children: [
4777
+ /* @__PURE__ */ jsx18("span", { className: "lax-message-response__marker", children: "\u23BF " }),
4778
+ /* @__PURE__ */ jsx18("span", { className: "lax-message-response__content", children })
4521
4779
  ] });
4522
4780
  }
4523
4781
  function WorkflowTaskListFooter() {
4524
4782
  const store = useWorkflowSessionStoreApi();
4525
- const state = useStore3(store, (s) => s.state);
4783
+ const state = useStore4(store, (s) => s.state);
4526
4784
  const [expanded, setExpanded] = useState7(false);
4527
4785
  const footerState = useMemo8(() => deriveWorkflowTaskFooterState(state), [state]);
4528
4786
  const visible = shouldShowWorkflowTaskListFooter(state);
@@ -4531,8 +4789,8 @@ function WorkflowTaskListFooter() {
4531
4789
  }
4532
4790
  const label = formatWorkflowTaskFooterLabel(state);
4533
4791
  const groups = groupWorkflowTasksByLoop(footerState.tasks);
4534
- return /* @__PURE__ */ jsxs11("div", { className: "lax-workflow-task-list-footer", "data-testid": "lax-workflow-task-list-footer", children: [
4535
- /* @__PURE__ */ jsxs11(
4792
+ return /* @__PURE__ */ jsxs13("div", { className: "lax-workflow-task-list-footer", "data-testid": "lax-workflow-task-list-footer", children: [
4793
+ /* @__PURE__ */ jsxs13(
4536
4794
  "button",
4537
4795
  {
4538
4796
  type: "button",
@@ -4542,18 +4800,18 @@ function WorkflowTaskListFooter() {
4542
4800
  onClick: () => setExpanded((v) => !v),
4543
4801
  children: [
4544
4802
  label,
4545
- /* @__PURE__ */ jsx16("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
4803
+ /* @__PURE__ */ jsx18("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
4546
4804
  ]
4547
4805
  }
4548
4806
  ),
4549
- expanded ? /* @__PURE__ */ jsx16(MessageResponse, { children: /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs11(
4807
+ expanded ? /* @__PURE__ */ jsx18(MessageResponse, { children: /* @__PURE__ */ jsx18("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs13(
4550
4808
  "div",
4551
4809
  {
4552
4810
  className: "lax-workflow-task-list-group",
4553
4811
  "data-testid": `lax-workflow-task-group-${group.loopSessionId}`,
4554
4812
  children: [
4555
- /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
4556
- /* @__PURE__ */ jsx16(TaskList, { tasks: group.tasks })
4813
+ /* @__PURE__ */ jsx18("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
4814
+ /* @__PURE__ */ jsx18(TaskList, { tasks: group.tasks })
4557
4815
  ]
4558
4816
  },
4559
4817
  group.loopSessionId
@@ -4562,10 +4820,11 @@ function WorkflowTaskListFooter() {
4562
4820
  }
4563
4821
 
4564
4822
  // src/view/workflow/WorkflowSession.tsx
4565
- import { useStore as useStore4 } from "zustand";
4566
- import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
4823
+ import { useStore as useStore5 } from "zustand";
4824
+ import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
4567
4825
  function WorkflowSession({
4568
4826
  source,
4827
+ authoritySource,
4569
4828
  initialEvents,
4570
4829
  registry,
4571
4830
  tierOverrides,
@@ -4585,10 +4844,11 @@ function WorkflowSession({
4585
4844
  groupParallelTools = false,
4586
4845
  workflowScale,
4587
4846
  onRequestLoopReplay,
4588
- onStreamComplete,
4847
+ onAuthorityTerminal,
4848
+ onDisplayStreamEnded,
4589
4849
  children
4590
4850
  }) {
4591
- const storeRef = useRef3(null);
4851
+ const storeRef = useRef4(null);
4592
4852
  if (storeRef.current === null) {
4593
4853
  const initialState = initialEvents && initialEvents.length > 0 ? reduceWorkflowEvents(initialEvents, { tierOverrides }) : void 0;
4594
4854
  storeRef.current = createWorkflowSessionStore(initialState, {
@@ -4598,6 +4858,11 @@ function WorkflowSession({
4598
4858
  maxHydratedCompletedLoops: workflowScale?.maxHydratedCompletedLoops ?? DEFAULT_WORKFLOW_SCALE_OPTIONS.maxHydratedCompletedLoops
4599
4859
  });
4600
4860
  }
4861
+ const store = storeRef.current;
4862
+ useWorkflowAuthorityBinding(store, authoritySource, { onAuthorityTerminal });
4863
+ const streamEnded = useStore5(store, (s) => s.state.display.streamEnded);
4864
+ const authorityStatus = useStore5(store, (s) => s.state.authority?.status);
4865
+ useWorkflowAwaitingAuthorityTimers(store, streamEnded, authorityStatus);
4601
4866
  const scaleOptions = useMemo9(
4602
4867
  () => ({ ...DEFAULT_WORKFLOW_SCALE_OPTIONS, ...workflowScale }),
4603
4868
  [workflowScale]
@@ -4635,38 +4900,20 @@ function WorkflowSession({
4635
4900
  workspaceRoot
4636
4901
  ]
4637
4902
  );
4638
- const containerTree = useStore4(
4639
- storeRef.current,
4640
- (s) => s.state.containerTree
4641
- );
4642
- const loopTreesBySessionId = useStore4(
4643
- storeRef.current,
4644
- (s) => s.state.loopTreesBySessionId
4645
- );
4646
- const openChildIdentities = useStore4(
4647
- storeRef.current,
4648
- (s) => s.state.openChildIdentities
4649
- );
4650
- const sessionStatus = useStore4(
4651
- storeRef.current,
4652
- (s) => s.state.status
4653
- );
4654
- const internalErrors = useStore4(
4655
- storeRef.current,
4656
- (s) => s.state.internalErrors
4657
- );
4658
- const onErrorRef = useRef3(onError);
4659
- const onStreamCompleteRef = useRef3(onStreamComplete);
4903
+ const sessionState = useStore5(store, (s) => s.state);
4904
+ const containerTree = sessionState.containerTree;
4905
+ const loopTreesBySessionId = sessionState.loopTreesBySessionId;
4906
+ const onErrorRef = useRef4(onError);
4907
+ const onDisplayStreamEndedRef = useRef4(onDisplayStreamEnded);
4660
4908
  onErrorRef.current = onError;
4661
- onStreamCompleteRef.current = onStreamComplete;
4662
- useEffect8(() => {
4663
- const store = storeRef.current;
4909
+ onDisplayStreamEndedRef.current = onDisplayStreamEnded;
4910
+ useEffect10(() => {
4664
4911
  const controller = new AbortController();
4665
4912
  void source.start((event, ctx) => {
4666
4913
  store.getState().applyEvent(event, ctx);
4667
4914
  }, controller.signal).then(() => {
4668
- store.getState().markStreamComplete();
4669
- onStreamCompleteRef.current?.();
4915
+ store.getState().markStreamEnded();
4916
+ onDisplayStreamEndedRef.current?.();
4670
4917
  }).catch((err) => {
4671
4918
  if (err instanceof SseTransportTerminalError) {
4672
4919
  store.getState().markAsError(err);
@@ -4678,39 +4925,16 @@ function WorkflowSession({
4678
4925
  return () => {
4679
4926
  controller.abort();
4680
4927
  };
4681
- }, [source]);
4682
- 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(
4928
+ }, [source, store]);
4929
+ return /* @__PURE__ */ jsx19(WorkflowSessionStoreContext.Provider, { value: store, children: /* @__PURE__ */ jsx19(WorkflowScaleContext.Provider, { value: scaleOptions, children: /* @__PURE__ */ jsx19(WorkflowLoopReplayProvider, { onRequestLoopReplay, children: /* @__PURE__ */ jsx19(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx19(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx19(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx19(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx19(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs14(
4683
4930
  "div",
4684
4931
  {
4685
4932
  className: "lax-agent-session lax-workflow-session",
4686
4933
  "data-testid": "lax-workflow-session",
4687
4934
  children: [
4688
- sessionStatus === "done_with_warning" ? /* @__PURE__ */ jsxs12(
4689
- "div",
4690
- {
4691
- className: "lax-workflow-closeout-warning",
4692
- "data-testid": "lax-workflow-closeout-warning",
4693
- role: "status",
4694
- children: [
4695
- /* @__PURE__ */ jsx17("strong", { children: "Workflow forced closeout" }),
4696
- /* @__PURE__ */ jsxs12("span", { children: [
4697
- " ",
4698
- "\u2014 stream ended with unresolved child scopes; not a healthy completion."
4699
- ] }),
4700
- openChildIdentities.length > 0 ? /* @__PURE__ */ jsx17("ul", { className: "lax-workflow-closeout-warning__scopes", children: openChildIdentities.map((identity) => /* @__PURE__ */ jsx17("li", { children: identity }, identity)) }) : null
4701
- ]
4702
- }
4703
- ) : null,
4704
- sessionStatus === "error" || internalErrors.length > 0 ? /* @__PURE__ */ jsx17(
4705
- "div",
4706
- {
4707
- className: "lax-workflow-error",
4708
- "data-testid": "lax-workflow-error",
4709
- role: "alert",
4710
- children: internalErrors[internalErrors.length - 1]?.message ?? "Workflow stream error"
4711
- }
4712
- ) : null,
4713
- /* @__PURE__ */ jsx17(
4935
+ /* @__PURE__ */ jsx19(WorkflowSessionBanners, { state: sessionState }),
4936
+ /* @__PURE__ */ jsx19(WorkflowGlobalSpinner, {}),
4937
+ /* @__PURE__ */ jsx19(
4714
4938
  WorkflowChrome,
4715
4939
  {
4716
4940
  containerTree,
@@ -4720,13 +4944,158 @@ function WorkflowSession({
4720
4944
  groupParallelTools
4721
4945
  }
4722
4946
  ),
4723
- /* @__PURE__ */ jsx17(WorkflowTaskListFooter, {}),
4947
+ /* @__PURE__ */ jsx19(WorkflowTaskListFooter, {}),
4724
4948
  children
4725
4949
  ]
4726
4950
  }
4727
4951
  ) }) }) }) }) }) }) }) });
4728
4952
  }
4729
4953
 
4954
+ // src/core/workflow/createMockAuthoritySource.ts
4955
+ function defaultDoneSnapshot() {
4956
+ return {
4957
+ taskId: "mock-task",
4958
+ status: "done",
4959
+ updatedAt: Date.now()
4960
+ };
4961
+ }
4962
+ function defaultRunningSnapshot() {
4963
+ return {
4964
+ taskId: "mock-task",
4965
+ status: "running",
4966
+ updatedAt: Date.now()
4967
+ };
4968
+ }
4969
+ function createMockAuthoritySource(snapshotOrOptions) {
4970
+ let snapshot = null;
4971
+ let pendingUpdates = [];
4972
+ if (snapshotOrOptions == null) {
4973
+ snapshot = defaultRunningSnapshot();
4974
+ } else if (typeof snapshotOrOptions === "object" && ("pendingUpdates" in snapshotOrOptions || "snapshot" in snapshotOrOptions) && !("status" in snapshotOrOptions)) {
4975
+ const opts = snapshotOrOptions;
4976
+ snapshot = opts.snapshot ?? defaultDoneSnapshot();
4977
+ pendingUpdates = opts.pendingUpdates ?? [];
4978
+ } else if (snapshotOrOptions === null) {
4979
+ snapshot = null;
4980
+ } else {
4981
+ snapshot = snapshotOrOptions;
4982
+ }
4983
+ return {
4984
+ getSnapshot: () => snapshot,
4985
+ subscribe(cb) {
4986
+ if (snapshot) {
4987
+ cb(snapshot);
4988
+ }
4989
+ for (const update of pendingUpdates) {
4990
+ snapshot = update;
4991
+ cb(update);
4992
+ }
4993
+ return () => {
4994
+ };
4995
+ }
4996
+ };
4997
+ }
4998
+ function createDoneAuthoritySource(taskId = "mock-task") {
4999
+ return createMockAuthoritySource({
5000
+ taskId,
5001
+ status: "done",
5002
+ updatedAt: Date.now()
5003
+ });
5004
+ }
5005
+ function createRunningAuthoritySource(taskId = "mock-task") {
5006
+ return createMockAuthoritySource({
5007
+ taskId,
5008
+ status: "running",
5009
+ updatedAt: Date.now()
5010
+ });
5011
+ }
5012
+ function createControllableAuthoritySource(initial) {
5013
+ let snapshot = initial;
5014
+ const listeners = /* @__PURE__ */ new Set();
5015
+ return {
5016
+ getSnapshot: () => snapshot,
5017
+ subscribe(cb) {
5018
+ listeners.add(cb);
5019
+ cb(snapshot);
5020
+ return () => {
5021
+ listeners.delete(cb);
5022
+ };
5023
+ },
5024
+ push(next) {
5025
+ snapshot = next;
5026
+ for (const listener of listeners) {
5027
+ listener(next);
5028
+ }
5029
+ }
5030
+ };
5031
+ }
5032
+
5033
+ // src/core/workflow/createPollingAuthoritySource.ts
5034
+ var DEFAULT_INTERVAL_MS = 2e3;
5035
+ function createPollingAuthoritySource(taskId, fetchFn, options) {
5036
+ let cached = options?.initialSnapshot ?? null;
5037
+ let intervalId;
5038
+ let pollInFlight = false;
5039
+ const listeners = /* @__PURE__ */ new Set();
5040
+ const notify = (snap) => {
5041
+ cached = snap;
5042
+ for (const listener of listeners) {
5043
+ listener(snap);
5044
+ }
5045
+ };
5046
+ const stopPoll = () => {
5047
+ if (intervalId != null) {
5048
+ clearInterval(intervalId);
5049
+ intervalId = void 0;
5050
+ }
5051
+ };
5052
+ const pollOnce = async () => {
5053
+ if (pollInFlight || options?.signal?.aborted) return;
5054
+ pollInFlight = true;
5055
+ try {
5056
+ const snap = await fetchFn(taskId);
5057
+ const prev = cached;
5058
+ if (prev == null || prev.status !== snap.status || prev.updatedAt !== snap.updatedAt) {
5059
+ notify(snap);
5060
+ }
5061
+ if (isAuthorityTerminalStatus(snap.status)) {
5062
+ stopPoll();
5063
+ }
5064
+ } finally {
5065
+ pollInFlight = false;
5066
+ }
5067
+ };
5068
+ const startPoll = () => {
5069
+ if (intervalId != null || options?.signal?.aborted) return;
5070
+ if (cached != null && isAuthorityTerminalStatus(cached.status)) {
5071
+ return;
5072
+ }
5073
+ void pollOnce();
5074
+ intervalId = setInterval(() => {
5075
+ void pollOnce();
5076
+ }, options?.intervalMs ?? DEFAULT_INTERVAL_MS);
5077
+ };
5078
+ options?.signal?.addEventListener("abort", stopPoll, { once: true });
5079
+ return {
5080
+ getSnapshot: () => cached,
5081
+ subscribe(cb) {
5082
+ listeners.add(cb);
5083
+ if (cached != null) {
5084
+ cb(cached);
5085
+ }
5086
+ if (cached == null || !isAuthorityTerminalStatus(cached.status)) {
5087
+ startPoll();
5088
+ }
5089
+ return () => {
5090
+ listeners.delete(cb);
5091
+ if (listeners.size === 0) {
5092
+ stopPoll();
5093
+ }
5094
+ };
5095
+ }
5096
+ };
5097
+ }
5098
+
4730
5099
  // src/view/tools/groupParallelTools.ts
4731
5100
  function buildTimelineEntries(rootIds, byId, options = {}) {
4732
5101
  const filtered = options.hideStandalonePermissions ? rootIds.filter((id) => byId[id]?.kind !== "permission") : [...rootIds];
@@ -4764,18 +5133,18 @@ function buildTimelineEntries(rootIds, byId, options = {}) {
4764
5133
  }
4765
5134
 
4766
5135
  // src/view/nodes/SubAgentNode.tsx
4767
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
5136
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
4768
5137
  function SubAgentBlock({ nodeId }) {
4769
5138
  const node = useNodeTyped(nodeId, "subagent");
4770
5139
  const childIds = useChildren(nodeId);
4771
5140
  const registry = useNodeRegistry();
4772
5141
  if (!node) return null;
4773
- return /* @__PURE__ */ jsxs13("div", { className: "lax-subagent-block", "data-status": node.status, children: [
4774
- /* @__PURE__ */ jsxs13("div", { className: "lax-subagent-block__header", children: [
5142
+ return /* @__PURE__ */ jsxs15("div", { className: "lax-subagent-block", "data-status": node.status, children: [
5143
+ /* @__PURE__ */ jsxs15("div", { className: "lax-subagent-block__header", children: [
4775
5144
  "SubAgent: ",
4776
5145
  node.subagentId
4777
5146
  ] }),
4778
- /* @__PURE__ */ jsx18("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx18(TimelineItem, { nodeId: childId, registry }, childId)) })
5147
+ /* @__PURE__ */ jsx20("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx20(TimelineItem, { nodeId: childId, registry }, childId)) })
4779
5148
  ] });
4780
5149
  }
4781
5150
 
@@ -4894,6 +5263,7 @@ export {
4894
5263
  WorkflowTaskListFooter,
4895
5264
  Write,
4896
5265
  WriteToolBody,
5266
+ applyAuthoritySnapshotToState,
4897
5267
  buildBashBodyBlocks,
4898
5268
  buildCanonicalFromTree,
4899
5269
  buildProjectionContext,
@@ -4909,21 +5279,27 @@ export {
4909
5279
  configurePlantumlServer,
4910
5280
  createActiveSessionBridge,
4911
5281
  createAgentxSseSource,
5282
+ createControllableAuthoritySource,
4912
5283
  createDefaultRegistry,
4913
5284
  createDefaultToolRegistry,
5285
+ createDoneAuthoritySource,
4914
5286
  createEmptyTree,
4915
5287
  createEmptyWorkflowSessionState,
4916
5288
  createInteractionBus,
5289
+ createMockAuthoritySource,
4917
5290
  createMockSource,
4918
5291
  createMultiSessionStore,
5292
+ createPollingAuthoritySource,
4919
5293
  createProjectionContext,
4920
5294
  createReplaySource,
5295
+ createRunningAuthoritySource,
4921
5296
  createSessionStore,
4922
5297
  createWorkflowSessionStore,
4923
5298
  deriveLoopSessionIdFromAgentEvent,
4924
5299
  displayPath,
4925
5300
  entryKey,
4926
5301
  filterAgentLoopReplayEvents,
5302
+ finalizeWorkflowDisplayOnStreamEnded,
4927
5303
  fixDiagramSvg,
4928
5304
  formatAgentTitle,
4929
5305
  formatDiffFromStrings,
@@ -4936,9 +5312,12 @@ export {
4936
5312
  formatTimeoutFooter,
4937
5313
  formatToolTitle,
4938
5314
  getChildIds,
5315
+ getDisplayProjectionStatus,
4939
5316
  getMainStageIds,
4940
5317
  getNoopInteractionBus,
4941
5318
  isAgentLoopReplayEventType,
5319
+ isAuthorityTerminal,
5320
+ isAuthorityTerminalStatus,
4942
5321
  isGitOperationCommand,
4943
5322
  isGroupableToolName,
4944
5323
  isMcpToolName,
@@ -4962,8 +5341,10 @@ export {
4962
5341
  resolveShellProgressFromPayload,
4963
5342
  resolveSseTransportPolicy,
4964
5343
  resolveToolBody,
5344
+ resolveWorkflowBanner,
4965
5345
  shouldApplyGrouping,
4966
5346
  shouldShowExploreDetail,
5347
+ shouldShowGlobalSpinner,
4967
5348
  shouldShowTaskListFooter,
4968
5349
  streamChunk,
4969
5350
  truncateCommand,