langchain_agentx_stream_ui 0.2.6 → 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
@@ -263,6 +263,17 @@ var BOUNDED_DEFAULTS = {
263
263
  reconnectTimeoutMs: 6e4,
264
264
  reconnectDelayMs: 1e3
265
265
  };
266
+ var legacyAutoReconnectWarned = false;
267
+ function warnLegacyAutoReconnectOnce() {
268
+ if (legacyAutoReconnectWarned) return;
269
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") {
270
+ return;
271
+ }
272
+ legacyAutoReconnectWarned = true;
273
+ console.warn(
274
+ "[langchain_agentx_stream_ui] autoReconnect is deprecated; use profile or policy.reconnect instead."
275
+ );
276
+ }
266
277
  var AGENT_TERMINAL = {
267
278
  primary: ["finish"],
268
279
  allowFinishFallback: false
@@ -353,6 +364,9 @@ function resolveSseTransportPolicy(input) {
353
364
  reconnectDelayMs,
354
365
  expectedApplicationKind
355
366
  } = input;
367
+ if (profile === void 0 && policyOverride === void 0 && autoReconnect !== void 0) {
368
+ warnLegacyAutoReconnectOnce();
369
+ }
356
370
  let base;
357
371
  if (profile) {
358
372
  base = { ...PROFILE_PRESETS[profile] };
@@ -850,11 +864,67 @@ function AgentSession({
850
864
  }
851
865
 
852
866
  // src/view/workflow/WorkflowSession.tsx
853
- 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";
854
868
 
855
869
  // src/core/context/WorkflowSessionContext.tsx
856
870
  import { createContext, useContext } from "react";
857
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
858
928
  var WorkflowSessionStoreContext = createContext(null);
859
929
  function useWorkflowSessionStoreApi() {
860
930
  const store = useContext(WorkflowSessionStoreContext);
@@ -865,7 +935,7 @@ function useWorkflowSessionStoreApi() {
865
935
  }
866
936
  function useWorkflowSessionStatus() {
867
937
  const store = useWorkflowSessionStoreApi();
868
- return useStore(store, (s) => s.state.status);
938
+ return useStore(store, (s) => getDisplayProjectionStatus(s.state));
869
939
  }
870
940
 
871
941
  // src/core/context/WorkflowLoopReplayContext.tsx
@@ -930,6 +1000,17 @@ function containerTreeToSnapshot(tree) {
930
1000
  }
931
1001
 
932
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
+ }
933
1014
  function createEmptyWorkflowProgress() {
934
1015
  return {
935
1016
  workflowId: null,
@@ -941,9 +1022,8 @@ function createEmptyWorkflowProgress() {
941
1022
  }
942
1023
  function createEmptyWorkflowSessionState() {
943
1024
  return {
944
- status: "connecting",
945
- terminalKind: null,
946
- openChildIdentities: [],
1025
+ authority: null,
1026
+ display: createEmptyWorkflowDisplayProjection(),
947
1027
  meta: { workflowRunId: null, startedAt: null, lastEventId: null },
948
1028
  containerTree: createEmptyWorkflowContainerTreeState(),
949
1029
  workflowProgress: createEmptyWorkflowProgress(),
@@ -2136,7 +2216,10 @@ function readOpenChildIdentities(data) {
2136
2216
  if (!Array.isArray(raw)) return [];
2137
2217
  return raw.filter((item) => typeof item === "string");
2138
2218
  }
2139
- function isWorkflowSessionTerminal(status) {
2219
+ function projectionStatusFromTerminalKind(kind, isWorkflowFailedEvent) {
2220
+ return sessionStatusFromTerminalKind(kind, isWorkflowFailedEvent);
2221
+ }
2222
+ function isDisplayProjectionTerminal(status) {
2140
2223
  return status === "done" || status === "done_with_warning" || status === "error";
2141
2224
  }
2142
2225
 
@@ -2509,18 +2592,16 @@ function applyStructureEvent(state, event) {
2509
2592
  containerTree,
2510
2593
  state.workflowProgress
2511
2594
  );
2512
- let status = nextState.status;
2513
- if (workflowProgress.status === "done") status = "done";
2514
- else if (workflowProgress.status === "error") status = "error";
2515
- else if (status === "connecting") status = "running";
2516
- let terminalKind = state.terminalKind;
2517
- 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;
2518
2599
  if (projectedEvent.event_type === "workflow-end" || projectedEvent.event_type === "workflow-failed") {
2519
2600
  const resolved = resolveWorkflowTerminalKind(
2520
2601
  structureData,
2521
2602
  projectedEvent.event_type
2522
2603
  );
2523
- status = sessionStatusFromTerminalKind(
2604
+ projectionStatus = projectionStatusFromTerminalKind(
2524
2605
  resolved,
2525
2606
  projectedEvent.event_type === "workflow-failed"
2526
2607
  );
@@ -2529,9 +2610,12 @@ function applyStructureEvent(state, event) {
2529
2610
  }
2530
2611
  const base = {
2531
2612
  ...nextState,
2532
- status,
2533
- terminalKind,
2534
- openChildIdentities,
2613
+ display: {
2614
+ ...nextState.display,
2615
+ projectionStatus,
2616
+ terminalKind,
2617
+ openChildIdentities
2618
+ },
2535
2619
  containerTree,
2536
2620
  workflowProgress
2537
2621
  };
@@ -2599,11 +2683,9 @@ function reduceWorkflowSession(state, event, eventIndex, options) {
2599
2683
  next.containerTree,
2600
2684
  next.workflowProgress
2601
2685
  );
2602
- let status = next.status;
2603
- if (workflowProgress.status === "done") status = "done";
2604
- else if (workflowProgress.status === "error") status = "error";
2605
- else if (reducedLoop.status === "error") status = "error";
2606
- 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";
2607
2689
  return {
2608
2690
  ...next,
2609
2691
  workflowProgress,
@@ -2612,61 +2694,38 @@ function reduceWorkflowSession(state, event, eventIndex, options) {
2612
2694
  [loopSessionId]: reducedLoop
2613
2695
  },
2614
2696
  activeLoopSessionId: loopSessionId,
2615
- status
2697
+ display: {
2698
+ ...next.display,
2699
+ projectionStatus
2700
+ }
2616
2701
  };
2617
2702
  }
2618
- function finalizeWorkflowSessionOnStreamComplete(state) {
2619
- if (isWorkflowSessionTerminal(state.status)) {
2620
- return { ...state, activeLoopSessionId: null };
2621
- }
2622
- const tree = state.containerTree;
2623
- const rootId = tree.rootContainerIds[0];
2624
- if (!rootId) {
2625
- return { ...state, status: "done", activeLoopSessionId: null };
2626
- }
2627
- const root = tree.containersById[rootId];
2628
- if (!root) {
2629
- 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
+ };
2630
2710
  }
2631
- const topLevelStages = Object.values(tree.containersById).filter(
2632
- (node) => node.scope === "stage" && node.workflow_path === root.workflow_path && node.workflow_depth === root.workflow_depth
2633
- );
2634
- const progressTotal = root.display?.progress_total ?? (topLevelStages.reduce(
2635
- (max, s) => Math.max(max, s.display?.progress_total ?? 0),
2636
- 0
2637
- ) || topLevelStages.length);
2638
- const settledStageCount = topLevelStages.filter(
2639
- (s) => s.status === "completed" || s.status === "failed" || s.status === "skipped"
2640
- ).length;
2641
- const progressCurrent = Math.max(
2642
- root.display?.progress_current ?? 0,
2643
- settledStageCount
2644
- );
2645
- const rootParallelItems = Object.values(tree.containersById).filter(
2646
- (node) => node.scope === "item" && node.workflow_path === root.workflow_path && node.workflow_depth === root.workflow_depth
2647
- );
2648
- const rootParallelSettled = topLevelStages.length === 0 && rootParallelItems.length > 0 && rootParallelItems.every(
2649
- (item) => item.status === "completed" || item.status === "failed" || item.status === "skipped"
2650
- ) && !Object.values(tree.containersById).some(
2651
- (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")
2652
- );
2653
- const workflowVisuallyComplete = root.status === "completed" || root.status === "failed" || progressTotal > 0 && progressCurrent >= progressTotal || rootParallelSettled;
2654
- if (!workflowVisuallyComplete) {
2655
- 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);
2656
2718
  }
2719
+ return withStreamEnded;
2720
+ }
2721
+ function forceSettleRunningContainersForDisplay(state) {
2722
+ const tree = state.containerTree;
2657
2723
  let containersById = { ...tree.containersById };
2658
2724
  for (const [id, node] of Object.entries(containersById)) {
2659
2725
  if (node.status === "running" || node.status === "pending") {
2660
2726
  containersById[id] = { ...node, status: "completed", is_open: false };
2661
2727
  }
2662
2728
  }
2663
- const settledRoot = containersById[rootId] ?? root;
2664
- if (settledRoot.status !== "completed" && settledRoot.status !== "failed") {
2665
- containersById = {
2666
- ...containersById,
2667
- [rootId]: { ...settledRoot, status: "completed", is_open: false }
2668
- };
2669
- }
2670
2729
  const loopTreesBySessionId = { ...state.loopTreesBySessionId };
2671
2730
  for (const [loopSessionId, loopTree] of Object.entries(loopTreesBySessionId)) {
2672
2731
  if (loopTree.status === "running" || loopTree.status === "connecting") {
@@ -2680,15 +2739,9 @@ function finalizeWorkflowSessionOnStreamComplete(state) {
2680
2739
  (id) => containersById[id]?.is_open
2681
2740
  )
2682
2741
  };
2683
- const workflowProgress = syncWorkflowProgressFromContainerTree(
2684
- containerTree,
2685
- state.workflowProgress
2686
- );
2687
2742
  return {
2688
2743
  ...state,
2689
- status: workflowProgress.status === "error" ? "error" : "done",
2690
2744
  containerTree,
2691
- workflowProgress,
2692
2745
  loopTreesBySessionId,
2693
2746
  activeLoopSessionId: null
2694
2747
  };
@@ -2750,7 +2803,10 @@ function finalizeWorkflowSessionOnStreamError(state, message, eventIndex, stack)
2750
2803
  }
2751
2804
  return {
2752
2805
  ...state,
2753
- status: "error",
2806
+ display: {
2807
+ ...state.display,
2808
+ projectionStatus: "error"
2809
+ },
2754
2810
  containerTree: {
2755
2811
  ...state.containerTree,
2756
2812
  containersById,
@@ -2795,7 +2851,10 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
2795
2851
  state: {
2796
2852
  ...evicted,
2797
2853
  meta: nextMeta,
2798
- status: evicted.status === "connecting" ? "running" : evicted.status
2854
+ display: {
2855
+ ...evicted.display,
2856
+ projectionStatus: evicted.display.projectionStatus === "connecting" ? "running" : evicted.display.projectionStatus
2857
+ }
2799
2858
  },
2800
2859
  eventCount: eventCount + 1
2801
2860
  });
@@ -2825,13 +2884,59 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
2825
2884
  )
2826
2885
  });
2827
2886
  },
2828
- markStreamComplete() {
2887
+ markStreamEnded() {
2829
2888
  set((current) => ({
2830
2889
  state: finalizeState(
2831
- finalizeWorkflowSessionOnStreamComplete(current.state)
2890
+ finalizeWorkflowDisplayOnStreamEnded(current.state)
2832
2891
  )
2833
2892
  }));
2834
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
+ },
2835
2940
  /**
2836
2941
  * P2:将 completed loop 加入 LRU 热缓存并淘汰溢出。
2837
2942
  * running 保护由 collectProtectedLoopSessionIds 按状态自动覆盖,不再依赖永久 pin。
@@ -2938,11 +3043,93 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
2938
3043
  }));
2939
3044
  }
2940
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
+
2941
3128
  // src/view/workflow/WorkflowChrome.tsx
2942
3129
  import { useMemo as useMemo7, useState as useState6 } from "react";
2943
3130
 
2944
3131
  // src/view/workflow/WorkflowAggregateContainer.tsx
2945
- 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";
2946
3133
 
2947
3134
  // src/view/workflow/WorkflowContainerLine.tsx
2948
3135
  import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -3352,7 +3539,7 @@ function WorkflowAggregateContainerInner({
3352
3539
  const uiStatus = mapAggregateUiStatus(node);
3353
3540
  const statusLabel = buildAggregateStatusLabel(node, siblingItems);
3354
3541
  const [expanded, setExpanded] = useState(isRunning);
3355
- useEffect3(() => {
3542
+ useEffect5(() => {
3356
3543
  if (isRunning) setExpanded(true);
3357
3544
  if (isDone) setExpanded(false);
3358
3545
  }, [isRunning, isDone]);
@@ -3471,7 +3658,7 @@ var WorkflowAggregateContainer = memo(
3471
3658
  );
3472
3659
 
3473
3660
  // src/view/workflow/WorkflowParallelGroup.tsx
3474
- 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";
3475
3662
 
3476
3663
  // src/core/workflow/workflowParallelGroupMetrics.ts
3477
3664
  var ACTIVE_ITEM_STATUSES = /* @__PURE__ */ new Set([
@@ -3565,7 +3752,7 @@ function formatParallelGroupSummary(metrics) {
3565
3752
  }
3566
3753
 
3567
3754
  // src/view/workflow/WorkflowStageContainer.tsx
3568
- 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";
3569
3756
  import { useStore as useStore2 } from "zustand";
3570
3757
 
3571
3758
  // src/core/workflow/loopTreeUtils.ts
@@ -3652,7 +3839,7 @@ function WorkflowStageContainerInner({
3652
3839
  const doneSummary = doneSummaryFromTree ?? (isDone ? cachedSummary : null);
3653
3840
  const shouldAutoExpandRunning = effectivelyRunning && !isSkipped;
3654
3841
  const [expanded, setExpanded] = useState2(shouldAutoExpandRunning);
3655
- useEffect4(() => {
3842
+ useEffect6(() => {
3656
3843
  if (shouldAutoExpandRunning) {
3657
3844
  setExpanded(true);
3658
3845
  return;
@@ -3677,7 +3864,7 @@ function WorkflowStageContainerInner({
3677
3864
  return next;
3678
3865
  });
3679
3866
  }, [isDone, node.loopSessionId, loopTree, requestLoopHydration]);
3680
- useEffect4(() => {
3867
+ useEffect6(() => {
3681
3868
  const onKeyDown = (event) => {
3682
3869
  if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "o") return;
3683
3870
  setExpanded(true);
@@ -3830,7 +4017,7 @@ function WorkflowParallelGroupInner({
3830
4017
  );
3831
4018
  const allSettled = metrics.total > 0 && metrics.running === 0 && metrics.completed + metrics.failed >= metrics.total;
3832
4019
  const [expanded, setExpanded] = useState3(hasActiveWork);
3833
- useEffect5(() => {
4020
+ useEffect7(() => {
3834
4021
  if (hasActiveWork) {
3835
4022
  setExpanded(true);
3836
4023
  return;
@@ -3842,7 +4029,7 @@ function WorkflowParallelGroupInner({
3842
4029
  const toggleExpanded = useCallback3(() => {
3843
4030
  setExpanded((prev) => !prev);
3844
4031
  }, []);
3845
- useEffect5(() => {
4032
+ useEffect7(() => {
3846
4033
  const onKeyDown = (event) => {
3847
4034
  if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "o") return;
3848
4035
  setExpanded(true);
@@ -3942,7 +4129,7 @@ function WorkflowRootContainer({
3942
4129
  }
3943
4130
 
3944
4131
  // src/view/workflow/WorkflowRouteContainer.tsx
3945
- 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";
3946
4133
  import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
3947
4134
  function WorkflowRouteContainer({
3948
4135
  node,
@@ -3967,7 +4154,7 @@ function WorkflowRouteContainer({
3967
4154
  const expandHint = doneSummary ? formatTeaserExpandHint(doneSummary.extraLines, verbose) : null;
3968
4155
  const doneLabel = doneSummary?.label ?? "done";
3969
4156
  const [expanded, setExpanded] = useState4(isRunning);
3970
- useEffect6(() => {
4157
+ useEffect8(() => {
3971
4158
  if (isSkipped) return;
3972
4159
  if (isRunning) setExpanded(true);
3973
4160
  if (isDone) setExpanded(false);
@@ -4043,7 +4230,7 @@ function WorkflowRouteContainer({
4043
4230
  }
4044
4231
 
4045
4232
  // src/view/workflow/WorkflowSubworkflowContainer.tsx
4046
- 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";
4047
4234
  import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
4048
4235
  function buildDoneSummary(node) {
4049
4236
  const display = node.display;
@@ -4074,7 +4261,7 @@ function WorkflowSubworkflowContainer({
4074
4261
  return node.content_blocks[node.content_blocks.length - 1]?.preview ?? "";
4075
4262
  }, [node.content_blocks]);
4076
4263
  const [expanded, setExpanded] = useState5(effectivelyRunning);
4077
- useEffect7(() => {
4264
+ useEffect9(() => {
4078
4265
  if (effectivelyRunning) setExpanded(true);
4079
4266
  else if (effectivelyDone) setExpanded(false);
4080
4267
  }, [effectivelyRunning, effectivelyDone]);
@@ -4426,9 +4613,94 @@ function WorkflowChrome({
4426
4613
  )) });
4427
4614
  }
4428
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
+
4429
4701
  // src/view/workflow/WorkflowTaskListFooter.tsx
4430
4702
  import { useMemo as useMemo8, useState as useState7 } from "react";
4431
- import { useStore as useStore3 } from "zustand";
4703
+ import { useStore as useStore4 } from "zustand";
4432
4704
 
4433
4705
  // src/view/workflow/workflowTaskListMerge.ts
4434
4706
  var STATUS_ORDER = {
@@ -4469,7 +4741,7 @@ function deriveWorkflowTaskFooterState(state) {
4469
4741
  tasks: mergeWorkflowLoopTasks(state.loopTreesBySessionId),
4470
4742
  taskPhase: deriveWorkflowTaskPhase(trees),
4471
4743
  hasEverShownTaskList: trees.some((tree) => tree.hasEverShownTaskList),
4472
- sessionStatus: state.status
4744
+ sessionStatus: getDisplayProjectionStatus(state)
4473
4745
  };
4474
4746
  }
4475
4747
  function shouldShowWorkflowTaskListFooter(state) {
@@ -4499,16 +4771,16 @@ function loopSessionDisplayLabel(loopSessionId) {
4499
4771
  }
4500
4772
 
4501
4773
  // src/view/workflow/WorkflowTaskListFooter.tsx
4502
- import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
4774
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
4503
4775
  function MessageResponse({ children }) {
4504
- return /* @__PURE__ */ jsxs11("div", { className: "lax-message-response", children: [
4505
- /* @__PURE__ */ jsx16("span", { className: "lax-message-response__marker", children: "\u23BF " }),
4506
- /* @__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 })
4507
4779
  ] });
4508
4780
  }
4509
4781
  function WorkflowTaskListFooter() {
4510
4782
  const store = useWorkflowSessionStoreApi();
4511
- const state = useStore3(store, (s) => s.state);
4783
+ const state = useStore4(store, (s) => s.state);
4512
4784
  const [expanded, setExpanded] = useState7(false);
4513
4785
  const footerState = useMemo8(() => deriveWorkflowTaskFooterState(state), [state]);
4514
4786
  const visible = shouldShowWorkflowTaskListFooter(state);
@@ -4517,8 +4789,8 @@ function WorkflowTaskListFooter() {
4517
4789
  }
4518
4790
  const label = formatWorkflowTaskFooterLabel(state);
4519
4791
  const groups = groupWorkflowTasksByLoop(footerState.tasks);
4520
- return /* @__PURE__ */ jsxs11("div", { className: "lax-workflow-task-list-footer", "data-testid": "lax-workflow-task-list-footer", children: [
4521
- /* @__PURE__ */ jsxs11(
4792
+ return /* @__PURE__ */ jsxs13("div", { className: "lax-workflow-task-list-footer", "data-testid": "lax-workflow-task-list-footer", children: [
4793
+ /* @__PURE__ */ jsxs13(
4522
4794
  "button",
4523
4795
  {
4524
4796
  type: "button",
@@ -4528,18 +4800,18 @@ function WorkflowTaskListFooter() {
4528
4800
  onClick: () => setExpanded((v) => !v),
4529
4801
  children: [
4530
4802
  label,
4531
- /* @__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" })
4532
4804
  ]
4533
4805
  }
4534
4806
  ),
4535
- 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(
4536
4808
  "div",
4537
4809
  {
4538
4810
  className: "lax-workflow-task-list-group",
4539
4811
  "data-testid": `lax-workflow-task-group-${group.loopSessionId}`,
4540
4812
  children: [
4541
- /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
4542
- /* @__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 })
4543
4815
  ]
4544
4816
  },
4545
4817
  group.loopSessionId
@@ -4548,10 +4820,11 @@ function WorkflowTaskListFooter() {
4548
4820
  }
4549
4821
 
4550
4822
  // src/view/workflow/WorkflowSession.tsx
4551
- import { useStore as useStore4 } from "zustand";
4552
- 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";
4553
4825
  function WorkflowSession({
4554
4826
  source,
4827
+ authoritySource,
4555
4828
  initialEvents,
4556
4829
  registry,
4557
4830
  tierOverrides,
@@ -4571,10 +4844,11 @@ function WorkflowSession({
4571
4844
  groupParallelTools = false,
4572
4845
  workflowScale,
4573
4846
  onRequestLoopReplay,
4574
- onStreamComplete,
4847
+ onAuthorityTerminal,
4848
+ onDisplayStreamEnded,
4575
4849
  children
4576
4850
  }) {
4577
- const storeRef = useRef3(null);
4851
+ const storeRef = useRef4(null);
4578
4852
  if (storeRef.current === null) {
4579
4853
  const initialState = initialEvents && initialEvents.length > 0 ? reduceWorkflowEvents(initialEvents, { tierOverrides }) : void 0;
4580
4854
  storeRef.current = createWorkflowSessionStore(initialState, {
@@ -4584,6 +4858,11 @@ function WorkflowSession({
4584
4858
  maxHydratedCompletedLoops: workflowScale?.maxHydratedCompletedLoops ?? DEFAULT_WORKFLOW_SCALE_OPTIONS.maxHydratedCompletedLoops
4585
4859
  });
4586
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);
4587
4866
  const scaleOptions = useMemo9(
4588
4867
  () => ({ ...DEFAULT_WORKFLOW_SCALE_OPTIONS, ...workflowScale }),
4589
4868
  [workflowScale]
@@ -4621,38 +4900,20 @@ function WorkflowSession({
4621
4900
  workspaceRoot
4622
4901
  ]
4623
4902
  );
4624
- const containerTree = useStore4(
4625
- storeRef.current,
4626
- (s) => s.state.containerTree
4627
- );
4628
- const loopTreesBySessionId = useStore4(
4629
- storeRef.current,
4630
- (s) => s.state.loopTreesBySessionId
4631
- );
4632
- const openChildIdentities = useStore4(
4633
- storeRef.current,
4634
- (s) => s.state.openChildIdentities
4635
- );
4636
- const sessionStatus = useStore4(
4637
- storeRef.current,
4638
- (s) => s.state.status
4639
- );
4640
- const internalErrors = useStore4(
4641
- storeRef.current,
4642
- (s) => s.state.internalErrors
4643
- );
4644
- const onErrorRef = useRef3(onError);
4645
- 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);
4646
4908
  onErrorRef.current = onError;
4647
- onStreamCompleteRef.current = onStreamComplete;
4648
- useEffect8(() => {
4649
- const store = storeRef.current;
4909
+ onDisplayStreamEndedRef.current = onDisplayStreamEnded;
4910
+ useEffect10(() => {
4650
4911
  const controller = new AbortController();
4651
4912
  void source.start((event, ctx) => {
4652
4913
  store.getState().applyEvent(event, ctx);
4653
4914
  }, controller.signal).then(() => {
4654
- store.getState().markStreamComplete();
4655
- onStreamCompleteRef.current?.();
4915
+ store.getState().markStreamEnded();
4916
+ onDisplayStreamEndedRef.current?.();
4656
4917
  }).catch((err) => {
4657
4918
  if (err instanceof SseTransportTerminalError) {
4658
4919
  store.getState().markAsError(err);
@@ -4664,39 +4925,16 @@ function WorkflowSession({
4664
4925
  return () => {
4665
4926
  controller.abort();
4666
4927
  };
4667
- }, [source]);
4668
- 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(
4669
4930
  "div",
4670
4931
  {
4671
4932
  className: "lax-agent-session lax-workflow-session",
4672
4933
  "data-testid": "lax-workflow-session",
4673
4934
  children: [
4674
- sessionStatus === "done_with_warning" ? /* @__PURE__ */ jsxs12(
4675
- "div",
4676
- {
4677
- className: "lax-workflow-closeout-warning",
4678
- "data-testid": "lax-workflow-closeout-warning",
4679
- role: "status",
4680
- children: [
4681
- /* @__PURE__ */ jsx17("strong", { children: "Workflow forced closeout" }),
4682
- /* @__PURE__ */ jsxs12("span", { children: [
4683
- " ",
4684
- "\u2014 stream ended with unresolved child scopes; not a healthy completion."
4685
- ] }),
4686
- openChildIdentities.length > 0 ? /* @__PURE__ */ jsx17("ul", { className: "lax-workflow-closeout-warning__scopes", children: openChildIdentities.map((identity) => /* @__PURE__ */ jsx17("li", { children: identity }, identity)) }) : null
4687
- ]
4688
- }
4689
- ) : null,
4690
- sessionStatus === "error" || internalErrors.length > 0 ? /* @__PURE__ */ jsx17(
4691
- "div",
4692
- {
4693
- className: "lax-workflow-error",
4694
- "data-testid": "lax-workflow-error",
4695
- role: "alert",
4696
- children: internalErrors[internalErrors.length - 1]?.message ?? "Workflow stream error"
4697
- }
4698
- ) : null,
4699
- /* @__PURE__ */ jsx17(
4935
+ /* @__PURE__ */ jsx19(WorkflowSessionBanners, { state: sessionState }),
4936
+ /* @__PURE__ */ jsx19(WorkflowGlobalSpinner, {}),
4937
+ /* @__PURE__ */ jsx19(
4700
4938
  WorkflowChrome,
4701
4939
  {
4702
4940
  containerTree,
@@ -4706,13 +4944,158 @@ function WorkflowSession({
4706
4944
  groupParallelTools
4707
4945
  }
4708
4946
  ),
4709
- /* @__PURE__ */ jsx17(WorkflowTaskListFooter, {}),
4947
+ /* @__PURE__ */ jsx19(WorkflowTaskListFooter, {}),
4710
4948
  children
4711
4949
  ]
4712
4950
  }
4713
4951
  ) }) }) }) }) }) }) }) });
4714
4952
  }
4715
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
+
4716
5099
  // src/view/tools/groupParallelTools.ts
4717
5100
  function buildTimelineEntries(rootIds, byId, options = {}) {
4718
5101
  const filtered = options.hideStandalonePermissions ? rootIds.filter((id) => byId[id]?.kind !== "permission") : [...rootIds];
@@ -4750,18 +5133,18 @@ function buildTimelineEntries(rootIds, byId, options = {}) {
4750
5133
  }
4751
5134
 
4752
5135
  // src/view/nodes/SubAgentNode.tsx
4753
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
5136
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
4754
5137
  function SubAgentBlock({ nodeId }) {
4755
5138
  const node = useNodeTyped(nodeId, "subagent");
4756
5139
  const childIds = useChildren(nodeId);
4757
5140
  const registry = useNodeRegistry();
4758
5141
  if (!node) return null;
4759
- return /* @__PURE__ */ jsxs13("div", { className: "lax-subagent-block", "data-status": node.status, children: [
4760
- /* @__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: [
4761
5144
  "SubAgent: ",
4762
5145
  node.subagentId
4763
5146
  ] }),
4764
- /* @__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)) })
4765
5148
  ] });
4766
5149
  }
4767
5150
 
@@ -4880,6 +5263,7 @@ export {
4880
5263
  WorkflowTaskListFooter,
4881
5264
  Write,
4882
5265
  WriteToolBody,
5266
+ applyAuthoritySnapshotToState,
4883
5267
  buildBashBodyBlocks,
4884
5268
  buildCanonicalFromTree,
4885
5269
  buildProjectionContext,
@@ -4895,21 +5279,27 @@ export {
4895
5279
  configurePlantumlServer,
4896
5280
  createActiveSessionBridge,
4897
5281
  createAgentxSseSource,
5282
+ createControllableAuthoritySource,
4898
5283
  createDefaultRegistry,
4899
5284
  createDefaultToolRegistry,
5285
+ createDoneAuthoritySource,
4900
5286
  createEmptyTree,
4901
5287
  createEmptyWorkflowSessionState,
4902
5288
  createInteractionBus,
5289
+ createMockAuthoritySource,
4903
5290
  createMockSource,
4904
5291
  createMultiSessionStore,
5292
+ createPollingAuthoritySource,
4905
5293
  createProjectionContext,
4906
5294
  createReplaySource,
5295
+ createRunningAuthoritySource,
4907
5296
  createSessionStore,
4908
5297
  createWorkflowSessionStore,
4909
5298
  deriveLoopSessionIdFromAgentEvent,
4910
5299
  displayPath,
4911
5300
  entryKey,
4912
5301
  filterAgentLoopReplayEvents,
5302
+ finalizeWorkflowDisplayOnStreamEnded,
4913
5303
  fixDiagramSvg,
4914
5304
  formatAgentTitle,
4915
5305
  formatDiffFromStrings,
@@ -4922,9 +5312,12 @@ export {
4922
5312
  formatTimeoutFooter,
4923
5313
  formatToolTitle,
4924
5314
  getChildIds,
5315
+ getDisplayProjectionStatus,
4925
5316
  getMainStageIds,
4926
5317
  getNoopInteractionBus,
4927
5318
  isAgentLoopReplayEventType,
5319
+ isAuthorityTerminal,
5320
+ isAuthorityTerminalStatus,
4928
5321
  isGitOperationCommand,
4929
5322
  isGroupableToolName,
4930
5323
  isMcpToolName,
@@ -4948,8 +5341,10 @@ export {
4948
5341
  resolveShellProgressFromPayload,
4949
5342
  resolveSseTransportPolicy,
4950
5343
  resolveToolBody,
5344
+ resolveWorkflowBanner,
4951
5345
  shouldApplyGrouping,
4952
5346
  shouldShowExploreDetail,
5347
+ shouldShowGlobalSpinner,
4953
5348
  shouldShowTaskListFooter,
4954
5349
  streamChunk,
4955
5350
  truncateCommand,