langchain_agentx_stream_ui 0.1.0 → 0.1.1

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
@@ -1,19 +1,25 @@
1
1
  import {
2
2
  ErrorCard,
3
+ MermaidDiagram,
3
4
  MultiAgentSession,
4
5
  NodeRegistry,
5
6
  PermissionCard,
7
+ PlantUMLBlock,
8
+ PlantUMLDiagram,
6
9
  ReasoningBubble,
10
+ RichMarkdown,
7
11
  StepCard,
8
12
  TextBubble,
9
13
  ToolCallCard,
10
14
  UnknownCard,
15
+ configureMermaid,
16
+ configurePlantumlServer,
11
17
  createActiveSessionBridge,
12
18
  createDefaultRegistry,
13
19
  createMultiSessionStore,
14
20
  markEventIdSeen,
15
21
  shouldSkipDuplicateEvent
16
- } from "./chunk-W6QQHTJ4.js";
22
+ } from "./chunk-R35BQYAY.js";
17
23
  import {
18
24
  DefaultToolBody,
19
25
  InteractionBusContext,
@@ -74,6 +80,7 @@ import {
74
80
  iterMountCandidates,
75
81
  projectCanonical,
76
82
  projectTimeline,
83
+ reduceAgentLoopEvent,
77
84
  reduceEvents,
78
85
  reduceTree,
79
86
  replayEvents,
@@ -98,8 +105,9 @@ import {
98
105
  useSessionIds,
99
106
  useSessionMeta,
100
107
  useSessionStatus,
108
+ useSessionViewOptions,
101
109
  useTimeline
102
- } from "./chunk-G2KYJCMM.js";
110
+ } from "./chunk-Q4HRD7EM.js";
103
111
  import {
104
112
  Agent,
105
113
  AgentToolBody,
@@ -145,7 +153,7 @@ import {
145
153
  } from "./chunk-4RIOBLGB.js";
146
154
 
147
155
  // src/view/AgentSession.tsx
148
- import { useEffect, useMemo, useRef } from "react";
156
+ import { useEffect as useEffect2, useMemo, useRef as useRef2 } from "react";
149
157
 
150
158
  // src/core/store.ts
151
159
  import { createStore } from "zustand/vanilla";
@@ -225,8 +233,307 @@ function createSessionStore(initialTree = createEmptyTree(), storeOptions) {
225
233
  }));
226
234
  }
227
235
 
236
+ // src/transport/applicationKind.ts
237
+ var ApplicationKindMismatchError = class extends Error {
238
+ name = "ApplicationKindMismatchError";
239
+ expected;
240
+ actual;
241
+ constructor(expected, actual) {
242
+ super(
243
+ `application_kind mismatch: expected ${expected}, got ${actual ?? "(missing)"}`
244
+ );
245
+ this.expected = expected;
246
+ this.actual = actual;
247
+ }
248
+ };
249
+ var MissingApplicationKindError = class extends Error {
250
+ name = "MissingApplicationKindError";
251
+ constructor() {
252
+ super("application_kind missing in SSE meta frame");
253
+ }
254
+ };
255
+
256
+ // src/transport/createAgentxSseSource.ts
257
+ var EXPECTED_PROTOCOL_VERSION = "1";
258
+ var SseTransportTerminalError = class extends Error {
259
+ kind;
260
+ constructor(kind, message) {
261
+ super(message);
262
+ this.name = "SseTransportTerminalError";
263
+ this.kind = kind;
264
+ }
265
+ };
266
+ function transportLog(message, level = "warn") {
267
+ const line = `[langchain_agentx_stream_ui] SSE transport: ${message}`;
268
+ if (level === "error") {
269
+ console.error(line);
270
+ } else {
271
+ console.warn(line);
272
+ }
273
+ }
274
+ function buildResumeStreamUrl(baseUrl, lastEventId, queryParam = "last_event_id") {
275
+ if (!lastEventId) return baseUrl;
276
+ const sep = baseUrl.includes("?") ? "&" : "?";
277
+ return `${baseUrl}${sep}${encodeURIComponent(queryParam)}=${encodeURIComponent(lastEventId)}`;
278
+ }
279
+ function createAgentxSseSource(options) {
280
+ const {
281
+ url,
282
+ autoReconnect = true,
283
+ reconnectDelayMs = 1e3,
284
+ onLastEventId,
285
+ EventSourceImpl = EventSource,
286
+ expectedApplicationKind
287
+ } = options;
288
+ return {
289
+ start(handler, signal) {
290
+ return new Promise((resolve, reject) => {
291
+ let es = null;
292
+ let handshakeOk = false;
293
+ let lastEventId = null;
294
+ let fatal = false;
295
+ let streamTerminal = false;
296
+ let bufferedDelta = null;
297
+ let flushTimer = null;
298
+ const flushBufferedDelta = () => {
299
+ if (flushTimer) {
300
+ clearTimeout(flushTimer);
301
+ flushTimer = null;
302
+ }
303
+ if (!bufferedDelta) return;
304
+ handler(bufferedDelta.event, bufferedDelta.ctx);
305
+ bufferedDelta = null;
306
+ };
307
+ const isBufferedDeltaEvent = (event) => event.event_type === "text-delta" || event.event_type === "reasoning-delta";
308
+ const mergeDeltaEvent = (prev, next) => {
309
+ if (prev.event_type !== next.event_type) return null;
310
+ if (prev.step_index !== next.step_index) return null;
311
+ if (prev.tool_name !== next.tool_name) return null;
312
+ if (prev.session_id !== next.session_id) return null;
313
+ const prevData = prev.data;
314
+ const nextData = next.data;
315
+ const prevChunk = prevData.delta ?? prevData.text ?? prevData.content ?? "";
316
+ const nextChunk = nextData.delta ?? nextData.text ?? nextData.content ?? "";
317
+ return {
318
+ ...next,
319
+ data: {
320
+ ...next.data,
321
+ text: `${prevChunk}${nextChunk}`
322
+ }
323
+ };
324
+ };
325
+ const cleanup = () => {
326
+ es?.close();
327
+ es = null;
328
+ };
329
+ const finish = (error) => {
330
+ flushBufferedDelta();
331
+ cleanup();
332
+ if (error) reject(error);
333
+ else resolve();
334
+ };
335
+ const failHandshake = (error) => {
336
+ fatal = true;
337
+ transportLog(error.message, "error");
338
+ cleanup();
339
+ finish(error);
340
+ };
341
+ const failPrematureClose = () => {
342
+ const error = new SseTransportTerminalError(
343
+ "premature_close",
344
+ "SSE connection lost before stream finished"
345
+ );
346
+ transportLog(error.message, "error");
347
+ cleanup();
348
+ finish(error);
349
+ };
350
+ const connect = (resumeFromId) => {
351
+ if (signal.aborted || fatal) {
352
+ finish();
353
+ return;
354
+ }
355
+ const connectUrl = resumeFromId != null && resumeFromId !== "" ? buildResumeStreamUrl(url, resumeFromId) : url;
356
+ es = new EventSourceImpl(connectUrl);
357
+ handshakeOk = false;
358
+ es.addEventListener("meta", (event) => {
359
+ try {
360
+ const meta = JSON.parse(event.data);
361
+ if (meta.protocol_version !== EXPECTED_PROTOCOL_VERSION) {
362
+ failHandshake(
363
+ new Error(`Unsupported protocol_version: ${meta.protocol_version}`)
364
+ );
365
+ return;
366
+ }
367
+ if (expectedApplicationKind) {
368
+ const actual = meta.application_kind;
369
+ if (actual === void 0 || actual === null) {
370
+ failHandshake(new MissingApplicationKindError());
371
+ return;
372
+ }
373
+ if (actual !== expectedApplicationKind) {
374
+ failHandshake(
375
+ new ApplicationKindMismatchError(expectedApplicationKind, actual)
376
+ );
377
+ return;
378
+ }
379
+ }
380
+ handshakeOk = true;
381
+ } catch (err) {
382
+ const message = err instanceof Error ? err.message : "Failed to decode meta frame";
383
+ failHandshake(new Error(message));
384
+ }
385
+ });
386
+ es.addEventListener("agentx", (event) => {
387
+ if (!handshakeOk) {
388
+ failHandshake(new Error("Received agentx frame before meta handshake"));
389
+ return;
390
+ }
391
+ try {
392
+ const agentEvent = JSON.parse(event.data);
393
+ const sseEventId = event.lastEventId || void 0;
394
+ if (sseEventId) {
395
+ lastEventId = sseEventId;
396
+ onLastEventId?.(sseEventId);
397
+ }
398
+ const ctx = sseEventId ? { sseEventId } : void 0;
399
+ if (isBufferedDeltaEvent(agentEvent)) {
400
+ if (bufferedDelta) {
401
+ const merged = mergeDeltaEvent(bufferedDelta.event, agentEvent);
402
+ if (merged) {
403
+ bufferedDelta = { event: merged, ctx };
404
+ } else {
405
+ flushBufferedDelta();
406
+ bufferedDelta = { event: agentEvent, ctx };
407
+ }
408
+ } else {
409
+ bufferedDelta = { event: agentEvent, ctx };
410
+ }
411
+ if (!flushTimer) {
412
+ flushTimer = setTimeout(() => {
413
+ flushBufferedDelta();
414
+ }, 16);
415
+ }
416
+ } else {
417
+ flushBufferedDelta();
418
+ handler(agentEvent, ctx);
419
+ if (agentEvent.event_type === "finish" || agentEvent.event_type === "error") {
420
+ streamTerminal = true;
421
+ }
422
+ }
423
+ } catch (err) {
424
+ const message = err instanceof Error ? err.message : "Failed to decode agentx frame";
425
+ transportLog(`Dropped agentx frame: ${message}`, "warn");
426
+ }
427
+ });
428
+ es.onerror = () => {
429
+ flushBufferedDelta();
430
+ if (signal.aborted || fatal || streamTerminal) {
431
+ cleanup();
432
+ finish();
433
+ return;
434
+ }
435
+ if (!handshakeOk) {
436
+ const error = new SseTransportTerminalError(
437
+ "handshake_failed",
438
+ "SSE connection failed before meta handshake"
439
+ );
440
+ transportLog(error.message, "error");
441
+ cleanup();
442
+ finish(error);
443
+ return;
444
+ }
445
+ if (autoReconnect) {
446
+ cleanup();
447
+ setTimeout(() => connect(lastEventId), reconnectDelayMs);
448
+ return;
449
+ }
450
+ failPrematureClose();
451
+ };
452
+ };
453
+ signal.addEventListener("abort", () => {
454
+ flushBufferedDelta();
455
+ fatal = true;
456
+ finish();
457
+ }, { once: true });
458
+ connect();
459
+ });
460
+ }
461
+ };
462
+ }
463
+ function createMockSource(events) {
464
+ return {
465
+ async start(handler, signal) {
466
+ for (const event of events) {
467
+ if (signal.aborted) break;
468
+ handler(event);
469
+ }
470
+ }
471
+ };
472
+ }
473
+
474
+ // src/view/agentLoop/ScopedSessionStore.tsx
475
+ import { useEffect, useRef } from "react";
476
+ import { jsx } from "react/jsx-runtime";
477
+ function ScopedSessionStoreProvider({
478
+ tree,
479
+ children
480
+ }) {
481
+ const storeRef = useRef(createSessionStore(tree, { initialEventCount: 1 }));
482
+ useEffect(() => {
483
+ storeRef.current.setState({ tree });
484
+ }, [tree]);
485
+ return /* @__PURE__ */ jsx(SessionStoreContext.Provider, { value: storeRef.current, children });
486
+ }
487
+
488
+ // src/view/agentLoop/AgentLoopView.tsx
489
+ import { jsx as jsx2 } from "react/jsx-runtime";
490
+ function AgentLoopViewBody({
491
+ showTaskListFooter = true,
492
+ virtualized,
493
+ virtualizeThreshold,
494
+ groupParallelTools
495
+ }) {
496
+ return /* @__PURE__ */ jsx2("div", { className: "lax-agent-loop-view", "data-testid": "lax-agent-loop-view", children: /* @__PURE__ */ jsx2(
497
+ SessionTimeline,
498
+ {
499
+ virtualized,
500
+ virtualizeThreshold,
501
+ groupParallelTools,
502
+ showTaskListFooter
503
+ }
504
+ ) });
505
+ }
506
+ function AgentLoopView({
507
+ tree,
508
+ showTaskListFooter = true,
509
+ virtualized,
510
+ virtualizeThreshold,
511
+ groupParallelTools
512
+ }) {
513
+ if (tree) {
514
+ return /* @__PURE__ */ jsx2(ScopedSessionStoreProvider, { tree, children: /* @__PURE__ */ jsx2(
515
+ AgentLoopViewBody,
516
+ {
517
+ showTaskListFooter,
518
+ virtualized,
519
+ virtualizeThreshold,
520
+ groupParallelTools
521
+ }
522
+ ) });
523
+ }
524
+ return /* @__PURE__ */ jsx2(
525
+ AgentLoopViewBody,
526
+ {
527
+ showTaskListFooter,
528
+ virtualized,
529
+ virtualizeThreshold,
530
+ groupParallelTools
531
+ }
532
+ );
533
+ }
534
+
228
535
  // src/view/AgentSession.tsx
229
- import { jsx, jsxs } from "react/jsx-runtime";
536
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
230
537
  function DebugPanel() {
231
538
  const status = useSessionStatus();
232
539
  const errors = useInternalErrors();
@@ -236,7 +543,7 @@ function DebugPanel() {
236
543
  "status: ",
237
544
  status
238
545
  ] }),
239
- /* @__PURE__ */ jsx("ul", { children: errors.map((err, i) => /* @__PURE__ */ jsxs("li", { children: [
546
+ /* @__PURE__ */ jsx3("ul", { children: errors.map((err, i) => /* @__PURE__ */ jsxs("li", { children: [
240
547
  "[",
241
548
  err.eventType,
242
549
  "] ",
@@ -244,20 +551,2690 @@ function DebugPanel() {
244
551
  ] }, `${err.eventIndex}-${i}`)) })
245
552
  ] });
246
553
  }
247
- function AgentSession({
554
+ function AgentSession({
555
+ source,
556
+ initialEvents,
557
+ registry,
558
+ tierOverrides: _tierOverrides,
559
+ onError,
560
+ autoReconnect: _autoReconnect,
561
+ debug = false,
562
+ virtualized = false,
563
+ virtualizeThreshold,
564
+ interactionBus,
565
+ toolDisplayRegistry,
566
+ defaultBodyMode = "preview",
567
+ groupParallelTools = false,
568
+ permissionUiMode = "standalone",
569
+ markdownRenderer,
570
+ displayMode = "normal",
571
+ verbose = false,
572
+ exploreFullscreenBash = true,
573
+ memoryDir = null,
574
+ workspaceRoot = null,
575
+ children
576
+ }) {
577
+ const storeRef = useRef2(null);
578
+ if (storeRef.current === null) {
579
+ const replayOpts = {
580
+ tierOverrides: _tierOverrides,
581
+ collectDebug: debug
582
+ };
583
+ const initialTree = initialEvents && initialEvents.length > 0 ? replayEvents(initialEvents, replayOpts) : void 0;
584
+ storeRef.current = createSessionStore(initialTree, {
585
+ tierOverrides: _tierOverrides,
586
+ debug,
587
+ initialEventCount: initialEvents?.length ?? 0
588
+ });
589
+ }
590
+ const registryRef = useMemo(() => registry ?? createDefaultRegistry(), [registry]);
591
+ const busRef = useMemo(
592
+ () => interactionBus ?? getNoopInteractionBus(),
593
+ [interactionBus]
594
+ );
595
+ const toolDisplayRef = useMemo(
596
+ () => ({
597
+ registry: toolDisplayRegistry ?? createDefaultToolRegistry(),
598
+ defaultBodyMode
599
+ }),
600
+ [toolDisplayRegistry, defaultBodyMode]
601
+ );
602
+ const sessionViewRef = useMemo(
603
+ () => ({
604
+ ...DEFAULT_SESSION_VIEW_OPTIONS,
605
+ groupParallelTools,
606
+ permissionUiMode,
607
+ verboseReasoning: debug,
608
+ displayMode,
609
+ verbose,
610
+ exploreFullscreenBash,
611
+ memoryDir,
612
+ workspaceRoot
613
+ }),
614
+ [
615
+ groupParallelTools,
616
+ permissionUiMode,
617
+ debug,
618
+ displayMode,
619
+ verbose,
620
+ exploreFullscreenBash,
621
+ memoryDir,
622
+ workspaceRoot
623
+ ]
624
+ );
625
+ useEffect2(() => {
626
+ const store = storeRef.current;
627
+ const controller = new AbortController();
628
+ void source.start((event, ctx) => {
629
+ store.getState().applyEvent(event, ctx);
630
+ }, controller.signal).catch((err) => {
631
+ if (err instanceof SseTransportTerminalError) {
632
+ const { tree } = store.getState();
633
+ if (tree.status === "running" || tree.status === "connecting") {
634
+ store.setState({ tree: { ...tree, status: "error" } });
635
+ }
636
+ }
637
+ });
638
+ return () => {
639
+ controller.abort();
640
+ };
641
+ }, [source, onError]);
642
+ return /* @__PURE__ */ jsx3(SessionStoreContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx3(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx3(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx3(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx3(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx3(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs("div", { className: "lax-agent-session", "data-testid": "lax-agent-session", children: [
643
+ children ?? /* @__PURE__ */ jsx3(
644
+ AgentLoopView,
645
+ {
646
+ virtualized,
647
+ virtualizeThreshold,
648
+ groupParallelTools
649
+ }
650
+ ),
651
+ debug ? /* @__PURE__ */ jsx3(DebugPanel, {}) : null
652
+ ] }) }) }) }) }) }) });
653
+ }
654
+
655
+ // src/view/workflow/WorkflowSession.tsx
656
+ import { useEffect as useEffect8, useMemo as useMemo9, useRef as useRef4 } from "react";
657
+
658
+ // src/core/context/WorkflowSessionContext.tsx
659
+ import { createContext, useContext } from "react";
660
+ import { useStore } from "zustand";
661
+ var WorkflowSessionStoreContext = createContext(null);
662
+ function useWorkflowSessionStoreApi() {
663
+ const store = useContext(WorkflowSessionStoreContext);
664
+ if (!store) {
665
+ throw new Error("useWorkflowSessionStoreApi must be used within WorkflowSession");
666
+ }
667
+ return store;
668
+ }
669
+ function useWorkflowSessionStatus() {
670
+ const store = useWorkflowSessionStoreApi();
671
+ return useStore(store, (s) => s.state.status);
672
+ }
673
+
674
+ // src/core/context/WorkflowLoopReplayContext.tsx
675
+ import { createContext as createContext2, useContext as useContext2 } from "react";
676
+ import { jsx as jsx4 } from "react/jsx-runtime";
677
+ var WorkflowLoopReplayContext = createContext2(
678
+ void 0
679
+ );
680
+ function WorkflowLoopReplayProvider({
681
+ onRequestLoopReplay,
682
+ children
683
+ }) {
684
+ return /* @__PURE__ */ jsx4(WorkflowLoopReplayContext.Provider, { value: onRequestLoopReplay, children });
685
+ }
686
+ function useWorkflowLoopReplayHandler() {
687
+ return useContext2(WorkflowLoopReplayContext);
688
+ }
689
+
690
+ // src/core/context/WorkflowScaleContext.tsx
691
+ import { createContext as createContext3, useContext as useContext3 } from "react";
692
+
693
+ // src/view/workflow/workflowScaleOptions.ts
694
+ var DEFAULT_WORKFLOW_SCALE_OPTIONS = {
695
+ stageVirtualizeThreshold: 40,
696
+ stagePageSize: 25,
697
+ evictCompletedLoops: true,
698
+ onlyExpandActiveRunning: true,
699
+ parallelItemVirtualizeThreshold: 40
700
+ };
701
+
702
+ // src/core/context/WorkflowScaleContext.tsx
703
+ var WorkflowScaleContext = createContext3(
704
+ DEFAULT_WORKFLOW_SCALE_OPTIONS
705
+ );
706
+ function useWorkflowScaleOptions() {
707
+ return useContext3(WorkflowScaleContext);
708
+ }
709
+
710
+ // src/core/workflowSessionStore.ts
711
+ import { createStore as createStore2 } from "zustand/vanilla";
712
+
713
+ // src/types/workflowContainer.ts
714
+ function createEmptyWorkflowContainerTreeState() {
715
+ return {
716
+ rootContainerIds: [],
717
+ activeContainerIds: [],
718
+ containersById: {}
719
+ };
720
+ }
721
+ function containerTreeToSnapshot(tree) {
722
+ const containers = {};
723
+ for (const [id, node] of Object.entries(tree.containersById)) {
724
+ const { loopSessionId: _loop, ...projection } = node;
725
+ containers[id] = projection;
726
+ }
727
+ return {
728
+ root_container_ids: [...tree.rootContainerIds],
729
+ active_container_ids: [...tree.activeContainerIds],
730
+ containers
731
+ };
732
+ }
733
+
734
+ // src/types/workflowSession.ts
735
+ function createEmptyWorkflowProgress() {
736
+ return {
737
+ workflowId: null,
738
+ workflowPath: null,
739
+ status: "running",
740
+ stages: [],
741
+ parallelItems: {}
742
+ };
743
+ }
744
+ function createEmptyWorkflowSessionState() {
745
+ return {
746
+ status: "connecting",
747
+ meta: { workflowRunId: null, startedAt: null, lastEventId: null },
748
+ containerTree: createEmptyWorkflowContainerTreeState(),
749
+ workflowProgress: createEmptyWorkflowProgress(),
750
+ loopTreesBySessionId: {},
751
+ stageDoneSummariesByContainerId: {},
752
+ activeLoopSessionId: null,
753
+ openScopeStack: [],
754
+ internalErrors: []
755
+ };
756
+ }
757
+
758
+ // src/core/workflow/workflowLoopTreeUtils.ts
759
+ function createWorkflowRunningLoopTree(seedMs = Date.now()) {
760
+ const tree = createEmptyTree();
761
+ tree.status = "running";
762
+ tree.meta.startedAt = seedMs;
763
+ return tree;
764
+ }
765
+
766
+ // src/types/workflowDisplay.ts
767
+ function readDisplayPayload(data) {
768
+ const display = data.display;
769
+ if (display != null && typeof display === "object" && !Array.isArray(display)) {
770
+ return display;
771
+ }
772
+ return null;
773
+ }
774
+
775
+ // src/core/workflow/workflowContainerProjector.ts
776
+ var OPEN_EVENT_TYPES = /* @__PURE__ */ new Set([
777
+ "workflow-start",
778
+ "subworkflow-start",
779
+ "stage-start",
780
+ "parallel-item-start",
781
+ "parallel-aggregate-start",
782
+ "route-branch-start"
783
+ ]);
784
+ var CLOSE_EVENT_TYPES = /* @__PURE__ */ new Set([
785
+ "workflow-end",
786
+ "workflow-failed",
787
+ "subworkflow-end",
788
+ "stage-done",
789
+ "stage-failed",
790
+ "parallel-item-done",
791
+ "parallel-item-failed",
792
+ "parallel-aggregate-done",
793
+ "parallel-aggregate-failed",
794
+ "route-branch-done",
795
+ "route-branch-failed"
796
+ ]);
797
+ var EVENT_TYPE_TO_SCOPE = {
798
+ "workflow-start": "workflow",
799
+ "workflow-end": "workflow",
800
+ "workflow-failed": "workflow",
801
+ "subworkflow-start": "subworkflow",
802
+ "subworkflow-end": "subworkflow",
803
+ "stage-start": "stage",
804
+ "stage-done": "stage",
805
+ "stage-failed": "stage",
806
+ "parallel-item-start": "item",
807
+ "parallel-item-done": "item",
808
+ "parallel-item-failed": "item",
809
+ "parallel-aggregate-start": "aggregate",
810
+ "parallel-aggregate-done": "aggregate",
811
+ "parallel-aggregate-failed": "aggregate",
812
+ "route-branch-start": "branch",
813
+ "route-branch-done": "branch",
814
+ "route-branch-failed": "branch"
815
+ };
816
+ var CONTENT_EVENT_TYPES = /* @__PURE__ */ new Set([
817
+ "reasoning-start",
818
+ "reasoning-delta",
819
+ "reasoning-end",
820
+ "text-start",
821
+ "text-delta",
822
+ "text-end",
823
+ "tool-input",
824
+ "tool-input-start",
825
+ "tool-input-delta",
826
+ "tool-call",
827
+ "tool-result",
828
+ "tool-error",
829
+ "tool-progress"
830
+ ]);
831
+ var SCOPE_PRIORITY = {
832
+ workflow: 0,
833
+ subworkflow: 1,
834
+ aggregate: 2,
835
+ stage: 3,
836
+ item: 3,
837
+ branch: 3
838
+ };
839
+ var FAILED_CLOSE_TYPES = /* @__PURE__ */ new Set([
840
+ "workflow-failed",
841
+ "stage-failed",
842
+ "parallel-item-failed",
843
+ "parallel-aggregate-failed",
844
+ "route-branch-failed"
845
+ ]);
846
+ function buildContainerId(workflowPath, scope, scopeKey) {
847
+ return `${workflowPath}|${scope}:${scopeKey}`;
848
+ }
849
+ function readString(data, key) {
850
+ const value = data[key];
851
+ return typeof value === "string" ? value : void 0;
852
+ }
853
+ function resolveScopeKey(eventType, data) {
854
+ if (eventType === "workflow-start" || eventType === "workflow-end" || eventType === "workflow-failed") {
855
+ const workflowId = readString(data, "workflow_id");
856
+ if (workflowId) return workflowId;
857
+ return readString(data, "workflow_path") ?? "workflow";
858
+ }
859
+ if (eventType === "subworkflow-start" || eventType === "subworkflow-end") {
860
+ return readString(data, "child_workflow_id") ?? "subworkflow";
861
+ }
862
+ if (eventType === "stage-start" || eventType === "stage-done" || eventType === "stage-failed") {
863
+ const stageKey = readString(data, "stage_key");
864
+ if (stageKey) return stageKey;
865
+ const stageIndex = data.stage_index;
866
+ return typeof stageIndex === "number" ? String(stageIndex) : "stage";
867
+ }
868
+ if (eventType === "parallel-item-start" || eventType === "parallel-item-done" || eventType === "parallel-item-failed") {
869
+ const itemKey = readString(data, "item_key");
870
+ if (itemKey) return itemKey;
871
+ const itemIndex = data.item_index;
872
+ return typeof itemIndex === "number" ? String(itemIndex) : "item";
873
+ }
874
+ if (eventType === "parallel-aggregate-start" || eventType === "parallel-aggregate-done" || eventType === "parallel-aggregate-failed") {
875
+ return readString(data, "aggregate_key") ?? "aggregate";
876
+ }
877
+ if (eventType === "route-branch-start" || eventType === "route-branch-done" || eventType === "route-branch-failed") {
878
+ return readString(data, "branch_key") ?? readString(data, "branch_node") ?? "branch";
879
+ }
880
+ const display = readDisplayPayload(data);
881
+ if (display?.debug_key) return display.debug_key;
882
+ return "unknown";
883
+ }
884
+ function displayStatus(data, fallback) {
885
+ const display = readDisplayPayload(data);
886
+ if (display?.status) return display.status;
887
+ return fallback;
888
+ }
889
+ function closeStatus(eventType) {
890
+ return FAILED_CLOSE_TYPES.has(eventType) ? "failed" : "completed";
891
+ }
892
+ function readPreview(data, ...keys) {
893
+ for (const key of keys) {
894
+ const value = data[key];
895
+ if (typeof value === "string") return value;
896
+ }
897
+ return "";
898
+ }
899
+ function contentBlockFromEvent(event) {
900
+ const { event_type: eventType } = event;
901
+ const payload = event.data ?? {};
902
+ if (eventType === "reasoning-start" || eventType === "reasoning-delta" || eventType === "reasoning-end") {
903
+ let preview = readPreview(payload, "delta", "text", "reasoning");
904
+ if (!preview && eventType === "reasoning-start") preview = "Thinking\u2026";
905
+ return { kind: "reasoning", preview, tool_name: null };
906
+ }
907
+ if (eventType === "text-start" || eventType === "text-delta" || eventType === "text-end") {
908
+ const preview = readPreview(payload, "delta", "text");
909
+ return { kind: "text", preview, tool_name: null };
910
+ }
911
+ if (CONTENT_EVENT_TYPES.has(eventType)) {
912
+ const toolName = event.tool_name ?? readString(payload, "tool_name");
913
+ const preview = readPreview(payload, "summary", "output", "input", "delta") || toolName || eventType;
914
+ return {
915
+ kind: "tool",
916
+ preview,
917
+ tool_name: toolName ?? null
918
+ };
919
+ }
920
+ return null;
921
+ }
922
+ function resolveContentScopeKey(data) {
923
+ for (const keyName of ["stage_key", "item_key", "branch_key", "debug_key"]) {
924
+ const value = readString(data, keyName);
925
+ if (value) return value;
926
+ }
927
+ const display = readDisplayPayload(data);
928
+ return display?.debug_key ?? null;
929
+ }
930
+ var WorkflowContainerActiveSet = class {
931
+ openOrder = [];
932
+ nodes = {};
933
+ restoreNode(node) {
934
+ this.nodes[node.container_id] = { ...node };
935
+ if (node.is_open && !this.openOrder.includes(node.container_id)) {
936
+ this.openOrder.push(node.container_id);
937
+ }
938
+ }
939
+ open(node) {
940
+ this.nodes[node.container_id] = node;
941
+ if (!this.openOrder.includes(node.container_id)) {
942
+ this.openOrder.push(node.container_id);
943
+ }
944
+ }
945
+ close(containerId, status) {
946
+ const node = this.nodes[containerId];
947
+ if (!node) return;
948
+ node.status = status;
949
+ node.is_open = false;
950
+ const idx = this.openOrder.indexOf(containerId);
951
+ if (idx !== -1) this.openOrder.splice(idx, 1);
952
+ }
953
+ get activeIds() {
954
+ return [...this.openOrder];
955
+ }
956
+ getNode(containerId) {
957
+ return this.nodes[containerId];
958
+ }
959
+ get nodesById() {
960
+ return this.nodes;
961
+ }
962
+ rankContainer(containerId) {
963
+ const node = this.nodes[containerId];
964
+ if (!node) return [-1, -1, -1, -1];
965
+ const openIndex = this.openOrder.indexOf(containerId);
966
+ const scopePriority = SCOPE_PRIORITY[node.scope] ?? 0;
967
+ return [node.workflow_depth, scopePriority, node.workflow_path.length, openIndex];
968
+ }
969
+ deepestActive() {
970
+ if (this.openOrder.length === 0) return null;
971
+ return this.openOrder.reduce(
972
+ (best, cid) => this.compareRank(cid, best) > 0 ? cid : best
973
+ );
974
+ }
975
+ resolveContentTarget(workflowPath, scopeKey) {
976
+ if (scopeKey) {
977
+ for (let i = this.openOrder.length - 1; i >= 0; i -= 1) {
978
+ const containerId = this.openOrder[i];
979
+ const node = this.nodes[containerId];
980
+ if (node.scope_key === scopeKey && node.is_open) {
981
+ if (workflowPath === null || node.workflow_path === workflowPath) {
982
+ return containerId;
983
+ }
984
+ }
985
+ }
986
+ }
987
+ if (workflowPath) {
988
+ const candidates = this.openOrder.filter((cid) => {
989
+ const node = this.nodes[cid];
990
+ if (!node.is_open) return false;
991
+ return node.workflow_path === workflowPath || workflowPath.startsWith(`${node.workflow_path}>`);
992
+ });
993
+ if (candidates.length > 0) {
994
+ return candidates.reduce(
995
+ (best, cid) => this.compareRank(cid, best) > 0 ? cid : best
996
+ );
997
+ }
998
+ }
999
+ return this.deepestActive();
1000
+ }
1001
+ closeDescendantsOfPath(workflowPath, status) {
1002
+ const toClose = this.openOrder.filter((cid) => {
1003
+ const node = this.nodes[cid];
1004
+ return node.workflow_path === workflowPath || node.workflow_path.startsWith(`${workflowPath}>`);
1005
+ });
1006
+ for (const containerId of toClose) {
1007
+ this.close(containerId, status);
1008
+ }
1009
+ }
1010
+ compareRank(a, b) {
1011
+ const ra = this.rankContainer(a);
1012
+ const rb = this.rankContainer(b);
1013
+ for (let i = 0; i < ra.length; i += 1) {
1014
+ if (ra[i] > rb[i]) return 1;
1015
+ if (ra[i] < rb[i]) return -1;
1016
+ }
1017
+ return 0;
1018
+ }
1019
+ /** 供 StateMachine 比较容器深度 rank */
1020
+ compareContainerRank(a, b) {
1021
+ return this.compareRank(a, b);
1022
+ }
1023
+ };
1024
+ var WorkflowContainerStateMachine = class {
1025
+ active = new WorkflowContainerActiveSet();
1026
+ rootIds = [];
1027
+ suppressContentContainerIds = /* @__PURE__ */ new Set();
1028
+ get activeSet() {
1029
+ return this.active;
1030
+ }
1031
+ setSuppressContentContainerIds(ids) {
1032
+ this.suppressContentContainerIds = ids;
1033
+ }
1034
+ rehydrate(snapshot) {
1035
+ this.rootIds.length = 0;
1036
+ this.rootIds.push(...snapshot.root_container_ids);
1037
+ for (const node of Object.values(snapshot.containers)) {
1038
+ this.active.restoreNode(node);
1039
+ }
1040
+ }
1041
+ apply(event) {
1042
+ const eventType = event.event_type;
1043
+ const data = event.data ?? {};
1044
+ if (OPEN_EVENT_TYPES.has(eventType)) {
1045
+ this.openContainer(eventType, data);
1046
+ return "open";
1047
+ }
1048
+ if (CLOSE_EVENT_TYPES.has(eventType)) {
1049
+ this.closeContainer(eventType, data);
1050
+ return "close";
1051
+ }
1052
+ if (CONTENT_EVENT_TYPES.has(eventType)) {
1053
+ this.appendContent(event);
1054
+ return "append-content";
1055
+ }
1056
+ return null;
1057
+ }
1058
+ snapshot() {
1059
+ return {
1060
+ root_container_ids: [...this.rootIds],
1061
+ containers: { ...this.active.nodesById },
1062
+ active_container_ids: this.active.activeIds
1063
+ };
1064
+ }
1065
+ openContainer(eventType, data) {
1066
+ const scope = EVENT_TYPE_TO_SCOPE[eventType];
1067
+ if (!scope) return;
1068
+ const workflowPath = readString(data, "workflow_path") ?? "";
1069
+ const workflowDepth = typeof data.workflow_depth === "number" ? data.workflow_depth : 0;
1070
+ const scopeKey = resolveScopeKey(eventType, data);
1071
+ const containerId = buildContainerId(workflowPath, scope, scopeKey);
1072
+ const display = readDisplayPayload(data);
1073
+ const title = display?.title ?? scopeKey;
1074
+ const subtitle = display?.description ?? "";
1075
+ const pattern = display?.pattern ?? "";
1076
+ const status = displayStatus(data, "running");
1077
+ const parentId = this.resolveParentId(workflowPath, scope);
1078
+ const node = {
1079
+ container_id: containerId,
1080
+ scope,
1081
+ workflow_path: workflowPath,
1082
+ workflow_depth: workflowDepth,
1083
+ scope_key: scopeKey,
1084
+ title,
1085
+ subtitle,
1086
+ status,
1087
+ pattern,
1088
+ parent_container_id: parentId,
1089
+ display,
1090
+ content_blocks: [],
1091
+ is_open: true
1092
+ };
1093
+ this.active.open(node);
1094
+ if (scope === "workflow" && !this.rootIds.includes(containerId)) {
1095
+ this.rootIds.push(containerId);
1096
+ }
1097
+ }
1098
+ closeContainer(eventType, data) {
1099
+ const scope = EVENT_TYPE_TO_SCOPE[eventType];
1100
+ if (!scope) return;
1101
+ const workflowPath = readString(data, "workflow_path") ?? "";
1102
+ const scopeKey = resolveScopeKey(eventType, data);
1103
+ const containerId = buildContainerId(workflowPath, scope, scopeKey);
1104
+ const status = displayStatus(data, closeStatus(eventType));
1105
+ this.active.close(containerId, status);
1106
+ if (scope === "workflow") {
1107
+ this.active.closeDescendantsOfPath(workflowPath, status);
1108
+ }
1109
+ }
1110
+ appendContent(event) {
1111
+ const data = event.data ?? {};
1112
+ const workflowPath = readString(data, "workflow_path") ?? null;
1113
+ const scopeKey = resolveContentScopeKey(data);
1114
+ const targetId = this.active.resolveContentTarget(workflowPath, scopeKey);
1115
+ if (!targetId) return;
1116
+ if (this.suppressContentContainerIds.has(targetId)) return;
1117
+ const block = contentBlockFromEvent(event);
1118
+ if (!block) return;
1119
+ const node = this.active.getNode(targetId);
1120
+ if (!node) return;
1121
+ const last = node.content_blocks[node.content_blocks.length - 1];
1122
+ if (last && last.kind === block.kind && (block.kind === "reasoning" || block.kind === "text")) {
1123
+ if (block.preview) {
1124
+ if (block.kind === "reasoning" && last.preview === "Thinking\u2026") {
1125
+ last.preview = block.preview;
1126
+ } else {
1127
+ last.preview = `${last.preview}${block.preview}`;
1128
+ }
1129
+ }
1130
+ return;
1131
+ }
1132
+ node.content_blocks.push(block);
1133
+ }
1134
+ resolveParentId(workflowPath, scope) {
1135
+ if (scope === "workflow") return null;
1136
+ const openNodes = this.active.activeIds.map((cid) => this.active.getNode(cid)).filter((node) => node.is_open);
1137
+ if (openNodes.length === 0) return null;
1138
+ const prefixCandidates = openNodes.filter(
1139
+ (node) => workflowPath === node.workflow_path || workflowPath.startsWith(`${node.workflow_path}>`)
1140
+ );
1141
+ if (prefixCandidates.length > 0) {
1142
+ const parent = prefixCandidates.reduce(
1143
+ (best, node) => this.active.compareContainerRank(node.container_id, best.container_id) > 0 ? node : best
1144
+ );
1145
+ if (parent.workflow_path === workflowPath && parent.scope === "workflow") {
1146
+ return parent.container_id;
1147
+ }
1148
+ if (workflowPath.startsWith(`${parent.workflow_path}>`)) {
1149
+ return parent.container_id;
1150
+ }
1151
+ if (parent.workflow_path === workflowPath && parent.scope !== scope) {
1152
+ return parent.container_id;
1153
+ }
1154
+ }
1155
+ const workflowCandidates = openNodes.filter((node) => node.scope === "workflow");
1156
+ if (workflowCandidates.length > 0) {
1157
+ return workflowCandidates.reduce(
1158
+ (best, node) => this.active.compareContainerRank(node.container_id, best.container_id) > 0 ? node : best
1159
+ ).container_id;
1160
+ }
1161
+ return openNodes.reduce(
1162
+ (best, node) => this.active.compareContainerRank(node.container_id, best.container_id) > 0 ? node : best
1163
+ ).container_id;
1164
+ }
1165
+ };
1166
+ var WorkflowContainerTreeProjector = class {
1167
+ machine = new WorkflowContainerStateMachine();
1168
+ rehydrate(snapshot) {
1169
+ this.machine.rehydrate(snapshot);
1170
+ }
1171
+ setSuppressContentContainerIds(ids) {
1172
+ this.machine.setSuppressContentContainerIds(ids);
1173
+ }
1174
+ consume(event) {
1175
+ return this.machine.apply(event);
1176
+ }
1177
+ consumeAll(events) {
1178
+ for (const event of events) {
1179
+ this.consume(event);
1180
+ }
1181
+ return this.snapshot();
1182
+ }
1183
+ snapshot() {
1184
+ return this.machine.snapshot();
1185
+ }
1186
+ };
1187
+ function reduceContainerSnapshot(snapshot, event, options) {
1188
+ const projector = new WorkflowContainerTreeProjector();
1189
+ projector.rehydrate(snapshot);
1190
+ if (options?.suppressContentContainerIds?.size) {
1191
+ projector.setSuppressContentContainerIds(options.suppressContentContainerIds);
1192
+ }
1193
+ projector.consume(event);
1194
+ return projector.snapshot();
1195
+ }
1196
+ function resolveContentTargetFromSnapshot(snapshot, workflowPath, scopeKey) {
1197
+ const active = new WorkflowContainerActiveSet();
1198
+ for (const containerId of snapshot.active_container_ids) {
1199
+ const node = snapshot.containers[containerId];
1200
+ if (node?.is_open) active.restoreNode(node);
1201
+ }
1202
+ return active.resolveContentTarget(workflowPath, scopeKey);
1203
+ }
1204
+ function mergeRuntimeContainerNodes(prev, snapshot) {
1205
+ const out = {};
1206
+ for (const [containerId, node] of Object.entries(snapshot.containers)) {
1207
+ out[containerId] = {
1208
+ ...node,
1209
+ loopSessionId: prev[containerId]?.loopSessionId ?? null
1210
+ };
1211
+ }
1212
+ return out;
1213
+ }
1214
+
1215
+ // src/core/workflow/loopSessionUtils.ts
1216
+ function readString2(data, key) {
1217
+ const value = data[key];
1218
+ return typeof value === "string" ? value : void 0;
1219
+ }
1220
+ var LOOP_TASK_OPEN_SCOPES = {
1221
+ "stage-start": "stage",
1222
+ "parallel-item-start": "item",
1223
+ "route-branch-start": "branch"
1224
+ };
1225
+ function buildLoopSessionId(workflowId, taskKey) {
1226
+ return `${workflowId}-${taskKey}`;
1227
+ }
1228
+ function deriveLoopSessionIdFromStructureOpen(event) {
1229
+ if (!LOOP_TASK_OPEN_SCOPES[event.event_type]) return null;
1230
+ const data = event.data ?? {};
1231
+ const workflowId = readString2(data, "workflow_id");
1232
+ if (!workflowId) return null;
1233
+ const taskKey = resolveScopeKey(event.event_type, data);
1234
+ if (!taskKey || taskKey === "unknown") return null;
1235
+ return buildLoopSessionId(workflowId, taskKey);
1236
+ }
1237
+ function deriveLoopSessionIdFromAgentEvent(event) {
1238
+ const data = event.data ?? {};
1239
+ const workflowId = readString2(data, "workflow_id") ?? readString2(data, "workflow_path");
1240
+ if (!workflowId) return null;
1241
+ const taskKey = readString2(data, "stage_key") ?? readString2(data, "item_key") ?? readString2(data, "branch_key");
1242
+ if (!taskKey) return null;
1243
+ return buildLoopSessionId(workflowId, taskKey);
1244
+ }
1245
+ function loopTaskScopeForStructureOpen(eventType) {
1246
+ return LOOP_TASK_OPEN_SCOPES[eventType];
1247
+ }
1248
+
1249
+ // src/core/workflowStructureEvents.ts
1250
+ var WORKFLOW_STRUCTURE_EVENT_TYPES = /* @__PURE__ */ new Set([
1251
+ "workflow-start",
1252
+ "workflow-end",
1253
+ "workflow-failed",
1254
+ "subworkflow-start",
1255
+ "subworkflow-end",
1256
+ "stage-start",
1257
+ "stage-done",
1258
+ "stage-failed",
1259
+ "parallel-item-start",
1260
+ "parallel-item-done",
1261
+ "parallel-item-failed",
1262
+ "parallel-aggregate-start",
1263
+ "parallel-aggregate-done",
1264
+ "parallel-aggregate-failed",
1265
+ "route-branch-start",
1266
+ "route-branch-done",
1267
+ "route-branch-failed"
1268
+ ]);
1269
+ function isWorkflowStructureEventType(eventType) {
1270
+ return WORKFLOW_STRUCTURE_EVENT_TYPES.has(eventType);
1271
+ }
1272
+
1273
+ // src/core/workflowReducer.ts
1274
+ function readString3(data, key) {
1275
+ const value = data[key];
1276
+ return typeof value === "string" ? value : void 0;
1277
+ }
1278
+ function mapContainerStatusToProgressStatus(status) {
1279
+ switch (status) {
1280
+ case "completed":
1281
+ return "done";
1282
+ case "failed":
1283
+ return "failed";
1284
+ case "running":
1285
+ return "running";
1286
+ default:
1287
+ return "pending";
1288
+ }
1289
+ }
1290
+ function syncWorkflowProgressFromContainerTree(tree, prev) {
1291
+ const workflowNodes = Object.values(tree.containersById).filter((n) => n.scope === "workflow");
1292
+ const rootWorkflow = workflowNodes.find((n) => tree.rootContainerIds.includes(n.container_id)) ?? workflowNodes[0];
1293
+ const stages = Object.values(tree.containersById).filter((n) => n.scope === "stage").sort((a, b) => {
1294
+ const ai = a.display?.progress_current ?? 0;
1295
+ const bi = b.display?.progress_current ?? 0;
1296
+ if (ai !== bi) return ai - bi;
1297
+ return a.scope_key.localeCompare(b.scope_key);
1298
+ }).map((node, index) => ({
1299
+ stageKey: node.scope_key,
1300
+ stageIndex: node.display?.progress_current ?? index,
1301
+ stageTotal: node.display?.progress_total,
1302
+ status: mapContainerStatusToProgressStatus(node.status),
1303
+ error: node.display?.status_reason
1304
+ }));
1305
+ const parallelItems = {};
1306
+ for (const node of Object.values(tree.containersById)) {
1307
+ if (node.scope !== "item") continue;
1308
+ parallelItems[node.scope_key] = {
1309
+ itemKey: node.scope_key,
1310
+ itemIndex: node.display?.item_index ?? 0,
1311
+ itemTotal: node.display?.item_total,
1312
+ status: mapContainerStatusToProgressStatus(node.status),
1313
+ loopSessionId: node.loopSessionId,
1314
+ error: node.display?.status_reason
1315
+ };
1316
+ }
1317
+ let runStatus = prev.status;
1318
+ if (rootWorkflow) {
1319
+ if (rootWorkflow.status === "completed") runStatus = "done";
1320
+ else if (rootWorkflow.status === "failed") runStatus = "error";
1321
+ else runStatus = "running";
1322
+ }
1323
+ return {
1324
+ workflowId: rootWorkflow?.scope_key ?? prev.workflowId,
1325
+ workflowPath: rootWorkflow?.workflow_path ?? prev.workflowPath,
1326
+ status: runStatus,
1327
+ stages,
1328
+ parallelItems
1329
+ };
1330
+ }
1331
+ function loopBoundContainerIds(containersById) {
1332
+ const ids = /* @__PURE__ */ new Set();
1333
+ for (const [containerId, node] of Object.entries(containersById)) {
1334
+ if (node.loopSessionId) ids.add(containerId);
1335
+ }
1336
+ return ids;
1337
+ }
1338
+ function applyStructureToContainerTree(tree, event) {
1339
+ const snapshot = reduceContainerSnapshot(containerTreeToSnapshot(tree), event, {
1340
+ suppressContentContainerIds: loopBoundContainerIds(tree.containersById)
1341
+ });
1342
+ let containersById = mergeRuntimeContainerNodes(tree.containersById, snapshot);
1343
+ containersById = enrichWorkflowRootFromStages(
1344
+ containersById,
1345
+ snapshot.root_container_ids
1346
+ );
1347
+ return {
1348
+ rootContainerIds: snapshot.root_container_ids,
1349
+ activeContainerIds: snapshot.active_container_ids,
1350
+ containersById
1351
+ };
1352
+ }
1353
+ function prebindLoopSessionFromStructureOpen(tree, event) {
1354
+ const loopSessionId = deriveLoopSessionIdFromStructureOpen(event);
1355
+ const scope = loopTaskScopeForStructureOpen(event.event_type);
1356
+ if (!loopSessionId || !scope) return tree;
1357
+ const data = event.data ?? {};
1358
+ const workflowPath = readString3(data, "workflow_path") ?? "";
1359
+ const scopeKey = resolveScopeKey(event.event_type, data);
1360
+ const containerId = buildContainerId(workflowPath, scope, scopeKey);
1361
+ const node = tree.containersById[containerId];
1362
+ if (!node) return tree;
1363
+ if (node.loopSessionId === loopSessionId && node.content_blocks.length === 0) {
1364
+ return tree;
1365
+ }
1366
+ return {
1367
+ ...tree,
1368
+ containersById: {
1369
+ ...tree.containersById,
1370
+ [containerId]: {
1371
+ ...node,
1372
+ loopSessionId,
1373
+ content_blocks: []
1374
+ }
1375
+ }
1376
+ };
1377
+ }
1378
+ function enrichWorkflowRootFromStages(containersById, rootContainerIds) {
1379
+ const rootId = rootContainerIds[0];
1380
+ if (!rootId) return containersById;
1381
+ const root = containersById[rootId];
1382
+ if (!root || root.scope !== "workflow") return containersById;
1383
+ const stages = Object.values(containersById).filter(
1384
+ (node) => node.scope === "stage" && node.workflow_path === root.workflow_path && node.workflow_depth === root.workflow_depth
1385
+ );
1386
+ if (stages.length === 0) return containersById;
1387
+ const pattern = root.pattern || root.display?.pattern || stages.find((s) => s.pattern)?.pattern || stages.find((s) => s.display?.pattern)?.display?.pattern || "chaining";
1388
+ const inferredTotal = stages.reduce(
1389
+ (max, s) => Math.max(max, s.display?.progress_total ?? 0),
1390
+ 0
1391
+ );
1392
+ const progressTotal = root.display?.progress_total ?? (inferredTotal > 0 ? inferredTotal : stages.length);
1393
+ const settled = stages.filter(
1394
+ (s) => s.status === "completed" || s.status === "failed"
1395
+ ).length;
1396
+ const progressCurrent = root.status === "completed" || root.status === "failed" ? progressTotal : Math.max(root.display?.progress_current ?? 0, settled);
1397
+ if (root.pattern === pattern && root.display?.pattern === pattern && root.display?.progress_total === progressTotal && root.display?.progress_current === progressCurrent) {
1398
+ return containersById;
1399
+ }
1400
+ const display = root.display ? {
1401
+ ...root.display,
1402
+ pattern,
1403
+ progress_total: progressTotal,
1404
+ progress_current: progressCurrent
1405
+ } : root.display;
1406
+ return {
1407
+ ...containersById,
1408
+ [rootId]: {
1409
+ ...root,
1410
+ pattern,
1411
+ display
1412
+ }
1413
+ };
1414
+ }
1415
+ function findContainerByLoopSession(tree, loopSessionId) {
1416
+ for (const [containerId, node] of Object.entries(tree.containersById)) {
1417
+ if (node.loopSessionId === loopSessionId) return containerId;
1418
+ }
1419
+ return null;
1420
+ }
1421
+ var WORKFLOW_DEFAULT_LOOP_SESSION_ID = "workflow-loop-default";
1422
+ function resolveAgentLoopSessionId(event) {
1423
+ if (event.session_id) return event.session_id;
1424
+ if (isWorkflowStructureEventType(event.event_type)) return null;
1425
+ if (event.event_type === "task-list-snapshot") return null;
1426
+ const derived = deriveLoopSessionIdFromAgentEvent(event);
1427
+ if (derived) return derived;
1428
+ return WORKFLOW_DEFAULT_LOOP_SESSION_ID;
1429
+ }
1430
+ function bindLoopSessionToContainerTree(tree, event) {
1431
+ const sessionId = resolveAgentLoopSessionId(event);
1432
+ if (!sessionId) return tree;
1433
+ if (findContainerByLoopSession(tree, sessionId)) return tree;
1434
+ const data = event.data ?? {};
1435
+ const workflowPath = readString3(data, "workflow_path") ?? null;
1436
+ const scopeKey = resolveContentScopeKey(data);
1437
+ let targetId = null;
1438
+ if (scopeKey) {
1439
+ targetId = resolveContentTargetFromSnapshot(
1440
+ containerTreeToSnapshot(tree),
1441
+ workflowPath,
1442
+ scopeKey
1443
+ );
1444
+ }
1445
+ if (!targetId) {
1446
+ for (const containerId of tree.activeContainerIds) {
1447
+ const node2 = tree.containersById[containerId];
1448
+ if ((node2?.scope === "item" || node2?.scope === "stage") && !node2.loopSessionId) {
1449
+ targetId = containerId;
1450
+ break;
1451
+ }
1452
+ }
1453
+ }
1454
+ if (!targetId) {
1455
+ targetId = resolveContentTargetFromSnapshot(
1456
+ containerTreeToSnapshot(tree),
1457
+ workflowPath,
1458
+ null
1459
+ );
1460
+ }
1461
+ if (!targetId) return tree;
1462
+ const node = tree.containersById[targetId];
1463
+ if (!node || node.loopSessionId) return tree;
1464
+ if (node.scope !== "stage" && node.scope !== "item" && node.scope !== "branch") {
1465
+ return tree;
1466
+ }
1467
+ return {
1468
+ ...tree,
1469
+ containersById: {
1470
+ ...tree.containersById,
1471
+ [targetId]: { ...node, loopSessionId: sessionId }
1472
+ }
1473
+ };
1474
+ }
1475
+ function applyStructureEvent(state, event) {
1476
+ let containerTree = applyStructureToContainerTree(state.containerTree, event);
1477
+ containerTree = prebindLoopSessionFromStructureOpen(containerTree, event);
1478
+ const loopSessionId = deriveLoopSessionIdFromStructureOpen(event);
1479
+ let nextState = { ...state, containerTree };
1480
+ if (loopSessionId) {
1481
+ nextState = ensureLoopTree(nextState, loopSessionId);
1482
+ }
1483
+ const workflowProgress = syncWorkflowProgressFromContainerTree(
1484
+ containerTree,
1485
+ state.workflowProgress
1486
+ );
1487
+ let status = nextState.status;
1488
+ if (workflowProgress.status === "done") status = "done";
1489
+ else if (workflowProgress.status === "error") status = "error";
1490
+ else if (status === "connecting") status = "running";
1491
+ const base = {
1492
+ ...nextState,
1493
+ status,
1494
+ containerTree,
1495
+ workflowProgress
1496
+ };
1497
+ if (event.session_id && !state.meta.workflowRunId) {
1498
+ return {
1499
+ ...base,
1500
+ meta: { ...state.meta, workflowRunId: event.session_id }
1501
+ };
1502
+ }
1503
+ return base;
1504
+ }
1505
+ function ensureLoopTree(state, loopSessionId) {
1506
+ if (state.loopTreesBySessionId[loopSessionId]) return state;
1507
+ const loopTree = createWorkflowRunningLoopTree();
1508
+ return {
1509
+ ...state,
1510
+ loopTreesBySessionId: {
1511
+ ...state.loopTreesBySessionId,
1512
+ [loopSessionId]: loopTree
1513
+ },
1514
+ activeLoopSessionId: state.activeLoopSessionId ?? loopSessionId
1515
+ };
1516
+ }
1517
+ function reduceWorkflowSession(state, event, eventIndex, options) {
1518
+ if (event.event_type === "task-list-snapshot") {
1519
+ const data = event.data;
1520
+ const taskListId = typeof data.task_list_id === "string" ? data.task_list_id : void 0;
1521
+ const loopSessionId2 = taskListId ?? event.session_id ?? "workflow-loop-default";
1522
+ let next2 = ensureLoopTree(state, loopSessionId2);
1523
+ const loopTree2 = next2.loopTreesBySessionId[loopSessionId2];
1524
+ const reducedLoop2 = reduceTree(loopTree2, event, eventIndex, options);
1525
+ return {
1526
+ ...next2,
1527
+ loopTreesBySessionId: {
1528
+ ...next2.loopTreesBySessionId,
1529
+ [loopSessionId2]: reducedLoop2
1530
+ },
1531
+ activeLoopSessionId: loopSessionId2
1532
+ };
1533
+ }
1534
+ if (isWorkflowStructureEventType(event.event_type)) {
1535
+ return applyStructureEvent(state, event);
1536
+ }
1537
+ const loopSessionId = resolveAgentLoopSessionId(event) ?? WORKFLOW_DEFAULT_LOOP_SESSION_ID;
1538
+ let containerTree = bindLoopSessionToContainerTree(state.containerTree, event);
1539
+ containerTree = applyStructureToContainerTree(containerTree, event);
1540
+ let next = ensureLoopTree({ ...state, containerTree }, loopSessionId);
1541
+ const loopTree = next.loopTreesBySessionId[loopSessionId];
1542
+ const reducedLoop = reduceTree(loopTree, event, eventIndex, options);
1543
+ const workflowProgress = syncWorkflowProgressFromContainerTree(
1544
+ next.containerTree,
1545
+ next.workflowProgress
1546
+ );
1547
+ return {
1548
+ ...next,
1549
+ workflowProgress,
1550
+ loopTreesBySessionId: {
1551
+ ...next.loopTreesBySessionId,
1552
+ [loopSessionId]: reducedLoop
1553
+ },
1554
+ activeLoopSessionId: loopSessionId,
1555
+ status: reducedLoop.status === "error" ? "error" : next.status === "connecting" ? "running" : next.status
1556
+ };
1557
+ }
1558
+ function reduceWorkflowEvents(events, options) {
1559
+ return events.reduce(
1560
+ (state, event, index) => reduceWorkflowSession(state, event, index, options),
1561
+ createEmptyWorkflowSessionState()
1562
+ );
1563
+ }
1564
+
1565
+ // src/core/workflow/stageDoneSummary.ts
1566
+ function countToolUses(tree) {
1567
+ return Object.values(tree.byId).filter((node) => node.kind === "tool_call").length;
1568
+ }
1569
+ function computeDurationSeconds(tree) {
1570
+ const started = tree.meta.startedAt;
1571
+ if (started == null) return null;
1572
+ let maxTs = started;
1573
+ for (const node of Object.values(tree.byId)) {
1574
+ if ("endedAt" in node && typeof node.endedAt === "number") {
1575
+ maxTs = Math.max(maxTs, node.endedAt);
1576
+ }
1577
+ if ("startedAt" in node && typeof node.startedAt === "number") {
1578
+ maxTs = Math.max(maxTs, node.startedAt);
1579
+ }
1580
+ }
1581
+ const seconds = Math.round((maxTs - started) / 1e3);
1582
+ return seconds > 0 ? seconds : null;
1583
+ }
1584
+ function lastTextContent(tree) {
1585
+ const textNodes = Object.values(tree.byId).filter((node) => node.kind === "text");
1586
+ if (textNodes.length === 0) return "";
1587
+ return textNodes[textNodes.length - 1].accumulated.trim();
1588
+ }
1589
+ function buildStageDoneSummary(tree) {
1590
+ if (tree.status !== "done" && tree.status !== "error") return null;
1591
+ const toolUses = countToolUses(tree);
1592
+ const duration = computeDurationSeconds(tree);
1593
+ const durationSuffix = duration != null ? ` \xB7 ${duration}s` : "";
1594
+ const label = toolUses > 0 ? `Done (${toolUses} tool use${toolUses === 1 ? "" : "s"}${durationSuffix})` : `Done (1 agent turn${durationSuffix})`;
1595
+ const fullText = lastTextContent(tree);
1596
+ const lines = fullText.split("\n").map((line) => line.trim()).filter(Boolean);
1597
+ const teaser = lines[0] ?? "";
1598
+ const extraLines = Math.max(0, lines.length - 1);
1599
+ return { label, teaser, extraLines };
1600
+ }
1601
+ function formatTeaserExpandHint(extraLines, verbose) {
1602
+ if (extraLines <= 0) return null;
1603
+ const shown = verbose ? Math.min(extraLines, 2) : 0;
1604
+ const hidden = extraLines - shown;
1605
+ if (hidden <= 0) return null;
1606
+ return `\u2026 +${hidden} lines (ctrl+o to expand)`;
1607
+ }
1608
+
1609
+ // src/core/workflow/workflowLoopEviction.ts
1610
+ function containersByLoopSessionId(tree) {
1611
+ const map = /* @__PURE__ */ new Map();
1612
+ for (const node of Object.values(tree.containersById)) {
1613
+ if (node.loopSessionId) map.set(node.loopSessionId, node);
1614
+ }
1615
+ return map;
1616
+ }
1617
+ function collectProtectedLoopSessionIds(state, pinnedLoopSessionIds) {
1618
+ const protectedIds = new Set(Object.keys(pinnedLoopSessionIds));
1619
+ const byLoop = containersByLoopSessionId(state.containerTree);
1620
+ if (state.activeLoopSessionId) {
1621
+ const activeContainer = byLoop.get(state.activeLoopSessionId);
1622
+ if (activeContainer && (activeContainer.status === "running" || activeContainer.status === "retrying")) {
1623
+ protectedIds.add(state.activeLoopSessionId);
1624
+ }
1625
+ }
1626
+ for (const containerId of state.containerTree.activeContainerIds) {
1627
+ const node = state.containerTree.containersById[containerId];
1628
+ if (!node?.loopSessionId) continue;
1629
+ if (node.status === "running" || node.status === "retrying") {
1630
+ protectedIds.add(node.loopSessionId);
1631
+ }
1632
+ }
1633
+ for (const node of Object.values(state.containerTree.containersById)) {
1634
+ if (!node.loopSessionId) continue;
1635
+ if (node.status === "running" || node.status === "retrying") {
1636
+ protectedIds.add(node.loopSessionId);
1637
+ }
1638
+ }
1639
+ return protectedIds;
1640
+ }
1641
+ function evictInactiveLoopTrees(state, pinnedLoopSessionIds) {
1642
+ const protectedIds = collectProtectedLoopSessionIds(state, pinnedLoopSessionIds);
1643
+ const byLoop = containersByLoopSessionId(state.containerTree);
1644
+ const nextLoops = { ...state.loopTreesBySessionId };
1645
+ const nextSummaries = {
1646
+ ...state.stageDoneSummariesByContainerId
1647
+ };
1648
+ for (const [loopSessionId, tree] of Object.entries(state.loopTreesBySessionId)) {
1649
+ if (protectedIds.has(loopSessionId)) continue;
1650
+ const container = byLoop.get(loopSessionId);
1651
+ if (!container) continue;
1652
+ if (container.status !== "completed" && container.status !== "failed") continue;
1653
+ const summary = buildStageDoneSummary(tree);
1654
+ if (summary) {
1655
+ nextSummaries[container.container_id] = summary;
1656
+ }
1657
+ delete nextLoops[loopSessionId];
1658
+ }
1659
+ return {
1660
+ ...state,
1661
+ loopTreesBySessionId: nextLoops,
1662
+ stageDoneSummariesByContainerId: nextSummaries
1663
+ };
1664
+ }
1665
+
1666
+ // src/core/workflowSessionStore.ts
1667
+ function safeReduceWorkflow(state, event, eventIndex, options) {
1668
+ try {
1669
+ return { state: reduceWorkflowSession(state, event, eventIndex, options) };
1670
+ } catch (err) {
1671
+ const message = err instanceof Error ? err.message : String(err);
1672
+ const stack = err instanceof Error ? err.stack : void 0;
1673
+ if (import.meta.env?.DEV) {
1674
+ console.error("[langchain_agentx_stream_ui] workflow reducer error:", err);
1675
+ }
1676
+ return {
1677
+ state: {
1678
+ ...state,
1679
+ internalErrors: [
1680
+ ...state.internalErrors,
1681
+ { eventIndex, eventType: event.event_type, message, stack }
1682
+ ]
1683
+ }
1684
+ };
1685
+ }
1686
+ }
1687
+ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionState(), storeOptions) {
1688
+ const reduceOpts = {
1689
+ tierOverrides: storeOptions?.tierOverrides
1690
+ };
1691
+ const initialEventCount = storeOptions?.initialEventCount ?? 0;
1692
+ const seenEventIds = new Set(storeOptions?.initialSeenEventIds);
1693
+ const evictCompletedLoops = storeOptions?.evictCompletedLoops ?? true;
1694
+ const pinnedLoopSessionIds = {};
1695
+ const finalizeState = (reduced) => {
1696
+ if (!evictCompletedLoops) return reduced;
1697
+ return evictInactiveLoopTrees(reduced, pinnedLoopSessionIds);
1698
+ };
1699
+ return createStore2((set, get) => ({
1700
+ state: initialState,
1701
+ eventCount: initialEventCount,
1702
+ applyEvent(event, ctx) {
1703
+ const sseEventId = ctx?.sseEventId;
1704
+ if (shouldSkipDuplicateEvent(seenEventIds, sseEventId)) return;
1705
+ const { state, eventCount } = get();
1706
+ const { state: reduced } = safeReduceWorkflow(state, event, eventCount, reduceOpts);
1707
+ const evicted = finalizeState(reduced);
1708
+ const nextMeta = sseEventId ? { ...evicted.meta, lastEventId: sseEventId } : evicted.meta;
1709
+ markEventIdSeen(seenEventIds, sseEventId);
1710
+ set({
1711
+ state: {
1712
+ ...evicted,
1713
+ meta: nextMeta,
1714
+ status: evicted.status === "connecting" ? "running" : evicted.status
1715
+ },
1716
+ eventCount: eventCount + 1
1717
+ });
1718
+ },
1719
+ applyEvents(events) {
1720
+ for (const event of events) {
1721
+ get().applyEvent(event);
1722
+ }
1723
+ },
1724
+ reset() {
1725
+ seenEventIds.clear();
1726
+ set({
1727
+ state: createEmptyWorkflowSessionState(),
1728
+ eventCount: 0
1729
+ });
1730
+ },
1731
+ markAsError(error) {
1732
+ const { state } = get();
1733
+ const message = error?.message ?? "Workflow stream error";
1734
+ set({
1735
+ state: {
1736
+ ...state,
1737
+ status: "error",
1738
+ internalErrors: [
1739
+ ...state.internalErrors,
1740
+ {
1741
+ eventIndex: get().eventCount,
1742
+ eventType: "stream-error",
1743
+ message,
1744
+ stack: error?.stack
1745
+ }
1746
+ ]
1747
+ }
1748
+ });
1749
+ },
1750
+ pinLoopSession(loopSessionId) {
1751
+ pinnedLoopSessionIds[loopSessionId] = true;
1752
+ set((current) => ({
1753
+ state: finalizeState(current.state)
1754
+ }));
1755
+ },
1756
+ hydrateLoopSession(loopSessionId, events) {
1757
+ pinnedLoopSessionIds[loopSessionId] = true;
1758
+ const { state, eventCount } = get();
1759
+ let tree = state.loopTreesBySessionId[loopSessionId] ?? createEmptyTree();
1760
+ let idx = eventCount;
1761
+ for (const event of events) {
1762
+ tree = reduceTree(tree, event, idx, reduceOpts);
1763
+ idx += 1;
1764
+ }
1765
+ set({
1766
+ state: finalizeState({
1767
+ ...state,
1768
+ loopTreesBySessionId: {
1769
+ ...state.loopTreesBySessionId,
1770
+ [loopSessionId]: tree
1771
+ }
1772
+ })
1773
+ });
1774
+ }
1775
+ }));
1776
+ }
1777
+
1778
+ // src/view/workflow/WorkflowChrome.tsx
1779
+ import { useMemo as useMemo7, useState as useState6 } from "react";
1780
+
1781
+ // src/view/workflow/WorkflowAggregateContainer.tsx
1782
+ import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState } from "react";
1783
+
1784
+ // src/view/workflow/WorkflowContainerLine.tsx
1785
+ import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
1786
+ function WorkflowContainerLine({
1787
+ title,
1788
+ description,
1789
+ uiStatus,
1790
+ meta = [],
1791
+ statusLabel,
1792
+ showDot = true,
1793
+ variant = "default",
1794
+ onToggle,
1795
+ expanded,
1796
+ interactive,
1797
+ titleTestId,
1798
+ descriptionTestId,
1799
+ headerTestId,
1800
+ debugScopeKey,
1801
+ verbose = false,
1802
+ className = "",
1803
+ style
1804
+ }) {
1805
+ const lineClass = [
1806
+ "lax-workflow-container-line",
1807
+ variant === "root" ? "lax-workflow-container-line--root" : "",
1808
+ onToggle && interactive !== false ? "lax-workflow-container-line--interactive" : "",
1809
+ className
1810
+ ].filter(Boolean).join(" ");
1811
+ const content = /* @__PURE__ */ jsxs2(Fragment, { children: [
1812
+ showDot ? /* @__PURE__ */ jsx5(
1813
+ "span",
1814
+ {
1815
+ className: "lax-workflow-container-line__status-dot lax-workflow-stage-container__status-dot",
1816
+ "data-status": uiStatus,
1817
+ "aria-hidden": true
1818
+ }
1819
+ ) : null,
1820
+ /* @__PURE__ */ jsx5("span", { className: "lax-workflow-container-line__title", "data-testid": titleTestId, children: title }),
1821
+ description ? /* @__PURE__ */ jsxs2(
1822
+ "span",
1823
+ {
1824
+ className: "lax-workflow-container-line__desc",
1825
+ "data-testid": descriptionTestId,
1826
+ children: [
1827
+ "(",
1828
+ description,
1829
+ ")"
1830
+ ]
1831
+ }
1832
+ ) : null,
1833
+ meta.length > 0 ? /* @__PURE__ */ jsx5("span", { className: "lax-workflow-container-line__meta", "data-testid": titleTestId ? `${titleTestId}-meta` : void 0, children: meta.join(" \xB7 ") }) : null,
1834
+ verbose && debugScopeKey ? /* @__PURE__ */ jsx5("span", { className: "lax-workflow-container-line__debug", "data-testid": "lax-workflow-id", children: debugScopeKey }) : null,
1835
+ /* @__PURE__ */ jsx5("span", { className: "lax-workflow-container-line__status", children: statusLabel })
1836
+ ] });
1837
+ if (onToggle && interactive !== false) {
1838
+ return /* @__PURE__ */ jsx5(
1839
+ "button",
1840
+ {
1841
+ type: "button",
1842
+ className: `${lineClass} lax-workflow-stage-container__header`,
1843
+ onClick: onToggle,
1844
+ "aria-expanded": expanded,
1845
+ "data-testid": headerTestId,
1846
+ style,
1847
+ children: content
1848
+ }
1849
+ );
1850
+ }
1851
+ if (onToggle) {
1852
+ return /* @__PURE__ */ jsx5(
1853
+ "div",
1854
+ {
1855
+ className: `${lineClass} lax-workflow-container-line--static-toggle`,
1856
+ onClick: onToggle,
1857
+ onKeyDown: (event) => {
1858
+ if (event.key === "Enter" || event.key === " ") {
1859
+ event.preventDefault();
1860
+ onToggle();
1861
+ }
1862
+ },
1863
+ role: "button",
1864
+ tabIndex: 0,
1865
+ "aria-expanded": expanded,
1866
+ "data-testid": headerTestId,
1867
+ style,
1868
+ children: content
1869
+ }
1870
+ );
1871
+ }
1872
+ return /* @__PURE__ */ jsx5("div", { className: lineClass, style, children: content });
1873
+ }
1874
+
1875
+ // src/view/workflow/WorkflowExpandTrigger.tsx
1876
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1877
+ function WorkflowExpandTrigger({
1878
+ summary,
1879
+ summaryTestId,
1880
+ teaser,
1881
+ expandHint,
1882
+ onToggle,
1883
+ expanded = false,
1884
+ testId,
1885
+ teaserTestId,
1886
+ className = ""
1887
+ }) {
1888
+ return /* @__PURE__ */ jsxs3(
1889
+ "button",
1890
+ {
1891
+ type: "button",
1892
+ className: `lax-workflow-expand-trigger${className ? ` ${className}` : ""}`,
1893
+ onClick: onToggle,
1894
+ "aria-expanded": expanded,
1895
+ "data-testid": testId,
1896
+ children: [
1897
+ /* @__PURE__ */ jsxs3("span", { className: "lax-workflow-expand-trigger__summary", "data-testid": summaryTestId, children: [
1898
+ "\u23BF ",
1899
+ summary
1900
+ ] }),
1901
+ teaser ? /* @__PURE__ */ jsx6(
1902
+ "span",
1903
+ {
1904
+ className: "lax-workflow-expand-trigger__teaser",
1905
+ "data-testid": teaserTestId,
1906
+ children: teaser
1907
+ }
1908
+ ) : null,
1909
+ expandHint ? /* @__PURE__ */ jsx6("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
1910
+ ]
1911
+ }
1912
+ );
1913
+ }
1914
+
1915
+ // src/view/workflow/workflowContainerLineUtils.ts
1916
+ function formatContainerScopeMetaLabel(scope) {
1917
+ return scope;
1918
+ }
1919
+ function withScopeMeta(node, parts) {
1920
+ return [formatContainerScopeMetaLabel(node.scope), ...parts];
1921
+ }
1922
+ function mapContainerUiStatus(status) {
1923
+ switch (status) {
1924
+ case "completed":
1925
+ return "done";
1926
+ case "failed":
1927
+ return "failed";
1928
+ case "running":
1929
+ case "retrying":
1930
+ return "running";
1931
+ case "skipped":
1932
+ return "skipped";
1933
+ default:
1934
+ return "pending";
1935
+ }
1936
+ }
1937
+ function mapContainerStatusLabel(status) {
1938
+ switch (status) {
1939
+ case "completed":
1940
+ return "done";
1941
+ case "failed":
1942
+ return "failed";
1943
+ case "running":
1944
+ return "Running\u2026";
1945
+ case "retrying":
1946
+ return "Retrying\u2026";
1947
+ case "skipped":
1948
+ return "skipped";
1949
+ case "blocked":
1950
+ return "blocked";
1951
+ case "cancelled":
1952
+ return "cancelled";
1953
+ default:
1954
+ return "pending";
1955
+ }
1956
+ }
1957
+ function mapWorkflowRootStatusLabel(status) {
1958
+ switch (status) {
1959
+ case "completed":
1960
+ return "done";
1961
+ case "failed":
1962
+ return "error";
1963
+ default:
1964
+ return "running";
1965
+ }
1966
+ }
1967
+ function buildStageProgressMeta(node) {
1968
+ const display = node.display;
1969
+ if (display?.progress_label) return display.progress_label;
1970
+ if (display?.progress_current != null && display?.progress_total != null) {
1971
+ return `Step ${display.progress_current}/${display.progress_total}`;
1972
+ }
1973
+ return null;
1974
+ }
1975
+ function buildWorkflowRootMeta(node) {
1976
+ const meta = [];
1977
+ if (node.pattern) meta.push(node.pattern);
1978
+ const display = node.display;
1979
+ if (display?.progress_current != null && display?.progress_total != null) {
1980
+ meta.push(`${display.progress_current}/${display.progress_total}`);
1981
+ }
1982
+ return withScopeMeta(node, meta);
1983
+ }
1984
+ function buildStageLineMeta(node) {
1985
+ const progress = buildStageProgressMeta(node);
1986
+ return withScopeMeta(node, progress ? [progress] : []);
1987
+ }
1988
+ function buildAggregateLineMeta(node) {
1989
+ return withScopeMeta(node, []);
1990
+ }
1991
+ function buildRouteLineMeta(node) {
1992
+ const meta = [];
1993
+ const decision = node.display?.decision_label;
1994
+ if (decision) meta.push(decision);
1995
+ const progress = buildStageProgressMeta(node);
1996
+ if (progress) meta.push(progress);
1997
+ return withScopeMeta(node, meta);
1998
+ }
1999
+ function workflowIndentUnit(depth) {
2000
+ return depth >= 4 ? "0.75rem" : "1rem";
2001
+ }
2002
+ function countSettledItems(items) {
2003
+ let settled = 0;
2004
+ for (const item of items) {
2005
+ if (item.status === "completed" || item.status === "failed") {
2006
+ settled += 1;
2007
+ }
2008
+ }
2009
+ return { settled, total: items.length };
2010
+ }
2011
+ function buildAggregateStatusLabel(node, siblingItems = []) {
2012
+ const display = node.display;
2013
+ let current = display?.progress_current;
2014
+ let total = display?.progress_total;
2015
+ if ((current == null || total == null) && siblingItems.length > 0) {
2016
+ const { settled, total: itemTotal } = countSettledItems(siblingItems);
2017
+ current = settled;
2018
+ total = itemTotal;
2019
+ }
2020
+ if (node.status === "pending" || node.status === "blocked") {
2021
+ if (current != null && total != null) {
2022
+ return `waiting (${current}/${total})`;
2023
+ }
2024
+ return "waiting";
2025
+ }
2026
+ if (current != null && total != null && (node.status === "running" || node.status === "retrying")) {
2027
+ return `waiting (${current}/${total})`;
2028
+ }
2029
+ return mapContainerStatusLabel(node.status);
2030
+ }
2031
+ function mapAggregateUiStatus(node) {
2032
+ if (node.status === "pending" || node.status === "blocked") return "pending";
2033
+ if (node.status === "completed") return "done";
2034
+ if (node.status === "failed") return "failed";
2035
+ if (node.status === "running" || node.status === "retrying") return "running";
2036
+ return mapContainerUiStatus(node.status);
2037
+ }
2038
+
2039
+ // src/view/workflow/WorkflowAggregateContainer.tsx
2040
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
2041
+ function mergeTeaser(node) {
2042
+ const blocks = node.content_blocks;
2043
+ if (blocks.length === 0) return "";
2044
+ const last = blocks[blocks.length - 1];
2045
+ return last?.preview ?? "";
2046
+ }
2047
+ function WorkflowAggregateContainer({
2048
+ node,
2049
+ loopTree,
2050
+ siblingItems = [],
2051
+ depth = 1,
2052
+ virtualized,
2053
+ virtualizeThreshold,
2054
+ groupParallelTools
2055
+ }) {
2056
+ const isRunning = node.status === "running" || node.status === "retrying";
2057
+ const isWaiting = node.status === "pending" || node.status === "blocked";
2058
+ const isDone = node.status === "completed" || node.status === "failed";
2059
+ const teaser = useMemo2(() => mergeTeaser(node), [node]);
2060
+ const waitingTeaser = isWaiting ? teaser || node.subtitle || "" : "";
2061
+ const testId = `lax-workflow-aggregate-${node.scope_key}`;
2062
+ const uiStatus = mapAggregateUiStatus(node);
2063
+ const statusLabel = buildAggregateStatusLabel(node, siblingItems);
2064
+ const [expanded, setExpanded] = useState(isRunning);
2065
+ useEffect3(() => {
2066
+ if (isRunning) setExpanded(true);
2067
+ if (isDone) setExpanded(false);
2068
+ }, [isRunning, isDone]);
2069
+ const toggleExpanded = useCallback(() => {
2070
+ setExpanded((prev) => !prev);
2071
+ }, []);
2072
+ const shellToggle = isRunning ? toggleExpanded : isDone && expanded ? toggleExpanded : void 0;
2073
+ return /* @__PURE__ */ jsxs4(
2074
+ "div",
2075
+ {
2076
+ className: `lax-workflow-aggregate lax-workflow-stage-container lax-workflow-stage-container--${isWaiting ? "pending" : isDone ? "done" : "running"}`,
2077
+ "data-testid": testId,
2078
+ "data-container-scope": "aggregate",
2079
+ style: { "--lax-workflow-depth": depth },
2080
+ children: [
2081
+ /* @__PURE__ */ jsx7(
2082
+ WorkflowContainerLine,
2083
+ {
2084
+ title: node.title,
2085
+ description: node.subtitle || void 0,
2086
+ uiStatus,
2087
+ meta: buildAggregateLineMeta(node),
2088
+ statusLabel,
2089
+ onToggle: shellToggle,
2090
+ interactive: isRunning,
2091
+ expanded,
2092
+ titleTestId: `${testId}-title`,
2093
+ descriptionTestId: `${testId}-desc`
2094
+ }
2095
+ ),
2096
+ isWaiting && waitingTeaser ? /* @__PURE__ */ jsxs4("div", { className: "lax-workflow-aggregate__waiting-teaser", "data-testid": `${testId}-waiting`, children: [
2097
+ "\u23BF ",
2098
+ waitingTeaser
2099
+ ] }) : null,
2100
+ expanded && isRunning && loopTree ? /* @__PURE__ */ jsx7("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx7(
2101
+ AgentLoopView,
2102
+ {
2103
+ tree: loopTree,
2104
+ showTaskListFooter: false,
2105
+ virtualized,
2106
+ virtualizeThreshold,
2107
+ groupParallelTools
2108
+ }
2109
+ ) }) : null,
2110
+ isDone && !expanded ? /* @__PURE__ */ jsx7(
2111
+ WorkflowExpandTrigger,
2112
+ {
2113
+ summary: `${node.title} \xB7 done`,
2114
+ teaser: teaser || void 0,
2115
+ onToggle: () => setExpanded(true),
2116
+ expanded: false,
2117
+ testId: `${testId}-done`,
2118
+ teaserTestId: `${testId}-teaser`
2119
+ }
2120
+ ) : null,
2121
+ isDone && expanded && loopTree ? /* @__PURE__ */ jsx7("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx7(
2122
+ AgentLoopView,
2123
+ {
2124
+ tree: loopTree,
2125
+ showTaskListFooter: false,
2126
+ virtualized,
2127
+ virtualizeThreshold,
2128
+ groupParallelTools
2129
+ }
2130
+ ) }) : null
2131
+ ]
2132
+ }
2133
+ );
2134
+ }
2135
+
2136
+ // src/view/workflow/WorkflowParallelGroup.tsx
2137
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo4, useState as useState3 } from "react";
2138
+ import { useStore as useStore3 } from "zustand";
2139
+
2140
+ // src/core/workflow/workflowParallelGroupMetrics.ts
2141
+ function readDisplayMetrics(items) {
2142
+ let hasMetrics = false;
2143
+ let running = 0;
2144
+ let completed = 0;
2145
+ let failed = 0;
2146
+ let total = 0;
2147
+ for (const item of items) {
2148
+ const metrics = item.display;
2149
+ if (metrics?.metrics_total != null || metrics?.metrics_running != null || metrics?.metrics_completed != null || metrics?.metrics_failed != null) {
2150
+ hasMetrics = true;
2151
+ }
2152
+ running += metrics?.metrics_running ?? 0;
2153
+ completed += metrics?.metrics_completed ?? 0;
2154
+ failed += metrics?.metrics_failed ?? 0;
2155
+ total += metrics?.metrics_total ?? 0;
2156
+ }
2157
+ if (!hasMetrics) return null;
2158
+ if (total <= 0) total = items.length;
2159
+ return { total, running, completed, failed };
2160
+ }
2161
+ function computeParallelGroupMetrics(items) {
2162
+ const fromDisplay = readDisplayMetrics(items);
2163
+ if (fromDisplay) return fromDisplay;
2164
+ let running = 0;
2165
+ let completed = 0;
2166
+ let failed = 0;
2167
+ for (const item of items) {
2168
+ switch (item.status) {
2169
+ case "running":
2170
+ case "retrying":
2171
+ running += 1;
2172
+ break;
2173
+ case "completed":
2174
+ completed += 1;
2175
+ break;
2176
+ case "failed":
2177
+ failed += 1;
2178
+ break;
2179
+ default:
2180
+ break;
2181
+ }
2182
+ }
2183
+ return {
2184
+ total: items.length,
2185
+ running,
2186
+ completed,
2187
+ failed
2188
+ };
2189
+ }
2190
+ function formatParallelGroupSummary(metrics) {
2191
+ if (metrics.running > 0) {
2192
+ const n2 = metrics.running;
2193
+ return `Running ${n2} agent${n2 === 1 ? "" : "s"}\u2026`;
2194
+ }
2195
+ if (metrics.failed > 0) {
2196
+ const ok = metrics.completed;
2197
+ return `Done (${ok} agent${ok === 1 ? "" : "s"}) \xB7 ${metrics.failed} failed`;
2198
+ }
2199
+ const n = metrics.total;
2200
+ return `Done (${n} agent${n === 1 ? "" : "s"})`;
2201
+ }
2202
+
2203
+ // src/view/workflow/WorkflowStageContainer.tsx
2204
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3, useState as useState2 } from "react";
2205
+ import { useStore as useStore2 } from "zustand";
2206
+
2207
+ // src/core/workflow/loopTreeUtils.ts
2208
+ function hasTimelineNodes(tree) {
2209
+ if (!tree) return false;
2210
+ return tree.rootChildren.length > 0 || Object.keys(tree.byId).length > 0;
2211
+ }
2212
+
2213
+ // src/view/workflow/workflowStageDoneBody.tsx
2214
+ import { jsx as jsx8 } from "react/jsx-runtime";
2215
+ function WorkflowStageDoneBody({
2216
+ loopTree,
2217
+ testId,
2218
+ virtualized,
2219
+ virtualizeThreshold,
2220
+ groupParallelTools,
2221
+ bodyClassName = "lax-workflow-stage-container__body"
2222
+ }) {
2223
+ if (loopTree && hasTimelineNodes(loopTree)) {
2224
+ return /* @__PURE__ */ jsx8("div", { className: bodyClassName, "data-testid": testId, children: /* @__PURE__ */ jsx8(
2225
+ AgentLoopView,
2226
+ {
2227
+ tree: loopTree,
2228
+ showTaskListFooter: false,
2229
+ virtualized,
2230
+ virtualizeThreshold,
2231
+ groupParallelTools
2232
+ }
2233
+ ) });
2234
+ }
2235
+ return /* @__PURE__ */ jsx8(
2236
+ "div",
2237
+ {
2238
+ className: `${bodyClassName} lax-workflow-stage-container__empty-hint`,
2239
+ "data-testid": testId,
2240
+ children: /* @__PURE__ */ jsx8("span", { className: "lax-workflow-stage-container__empty-hint-text", children: "\uFF08\u6B64\u9636\u6BB5\u6682\u65E0 Agent \u65F6\u95F4\u7EBF\u5185\u5BB9\uFF09" })
2241
+ }
2242
+ );
2243
+ }
2244
+
2245
+ // src/view/workflow/workflowStageListUtils.ts
2246
+ function stageSortKey(node) {
2247
+ return node.display?.progress_current ?? 0;
2248
+ }
2249
+ function partitionWorkflowStages(stages, pageSize, showAll) {
2250
+ if (showAll || stages.length <= pageSize) {
2251
+ return { visible: stages, hiddenCount: 0, total: stages.length };
2252
+ }
2253
+ const sorted = [...stages].sort(
2254
+ (a, b) => stageSortKey(a) - stageSortKey(b) || a.scope_key.localeCompare(b.scope_key)
2255
+ );
2256
+ const always = [];
2257
+ const done = [];
2258
+ for (const stage of sorted) {
2259
+ if (stage.status === "running" || stage.status === "retrying" || stage.status === "failed" || stage.status === "pending" || stage.status === "blocked") {
2260
+ always.push(stage);
2261
+ } else if (stage.status === "completed" || stage.status === "skipped") {
2262
+ done.push(stage);
2263
+ } else {
2264
+ always.push(stage);
2265
+ }
2266
+ }
2267
+ const recentDone = done.slice(Math.max(0, done.length - pageSize));
2268
+ const visible = [...always, ...recentDone].sort(
2269
+ (a, b) => stageSortKey(a) - stageSortKey(b) || a.scope_key.localeCompare(b.scope_key)
2270
+ );
2271
+ const hiddenCount = stages.length - visible.length;
2272
+ return { visible, hiddenCount, total: stages.length };
2273
+ }
2274
+ function isActiveWorkflowContainer(node, activeLoopSessionId, activeContainerIds) {
2275
+ if (activeContainerIds.includes(node.container_id)) return true;
2276
+ if (node.loopSessionId && node.loopSessionId === activeLoopSessionId) return true;
2277
+ return false;
2278
+ }
2279
+
2280
+ // src/view/workflow/WorkflowStageContainer.tsx
2281
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
2282
+ function containerTestId(node) {
2283
+ if (node.scope === "item") return `lax-workflow-parallel-${node.scope_key}`;
2284
+ if (node.scope === "stage") return `lax-workflow-stage-${node.scope_key}`;
2285
+ return `lax-workflow-container-${node.scope_key}`;
2286
+ }
2287
+ function bodyTestId(node) {
2288
+ if (node.scope === "item") return `lax-workflow-parallel-body-${node.scope_key}`;
2289
+ return `lax-workflow-stage-body-${node.scope_key}`;
2290
+ }
2291
+ function WorkflowStageContainer({
2292
+ node,
2293
+ loopTree,
2294
+ depth = 0,
2295
+ virtualized,
2296
+ virtualizeThreshold,
2297
+ groupParallelTools
2298
+ }) {
2299
+ const { verbose } = useSessionViewOptions();
2300
+ const scale = useWorkflowScaleOptions();
2301
+ const store = useWorkflowSessionStoreApi();
2302
+ const replayHandler = useWorkflowLoopReplayHandler();
2303
+ const activeLoopSessionId = useStore2(store, (s) => s.state.activeLoopSessionId);
2304
+ const activeContainerIds = useStore2(store, (s) => s.state.containerTree.activeContainerIds);
2305
+ const cachedSummary = useStore2(
2306
+ store,
2307
+ (s) => s.state.stageDoneSummariesByContainerId[node.container_id]
2308
+ );
2309
+ const pinLoopSession = useStore2(store, (s) => s.pinLoopSession);
2310
+ const uiStatus = mapContainerUiStatus(node.status);
2311
+ const isRunning = node.status === "running" || node.status === "retrying";
2312
+ const isDone = node.status === "completed" || node.status === "failed";
2313
+ const isSkipped = node.status === "skipped";
2314
+ const isActive = isActiveWorkflowContainer(node, activeLoopSessionId, activeContainerIds);
2315
+ const doneSummaryFromTree = useMemo3(
2316
+ () => loopTree && isDone ? buildStageDoneSummary(loopTree) : null,
2317
+ [loopTree, isDone]
2318
+ );
2319
+ const doneSummary = doneSummaryFromTree ?? (isDone ? cachedSummary : null);
2320
+ const shouldAutoExpandRunning = isRunning && !isSkipped && (!scale.onlyExpandActiveRunning || isActive);
2321
+ const [expanded, setExpanded] = useState2(shouldAutoExpandRunning);
2322
+ useEffect4(() => {
2323
+ if (shouldAutoExpandRunning) {
2324
+ setExpanded(true);
2325
+ return;
2326
+ }
2327
+ if (isRunning && !isSkipped && scale.onlyExpandActiveRunning) {
2328
+ setExpanded(false);
2329
+ return;
2330
+ }
2331
+ if (isDone || isSkipped) {
2332
+ setExpanded(false);
2333
+ }
2334
+ }, [shouldAutoExpandRunning, isRunning, isDone, isSkipped, scale.onlyExpandActiveRunning]);
2335
+ const requestLoopHydration = useCallback2(() => {
2336
+ const loopSessionId = node.loopSessionId;
2337
+ if (!loopSessionId || loopTree) return;
2338
+ pinLoopSession(loopSessionId);
2339
+ void replayHandler?.(loopSessionId);
2340
+ }, [node.loopSessionId, loopTree, pinLoopSession, replayHandler]);
2341
+ const toggleExpanded = useCallback2(() => {
2342
+ setExpanded((prev) => {
2343
+ const next = !prev;
2344
+ if (next && isDone && node.loopSessionId && !loopTree) {
2345
+ requestLoopHydration();
2346
+ }
2347
+ return next;
2348
+ });
2349
+ }, [isDone, node.loopSessionId, loopTree, requestLoopHydration]);
2350
+ useEffect4(() => {
2351
+ const onKeyDown = (event) => {
2352
+ if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "o") return;
2353
+ setExpanded(true);
2354
+ requestLoopHydration();
2355
+ };
2356
+ window.addEventListener("keydown", onKeyDown);
2357
+ return () => window.removeEventListener("keydown", onKeyDown);
2358
+ }, [requestLoopHydration]);
2359
+ const displayTitle = node.title;
2360
+ const usesScopedLoop = Boolean(node.loopSessionId);
2361
+ const runningLoopTree = loopTree ?? (usesScopedLoop && isRunning && isActive ? createWorkflowRunningLoopTree() : void 0);
2362
+ const staticContentTeaser = !usesScopedLoop && !loopTree && node.content_blocks.length > 0 ? node.content_blocks[node.content_blocks.length - 1].preview : "";
2363
+ const teaserText = doneSummary?.teaser || staticContentTeaser;
2364
+ const expandHint = doneSummary ? formatTeaserExpandHint(doneSummary.extraLines, verbose) : null;
2365
+ const doneLabel = doneSummary?.label ?? "done";
2366
+ const statusLabel = isDone ? doneLabel : mapContainerStatusLabel(node.status);
2367
+ const testId = containerTestId(node);
2368
+ const isItem = node.scope === "item";
2369
+ const canToggleShell = isRunning && !isSkipped || isDone;
2370
+ const shellToggle = canToggleShell ? toggleExpanded : void 0;
2371
+ return /* @__PURE__ */ jsxs5(
2372
+ "div",
2373
+ {
2374
+ className: `lax-workflow-stage-container lax-workflow-stage-container--${uiStatus}${isSkipped ? " lax-workflow-stage-container--skipped" : ""}${isItem ? " lax-workflow-parallel-item" : " lax-workflow-stage"}`,
2375
+ "data-testid": testId,
2376
+ "data-stage-status": uiStatus,
2377
+ "data-container-scope": node.scope,
2378
+ "data-active": isActive ? "true" : void 0,
2379
+ style: {
2380
+ "--lax-workflow-depth": depth,
2381
+ "--lax-workflow-indent-unit": workflowIndentUnit(depth)
2382
+ },
2383
+ children: [
2384
+ /* @__PURE__ */ jsx9(
2385
+ WorkflowContainerLine,
2386
+ {
2387
+ title: displayTitle,
2388
+ description: node.subtitle || void 0,
2389
+ uiStatus,
2390
+ meta: buildStageLineMeta(node),
2391
+ statusLabel,
2392
+ onToggle: shellToggle,
2393
+ interactive: canToggleShell,
2394
+ expanded,
2395
+ headerTestId: isDone ? `${testId}-done` : void 0,
2396
+ titleTestId: `${testId}-title`,
2397
+ descriptionTestId: `${testId}-desc`,
2398
+ className: isItem ? "lax-workflow-parallel-item__header" : void 0
2399
+ }
2400
+ ),
2401
+ isDone && !expanded && (teaserText || expandHint) ? /* @__PURE__ */ jsxs5("div", { className: "lax-workflow-stage-container__done-teaser", children: [
2402
+ teaserText ? /* @__PURE__ */ jsx9(
2403
+ "span",
2404
+ {
2405
+ className: "lax-workflow-stage-container__done-teaser-line",
2406
+ "data-testid": `${testId}-teaser`,
2407
+ children: `\u23BF ${teaserText}`
2408
+ }
2409
+ ) : null,
2410
+ expandHint ? /* @__PURE__ */ jsx9("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
2411
+ ] }) : null,
2412
+ expanded && isRunning && !usesScopedLoop && node.content_blocks.length > 0 ? /* @__PURE__ */ jsx9(
2413
+ "div",
2414
+ {
2415
+ className: "lax-workflow-stage-container__body lax-workflow-stage-container__content-preview",
2416
+ "data-testid": bodyTestId(node),
2417
+ children: node.content_blocks.map((block, index) => /* @__PURE__ */ jsx9(
2418
+ "div",
2419
+ {
2420
+ className: "lax-workflow-stage-container__teaser",
2421
+ children: block.preview
2422
+ },
2423
+ `${block.kind}-${index}`
2424
+ ))
2425
+ }
2426
+ ) : null,
2427
+ expanded && isRunning && runningLoopTree ? /* @__PURE__ */ jsx9(
2428
+ "div",
2429
+ {
2430
+ className: isItem ? "lax-workflow-parallel-item__body lax-workflow-stage-container__body" : "lax-workflow-stage-container__body",
2431
+ "data-testid": bodyTestId(node),
2432
+ children: /* @__PURE__ */ jsx9(
2433
+ AgentLoopView,
2434
+ {
2435
+ tree: runningLoopTree,
2436
+ showTaskListFooter: false,
2437
+ virtualized,
2438
+ virtualizeThreshold,
2439
+ groupParallelTools
2440
+ }
2441
+ )
2442
+ }
2443
+ ) : null,
2444
+ isDone && expanded ? loopTree ? /* @__PURE__ */ jsx9(
2445
+ WorkflowStageDoneBody,
2446
+ {
2447
+ node,
2448
+ loopTree,
2449
+ testId: bodyTestId(node),
2450
+ virtualized,
2451
+ virtualizeThreshold,
2452
+ groupParallelTools,
2453
+ bodyClassName: isItem ? "lax-workflow-parallel-item__body lax-workflow-stage-container__body" : "lax-workflow-stage-container__body"
2454
+ }
2455
+ ) : /* @__PURE__ */ jsxs5(
2456
+ "div",
2457
+ {
2458
+ className: "lax-workflow-stage-container__body lax-workflow-loop-replay-pending",
2459
+ "data-testid": bodyTestId(node),
2460
+ children: [
2461
+ teaserText ? /* @__PURE__ */ jsx9("p", { className: "lax-workflow-stage-container__teaser", children: teaserText }) : null,
2462
+ /* @__PURE__ */ jsx9("p", { className: "lax-workflow-loop-replay-pending__hint", children: replayHandler ? "Loading loop details\u2026" : "Loop details evicted from memory." })
2463
+ ]
2464
+ }
2465
+ ) : null
2466
+ ]
2467
+ }
2468
+ );
2469
+ }
2470
+
2471
+ // src/view/workflow/WorkflowVirtualRowList.tsx
2472
+ import { useRef as useRef3 } from "react";
2473
+ import { useVirtualizer } from "@tanstack/react-virtual";
2474
+ import { jsx as jsx10 } from "react/jsx-runtime";
2475
+ var DEFAULT_STAGE_ROW_ESTIMATE = 56;
2476
+ function WorkflowVirtualRowList({
2477
+ count,
2478
+ estimateSize = DEFAULT_STAGE_ROW_ESTIMATE,
2479
+ testId,
2480
+ className,
2481
+ renderRow
2482
+ }) {
2483
+ const parentRef = useRef3(null);
2484
+ const virtualizer = useVirtualizer({
2485
+ count,
2486
+ getScrollElement: () => parentRef.current,
2487
+ estimateSize: () => estimateSize,
2488
+ overscan: 8
2489
+ });
2490
+ if (count === 0) return null;
2491
+ return /* @__PURE__ */ jsx10(
2492
+ "div",
2493
+ {
2494
+ ref: parentRef,
2495
+ className: className ? `lax-workflow-virtual-list ${className}` : "lax-workflow-virtual-list",
2496
+ "data-testid": testId,
2497
+ children: /* @__PURE__ */ jsx10(
2498
+ "div",
2499
+ {
2500
+ className: "lax-workflow-virtual-list__inner",
2501
+ style: {
2502
+ height: `${virtualizer.getTotalSize()}px`,
2503
+ position: "relative",
2504
+ width: "100%"
2505
+ },
2506
+ children: virtualizer.getVirtualItems().map((virtualRow) => /* @__PURE__ */ jsx10(
2507
+ "div",
2508
+ {
2509
+ className: "lax-workflow-virtual-list__item",
2510
+ "data-index": virtualRow.index,
2511
+ ref: virtualizer.measureElement,
2512
+ style: {
2513
+ position: "absolute",
2514
+ top: 0,
2515
+ left: 0,
2516
+ width: "100%",
2517
+ transform: `translateY(${virtualRow.start}px)`
2518
+ },
2519
+ children: renderRow(virtualRow.index)
2520
+ },
2521
+ virtualRow.key
2522
+ ))
2523
+ }
2524
+ )
2525
+ }
2526
+ );
2527
+ }
2528
+
2529
+ // src/view/workflow/WorkflowParallelGroup.tsx
2530
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2531
+ function resolveLoopTree(node, loopTreesBySessionId) {
2532
+ if (!node.loopSessionId) return void 0;
2533
+ return loopTreesBySessionId[node.loopSessionId];
2534
+ }
2535
+ function WorkflowParallelGroup({
2536
+ groupKey,
2537
+ items,
2538
+ loopTreesBySessionId,
2539
+ depth = 1,
2540
+ virtualized,
2541
+ virtualizeThreshold,
2542
+ groupParallelTools
2543
+ }) {
2544
+ const scale = useWorkflowScaleOptions();
2545
+ const store = useWorkflowSessionStoreApi();
2546
+ const activeLoopSessionId = useStore3(store, (s) => s.state.activeLoopSessionId);
2547
+ const activeContainerIds = useStore3(store, (s) => s.state.containerTree.activeContainerIds);
2548
+ const metrics = useMemo4(() => computeParallelGroupMetrics(items), [items]);
2549
+ const summary = formatParallelGroupSummary(metrics);
2550
+ const hasRunning = metrics.running > 0;
2551
+ const hasActiveItem = useMemo4(
2552
+ () => items.some((item) => isActiveWorkflowContainer(item, activeLoopSessionId, activeContainerIds)),
2553
+ [items, activeLoopSessionId, activeContainerIds]
2554
+ );
2555
+ const allSettled = metrics.total > 0 && metrics.running === 0 && metrics.completed + metrics.failed >= metrics.total;
2556
+ const shouldAutoExpandGroup = hasRunning && (!scale.onlyExpandActiveRunning || hasActiveItem);
2557
+ const [expanded, setExpanded] = useState3(shouldAutoExpandGroup);
2558
+ useEffect5(() => {
2559
+ if (shouldAutoExpandGroup) {
2560
+ setExpanded(true);
2561
+ return;
2562
+ }
2563
+ if (hasRunning && scale.onlyExpandActiveRunning) {
2564
+ setExpanded(false);
2565
+ return;
2566
+ }
2567
+ if (allSettled) {
2568
+ setExpanded(false);
2569
+ }
2570
+ }, [shouldAutoExpandGroup, hasRunning, allSettled, scale.onlyExpandActiveRunning]);
2571
+ const toggleExpanded = useCallback3(() => {
2572
+ setExpanded((prev) => !prev);
2573
+ }, []);
2574
+ useEffect5(() => {
2575
+ const onKeyDown = (event) => {
2576
+ if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "o") return;
2577
+ setExpanded(true);
2578
+ };
2579
+ window.addEventListener("keydown", onKeyDown);
2580
+ return () => window.removeEventListener("keydown", onKeyDown);
2581
+ }, []);
2582
+ const useVirtualItems = items.length >= scale.parallelItemVirtualizeThreshold;
2583
+ const renderItem = (index) => {
2584
+ const item = items[index];
2585
+ return /* @__PURE__ */ jsx11(
2586
+ WorkflowStageContainer,
2587
+ {
2588
+ node: item,
2589
+ loopTree: resolveLoopTree(item, loopTreesBySessionId),
2590
+ depth,
2591
+ virtualized,
2592
+ virtualizeThreshold,
2593
+ groupParallelTools
2594
+ },
2595
+ item.container_id
2596
+ );
2597
+ };
2598
+ return /* @__PURE__ */ jsxs6(
2599
+ "div",
2600
+ {
2601
+ className: "lax-workflow-parallel-group",
2602
+ "data-testid": `lax-workflow-parallel-group-${groupKey}`,
2603
+ children: [
2604
+ /* @__PURE__ */ jsx11(
2605
+ WorkflowExpandTrigger,
2606
+ {
2607
+ summary,
2608
+ expandHint: !expanded && !hasRunning ? "(ctrl+o to expand)" : null,
2609
+ onToggle: toggleExpanded,
2610
+ expanded,
2611
+ testId: `lax-workflow-parallel-group-summary-${groupKey}`,
2612
+ className: "lax-workflow-parallel-group__summary"
2613
+ }
2614
+ ),
2615
+ expanded ? useVirtualItems ? /* @__PURE__ */ jsx11(
2616
+ WorkflowVirtualRowList,
2617
+ {
2618
+ count: items.length,
2619
+ testId: "lax-workflow-parallel-virtual-list",
2620
+ className: "lax-workflow-parallel-list lax-workflow-parallel-list--virtual",
2621
+ renderRow: renderItem
2622
+ }
2623
+ ) : /* @__PURE__ */ jsx11("div", { className: "lax-workflow-parallel-list", "data-testid": "lax-workflow-parallel-list", children: items.map((_, index) => renderItem(index)) }) : null
2624
+ ]
2625
+ }
2626
+ );
2627
+ }
2628
+
2629
+ // src/view/workflow/WorkflowRootContainer.tsx
2630
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2631
+ function WorkflowRootContainer({
2632
+ node,
2633
+ children
2634
+ }) {
2635
+ const { verbose } = useSessionViewOptions();
2636
+ const uiStatus = mapContainerUiStatus(node.status);
2637
+ const statusLabel = mapWorkflowRootStatusLabel(node.status);
2638
+ return /* @__PURE__ */ jsxs7("div", { className: "lax-workflow-root", "data-testid": "lax-workflow-root", children: [
2639
+ /* @__PURE__ */ jsx12(
2640
+ WorkflowContainerLine,
2641
+ {
2642
+ variant: "root",
2643
+ title: node.title,
2644
+ description: node.subtitle || void 0,
2645
+ uiStatus,
2646
+ meta: buildWorkflowRootMeta(node),
2647
+ statusLabel,
2648
+ titleTestId: "lax-workflow-root-title",
2649
+ descriptionTestId: "lax-workflow-root-desc",
2650
+ debugScopeKey: node.scope_key,
2651
+ verbose
2652
+ }
2653
+ ),
2654
+ children ? /* @__PURE__ */ jsx12("div", { className: "lax-workflow-container-tree", children }) : null
2655
+ ] });
2656
+ }
2657
+
2658
+ // src/view/workflow/WorkflowRouteContainer.tsx
2659
+ import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo5, useState as useState4 } from "react";
2660
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2661
+ function WorkflowRouteContainer({
2662
+ node,
2663
+ loopTree,
2664
+ depth = 1,
2665
+ virtualized,
2666
+ virtualizeThreshold,
2667
+ groupParallelTools
2668
+ }) {
2669
+ const { verbose } = useSessionViewOptions();
2670
+ const isSkipped = node.status === "skipped";
2671
+ const uiStatus = mapContainerUiStatus(node.status);
2672
+ const isRunning = !isSkipped && (node.status === "running" || node.status === "retrying");
2673
+ const isDone = !isSkipped && (node.status === "completed" || node.status === "failed");
2674
+ const testId = `lax-workflow-route-${node.scope_key}`;
2675
+ const doneSummary = useMemo5(
2676
+ () => loopTree && isDone ? buildStageDoneSummary(loopTree) : null,
2677
+ [loopTree, isDone]
2678
+ );
2679
+ const staticContentTeaser = !loopTree && node.content_blocks.length > 0 ? node.content_blocks[node.content_blocks.length - 1].preview : "";
2680
+ const teaserText = doneSummary?.teaser || staticContentTeaser;
2681
+ const expandHint = doneSummary ? formatTeaserExpandHint(doneSummary.extraLines, verbose) : null;
2682
+ const doneLabel = doneSummary?.label ?? "done";
2683
+ const [expanded, setExpanded] = useState4(isRunning);
2684
+ useEffect6(() => {
2685
+ if (isSkipped) return;
2686
+ if (isRunning) setExpanded(true);
2687
+ if (isDone) setExpanded(false);
2688
+ }, [isRunning, isDone, isSkipped]);
2689
+ const toggleExpanded = useCallback4(() => {
2690
+ if (!isSkipped) setExpanded((prev) => !prev);
2691
+ }, [isSkipped]);
2692
+ const shellToggle = isSkipped ? void 0 : isRunning ? toggleExpanded : isDone && expanded ? toggleExpanded : void 0;
2693
+ const depthStyle = {
2694
+ "--lax-workflow-depth": depth,
2695
+ "--lax-workflow-indent-unit": workflowIndentUnit(depth)
2696
+ };
2697
+ return /* @__PURE__ */ jsxs8(
2698
+ "div",
2699
+ {
2700
+ className: `lax-workflow-route-branch lax-workflow-stage-container lax-workflow-stage-container--${uiStatus}${isSkipped ? " lax-workflow-route-branch--skipped" : ""}`,
2701
+ "data-testid": testId,
2702
+ "data-route-status": node.status,
2703
+ "data-container-scope": "branch",
2704
+ style: depthStyle,
2705
+ children: [
2706
+ /* @__PURE__ */ jsx13(
2707
+ WorkflowContainerLine,
2708
+ {
2709
+ title: node.title,
2710
+ description: node.subtitle || void 0,
2711
+ uiStatus,
2712
+ meta: buildRouteLineMeta(node),
2713
+ statusLabel: isSkipped ? "skipped" : mapContainerStatusLabel(node.status),
2714
+ onToggle: shellToggle,
2715
+ interactive: !isSkipped && isRunning,
2716
+ expanded,
2717
+ titleTestId: `${testId}-title`,
2718
+ descriptionTestId: `${testId}-desc`
2719
+ }
2720
+ ),
2721
+ !isSkipped && expanded && isRunning && loopTree ? /* @__PURE__ */ jsx13("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx13(
2722
+ AgentLoopView,
2723
+ {
2724
+ tree: loopTree,
2725
+ showTaskListFooter: false,
2726
+ virtualized,
2727
+ virtualizeThreshold,
2728
+ groupParallelTools
2729
+ }
2730
+ ) }) : null,
2731
+ !isSkipped && isDone && !expanded ? /* @__PURE__ */ jsx13(
2732
+ WorkflowExpandTrigger,
2733
+ {
2734
+ summary: `${node.title} \xB7 ${doneLabel}`,
2735
+ teaser: teaserText || void 0,
2736
+ expandHint,
2737
+ onToggle: () => setExpanded(true),
2738
+ expanded: false,
2739
+ testId: `${testId}-done`,
2740
+ teaserTestId: teaserText ? `${testId}-teaser` : void 0
2741
+ }
2742
+ ) : null,
2743
+ !isSkipped && isDone && expanded ? /* @__PURE__ */ jsx13(
2744
+ WorkflowStageDoneBody,
2745
+ {
2746
+ node,
2747
+ loopTree,
2748
+ testId: `${testId}-body`,
2749
+ virtualized,
2750
+ virtualizeThreshold,
2751
+ groupParallelTools
2752
+ }
2753
+ ) : null
2754
+ ]
2755
+ }
2756
+ );
2757
+ }
2758
+
2759
+ // src/view/workflow/WorkflowSubworkflowContainer.tsx
2760
+ import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo6, useState as useState5 } from "react";
2761
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
2762
+ function buildDoneSummary(node) {
2763
+ const display = node.display;
2764
+ if (display?.progress_total != null) {
2765
+ const total = display.progress_total;
2766
+ const current = node.status === "completed" ? total : display.progress_current ?? total;
2767
+ return `${current}/${total} \xB7 done`;
2768
+ }
2769
+ return "done";
2770
+ }
2771
+ function WorkflowSubworkflowContainer({
2772
+ node,
2773
+ depth = 1,
2774
+ children
2775
+ }) {
2776
+ const { verbose } = useSessionViewOptions();
2777
+ const uiStatus = mapContainerUiStatus(node.status);
2778
+ const isRunning = node.status === "running" || node.status === "retrying";
2779
+ const isDone = node.status === "completed" || node.status === "failed";
2780
+ const testId = `lax-workflow-subworkflow-${node.scope_key}`;
2781
+ const teaser = useMemo6(() => {
2782
+ if (node.content_blocks.length === 0) return "";
2783
+ return node.content_blocks[node.content_blocks.length - 1]?.preview ?? "";
2784
+ }, [node.content_blocks]);
2785
+ const [expanded, setExpanded] = useState5(isRunning);
2786
+ useEffect7(() => {
2787
+ if (isRunning) setExpanded(true);
2788
+ if (isDone) setExpanded(false);
2789
+ }, [isRunning, isDone]);
2790
+ const toggleExpanded = useCallback5(() => {
2791
+ setExpanded((prev) => !prev);
2792
+ }, []);
2793
+ const shellToggle = isRunning ? toggleExpanded : isDone && expanded ? toggleExpanded : void 0;
2794
+ const depthStyle = {
2795
+ "--lax-workflow-depth": depth,
2796
+ "--lax-workflow-indent-unit": workflowIndentUnit(depth)
2797
+ };
2798
+ const showShellLine = !(isDone && !expanded);
2799
+ return /* @__PURE__ */ jsxs9(
2800
+ "div",
2801
+ {
2802
+ className: "lax-workflow-subworkflow",
2803
+ "data-testid": testId,
2804
+ "data-container-scope": "subworkflow",
2805
+ style: depthStyle,
2806
+ children: [
2807
+ showShellLine ? /* @__PURE__ */ jsx14(
2808
+ WorkflowContainerLine,
2809
+ {
2810
+ variant: "root",
2811
+ title: node.title,
2812
+ description: node.subtitle || void 0,
2813
+ uiStatus,
2814
+ meta: buildWorkflowRootMeta(node),
2815
+ statusLabel: mapWorkflowRootStatusLabel(node.status),
2816
+ onToggle: shellToggle,
2817
+ interactive: isRunning,
2818
+ expanded,
2819
+ titleTestId: `${testId}-title`,
2820
+ descriptionTestId: `${testId}-desc`,
2821
+ debugScopeKey: node.scope_key,
2822
+ verbose
2823
+ }
2824
+ ) : null,
2825
+ expanded && children ? /* @__PURE__ */ jsx14(
2826
+ "div",
2827
+ {
2828
+ className: "lax-workflow-subworkflow-boundary",
2829
+ "data-testid": `${testId}-boundary`,
2830
+ style: depthStyle,
2831
+ children
2832
+ }
2833
+ ) : null,
2834
+ isDone && !expanded ? /* @__PURE__ */ jsx14(
2835
+ WorkflowExpandTrigger,
2836
+ {
2837
+ summary: `${node.title} \xB7 ${buildDoneSummary(node)}`,
2838
+ summaryTestId: `${testId}-title`,
2839
+ teaser: teaser || void 0,
2840
+ onToggle: () => setExpanded(true),
2841
+ expanded: false,
2842
+ testId: `${testId}-done`,
2843
+ teaserTestId: teaser ? `${testId}-teaser` : void 0
2844
+ }
2845
+ ) : null
2846
+ ]
2847
+ }
2848
+ );
2849
+ }
2850
+
2851
+ // src/view/workflow/WorkflowChrome.tsx
2852
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2853
+ function collectChildContainers(containersById, parentId) {
2854
+ return Object.values(containersById).filter((node) => node.parent_container_id === parentId).sort((a, b) => a.scope_key.localeCompare(b.scope_key));
2855
+ }
2856
+ function resolveLoopTree2(node, loopTreesBySessionId) {
2857
+ if (!node.loopSessionId) return void 0;
2858
+ return loopTreesBySessionId[node.loopSessionId];
2859
+ }
2860
+ function collectSiblingItems(node, containersById) {
2861
+ if (!node.parent_container_id) return [];
2862
+ return collectChildContainers(containersById, node.parent_container_id).filter(
2863
+ (child) => child.scope === "item"
2864
+ );
2865
+ }
2866
+ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps) {
2867
+ const loopTree = resolveLoopTree2(node, loopTreesBySessionId);
2868
+ switch (node.scope) {
2869
+ case "aggregate":
2870
+ return /* @__PURE__ */ jsx15(
2871
+ WorkflowAggregateContainer,
2872
+ {
2873
+ node,
2874
+ loopTree,
2875
+ siblingItems: collectSiblingItems(node, containerTree.containersById),
2876
+ depth,
2877
+ ...viewProps
2878
+ },
2879
+ node.container_id
2880
+ );
2881
+ case "branch":
2882
+ return /* @__PURE__ */ jsx15(
2883
+ WorkflowRouteContainer,
2884
+ {
2885
+ node,
2886
+ loopTree,
2887
+ depth,
2888
+ ...viewProps
2889
+ },
2890
+ node.container_id
2891
+ );
2892
+ default:
2893
+ return /* @__PURE__ */ jsx15(
2894
+ WorkflowStageContainer,
2895
+ {
2896
+ node,
2897
+ loopTree,
2898
+ depth,
2899
+ ...viewProps
2900
+ },
2901
+ node.container_id
2902
+ );
2903
+ }
2904
+ }
2905
+ function renderItemSiblings(items, groupKey, depth, containerTree, loopTreesBySessionId, viewProps) {
2906
+ if (items.length >= 2) {
2907
+ return /* @__PURE__ */ jsx15(
2908
+ WorkflowParallelGroup,
2909
+ {
2910
+ groupKey,
2911
+ items,
2912
+ loopTreesBySessionId,
2913
+ depth,
2914
+ ...viewProps
2915
+ }
2916
+ );
2917
+ }
2918
+ if (items.length === 1) {
2919
+ return renderLeafContainer(items[0], depth, containerTree, loopTreesBySessionId, viewProps);
2920
+ }
2921
+ return null;
2922
+ }
2923
+ function renderContainerChildren(parentId, containerTree, loopTreesBySessionId, viewProps, depth) {
2924
+ const children = collectChildContainers(containerTree.containersById, parentId);
2925
+ const items = children.filter((node) => node.scope === "item");
2926
+ const rest = children.filter((node) => node.scope !== "item");
2927
+ return /* @__PURE__ */ jsxs10(Fragment2, { children: [
2928
+ renderItemSiblings(items, parentId, depth, containerTree, loopTreesBySessionId, viewProps),
2929
+ rest.map((node) => renderContainerNode(node, containerTree, loopTreesBySessionId, viewProps, depth))
2930
+ ] });
2931
+ }
2932
+ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProps, depth) {
2933
+ const nested = renderContainerChildren(
2934
+ node.container_id,
2935
+ containerTree,
2936
+ loopTreesBySessionId,
2937
+ viewProps,
2938
+ depth + 1
2939
+ );
2940
+ const hasNested = collectChildContainers(containerTree.containersById, node.container_id).length > 0;
2941
+ if (node.scope === "subworkflow") {
2942
+ return /* @__PURE__ */ jsx15(
2943
+ WorkflowSubworkflowContainer,
2944
+ {
2945
+ node,
2946
+ depth,
2947
+ children: hasNested ? nested : null
2948
+ },
2949
+ node.container_id
2950
+ );
2951
+ }
2952
+ if (node.scope === "stage") {
2953
+ return /* @__PURE__ */ jsxs10(
2954
+ "div",
2955
+ {
2956
+ className: "lax-workflow-nested-block",
2957
+ "data-testid": `lax-workflow-nested-${node.scope}-${node.scope_key}`,
2958
+ children: [
2959
+ renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps),
2960
+ hasNested ? /* @__PURE__ */ jsx15("div", { className: "lax-workflow-container-children", children: nested }) : null
2961
+ ]
2962
+ },
2963
+ node.container_id
2964
+ );
2965
+ }
2966
+ if (node.scope === "aggregate" || node.scope === "branch") {
2967
+ return /* @__PURE__ */ jsxs10("div", { className: "lax-workflow-container-children", children: [
2968
+ renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps),
2969
+ hasNested ? nested : null
2970
+ ] }, node.container_id);
2971
+ }
2972
+ return renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, viewProps);
2973
+ }
2974
+ function renderWorkflowStageContent(node, containerTree, loopTreesBySessionId, viewProps) {
2975
+ return renderContainerNode(node, containerTree, loopTreesBySessionId, viewProps, 1);
2976
+ }
2977
+ function renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewProps, as = "li") {
2978
+ const className = "lax-workflow-stage-list__item";
2979
+ const content = renderWorkflowStageContent(
2980
+ node,
2981
+ containerTree,
2982
+ loopTreesBySessionId,
2983
+ viewProps
2984
+ );
2985
+ if (as === "div") {
2986
+ return /* @__PURE__ */ jsx15("div", { className, role: "listitem", children: content }, node.container_id);
2987
+ }
2988
+ return /* @__PURE__ */ jsx15("li", { className, children: content }, node.container_id);
2989
+ }
2990
+ function WorkflowStageListPanel({
2991
+ stages,
2992
+ containerTree,
2993
+ loopTreesBySessionId,
2994
+ viewProps
2995
+ }) {
2996
+ const scale = useWorkflowScaleOptions();
2997
+ const [showAllStages, setShowAllStages] = useState6(false);
2998
+ const partition = useMemo7(
2999
+ () => partitionWorkflowStages(stages, scale.stagePageSize, showAllStages),
3000
+ [stages, scale.stagePageSize, showAllStages]
3001
+ );
3002
+ const useVirtual = partition.visible.length >= scale.stageVirtualizeThreshold;
3003
+ if (partition.visible.length === 0) return null;
3004
+ const renderRow = (index) => renderWorkflowStageRow(
3005
+ partition.visible[index],
3006
+ containerTree,
3007
+ loopTreesBySessionId,
3008
+ viewProps,
3009
+ "div"
3010
+ );
3011
+ return /* @__PURE__ */ jsxs10(Fragment2, { children: [
3012
+ useVirtual ? /* @__PURE__ */ jsx15("div", { className: "lax-workflow-stage-list-virtual-host", role: "list", children: /* @__PURE__ */ jsx15(
3013
+ WorkflowVirtualRowList,
3014
+ {
3015
+ count: partition.visible.length,
3016
+ testId: "lax-workflow-stage-virtual-list",
3017
+ className: "lax-workflow-stage-list lax-workflow-stage-list--virtual",
3018
+ renderRow
3019
+ }
3020
+ ) }) : /* @__PURE__ */ jsx15("ol", { className: "lax-workflow-stage-list", "data-testid": "lax-workflow-stage-list", children: partition.visible.map(
3021
+ (node) => renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewProps)
3022
+ ) }),
3023
+ partition.hiddenCount > 0 ? /* @__PURE__ */ jsxs10(
3024
+ "button",
3025
+ {
3026
+ type: "button",
3027
+ className: "lax-workflow-stage-list__show-all",
3028
+ "data-testid": "lax-workflow-stage-show-all",
3029
+ onClick: () => setShowAllStages(true),
3030
+ children: [
3031
+ "+",
3032
+ partition.hiddenCount,
3033
+ " stages collapsed \xB7 Show all (",
3034
+ partition.total,
3035
+ ")"
3036
+ ]
3037
+ }
3038
+ ) : null,
3039
+ showAllStages && partition.hiddenCount === 0 && stages.length > scale.stagePageSize ? /* @__PURE__ */ jsx15(
3040
+ "button",
3041
+ {
3042
+ type: "button",
3043
+ className: "lax-workflow-stage-list__show-all",
3044
+ "data-testid": "lax-workflow-stage-show-less",
3045
+ onClick: () => setShowAllStages(false),
3046
+ children: "Show fewer stages"
3047
+ }
3048
+ ) : null
3049
+ ] });
3050
+ }
3051
+ function renderWorkflowChildren(workflowNode, containerTree, loopTreesBySessionId, viewProps) {
3052
+ const children = collectChildContainers(
3053
+ containerTree.containersById,
3054
+ workflowNode.container_id
3055
+ );
3056
+ const stages = children.filter((node) => node.scope === "stage");
3057
+ const items = children.filter((node) => node.scope === "item");
3058
+ const other = children.filter(
3059
+ (node) => node.scope !== "stage" && node.scope !== "item"
3060
+ );
3061
+ return /* @__PURE__ */ jsxs10(Fragment2, { children: [
3062
+ stages.length > 0 ? /* @__PURE__ */ jsx15(
3063
+ WorkflowStageListPanel,
3064
+ {
3065
+ stages,
3066
+ containerTree,
3067
+ loopTreesBySessionId,
3068
+ viewProps
3069
+ }
3070
+ ) : null,
3071
+ renderItemSiblings(items, workflowNode.container_id, 1, containerTree, loopTreesBySessionId, viewProps),
3072
+ other.map(
3073
+ (node) => renderContainerNode(node, containerTree, loopTreesBySessionId, viewProps, 1)
3074
+ )
3075
+ ] });
3076
+ }
3077
+ function WorkflowChrome({
3078
+ containerTree,
3079
+ loopTreesBySessionId = {},
3080
+ virtualized,
3081
+ virtualizeThreshold,
3082
+ groupParallelTools
3083
+ }) {
3084
+ const viewProps = { virtualized, virtualizeThreshold, groupParallelTools };
3085
+ const rootNodes = containerTree.rootContainerIds.map((id) => containerTree.containersById[id]).filter((node) => node != null);
3086
+ if (rootNodes.length === 0) {
3087
+ return /* @__PURE__ */ jsx15("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: /* @__PURE__ */ jsx15(
3088
+ WorkflowContainerLine,
3089
+ {
3090
+ variant: "root",
3091
+ title: "Workflow",
3092
+ uiStatus: "pending",
3093
+ statusLabel: "pending"
3094
+ }
3095
+ ) });
3096
+ }
3097
+ return /* @__PURE__ */ jsx15("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: rootNodes.map((workflowNode) => /* @__PURE__ */ jsx15(WorkflowRootContainer, { node: workflowNode, children: renderWorkflowChildren(
3098
+ workflowNode,
3099
+ containerTree,
3100
+ loopTreesBySessionId,
3101
+ viewProps
3102
+ ) }, workflowNode.container_id)) });
3103
+ }
3104
+
3105
+ // src/view/workflow/WorkflowTaskListFooter.tsx
3106
+ import { useMemo as useMemo8, useState as useState7 } from "react";
3107
+ import { useStore as useStore4 } from "zustand";
3108
+
3109
+ // src/view/workflow/workflowTaskListMerge.ts
3110
+ var STATUS_ORDER = {
3111
+ in_progress: 0,
3112
+ pending: 1,
3113
+ completed: 2
3114
+ };
3115
+ function mergeWorkflowLoopTasks(loopTreesBySessionId) {
3116
+ const merged = [];
3117
+ const loopIds = Object.keys(loopTreesBySessionId).sort();
3118
+ for (const loopSessionId of loopIds) {
3119
+ const tree = loopTreesBySessionId[loopSessionId];
3120
+ if (!tree) continue;
3121
+ for (const task of Object.values(tree.tasks)) {
3122
+ merged.push({ ...task, loopSessionId });
3123
+ }
3124
+ }
3125
+ return merged.sort((a, b) => {
3126
+ const sa = STATUS_ORDER[a.status] ?? 99;
3127
+ const sb = STATUS_ORDER[b.status] ?? 99;
3128
+ if (sa !== sb) return sa - sb;
3129
+ const loopCmp = a.loopSessionId.localeCompare(b.loopSessionId);
3130
+ if (loopCmp !== 0) return loopCmp;
3131
+ return a.id.localeCompare(b.id);
3132
+ });
3133
+ }
3134
+ function deriveWorkflowTaskPhase(trees) {
3135
+ if (trees.length === 0) return "hidden";
3136
+ if (trees.some((tree) => tree.taskPhase === "visible")) return "visible";
3137
+ if (trees.every((tree) => tree.taskPhase === "hidden-after-completion")) {
3138
+ return "hidden-after-completion";
3139
+ }
3140
+ return "hidden";
3141
+ }
3142
+ function deriveWorkflowTaskFooterState(state) {
3143
+ const trees = Object.values(state.loopTreesBySessionId);
3144
+ return {
3145
+ tasks: mergeWorkflowLoopTasks(state.loopTreesBySessionId),
3146
+ taskPhase: deriveWorkflowTaskPhase(trees),
3147
+ hasEverShownTaskList: trees.some((tree) => tree.hasEverShownTaskList),
3148
+ sessionStatus: state.status
3149
+ };
3150
+ }
3151
+ function shouldShowWorkflowTaskListFooter(state) {
3152
+ const { tasks, taskPhase, hasEverShownTaskList, sessionStatus } = deriveWorkflowTaskFooterState(state);
3153
+ return shouldShowTaskListFooter(taskPhase, tasks, hasEverShownTaskList, sessionStatus);
3154
+ }
3155
+ function formatWorkflowTaskFooterLabel(state) {
3156
+ const { tasks } = deriveWorkflowTaskFooterState(state);
3157
+ return formatTaskFooterLabel(tasks);
3158
+ }
3159
+ function groupWorkflowTasksByLoop(tasks) {
3160
+ const groups = /* @__PURE__ */ new Map();
3161
+ for (const task of tasks) {
3162
+ const bucket = groups.get(task.loopSessionId) ?? [];
3163
+ bucket.push(task);
3164
+ groups.set(task.loopSessionId, bucket);
3165
+ }
3166
+ return Array.from(groups.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([loopSessionId, groupTasks]) => ({ loopSessionId, tasks: groupTasks }));
3167
+ }
3168
+ function loopSessionDisplayLabel(loopSessionId) {
3169
+ const marker = "-";
3170
+ const idx = loopSessionId.lastIndexOf(marker);
3171
+ if (idx !== -1 && idx < loopSessionId.length - 1) {
3172
+ return loopSessionId.slice(idx + 1);
3173
+ }
3174
+ return loopSessionId;
3175
+ }
3176
+
3177
+ // src/view/workflow/WorkflowTaskListFooter.tsx
3178
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
3179
+ function MessageResponse({ children }) {
3180
+ return /* @__PURE__ */ jsxs11("div", { className: "lax-message-response", children: [
3181
+ /* @__PURE__ */ jsx16("span", { className: "lax-message-response__marker", children: "\u23BF " }),
3182
+ /* @__PURE__ */ jsx16("span", { className: "lax-message-response__content", children })
3183
+ ] });
3184
+ }
3185
+ function WorkflowTaskListFooter() {
3186
+ const store = useWorkflowSessionStoreApi();
3187
+ const state = useStore4(store, (s) => s.state);
3188
+ const [expanded, setExpanded] = useState7(false);
3189
+ const footerState = useMemo8(() => deriveWorkflowTaskFooterState(state), [state]);
3190
+ const visible = shouldShowWorkflowTaskListFooter(state);
3191
+ if (!visible) {
3192
+ return null;
3193
+ }
3194
+ const label = formatWorkflowTaskFooterLabel(state);
3195
+ const groups = groupWorkflowTasksByLoop(footerState.tasks);
3196
+ return /* @__PURE__ */ jsxs11("div", { className: "lax-workflow-task-list-footer", "data-testid": "lax-workflow-task-list-footer", children: [
3197
+ /* @__PURE__ */ jsxs11(
3198
+ "button",
3199
+ {
3200
+ type: "button",
3201
+ className: "lax-task-list-footer__pill lax-workflow-task-list-footer__pill",
3202
+ "data-testid": "lax-workflow-task-list-footer-pill",
3203
+ "aria-expanded": expanded,
3204
+ onClick: () => setExpanded((v) => !v),
3205
+ children: [
3206
+ label,
3207
+ /* @__PURE__ */ jsx16("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
3208
+ ]
3209
+ }
3210
+ ),
3211
+ expanded ? /* @__PURE__ */ jsx16(MessageResponse, { children: /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs11(
3212
+ "div",
3213
+ {
3214
+ className: "lax-workflow-task-list-group",
3215
+ "data-testid": `lax-workflow-task-group-${group.loopSessionId}`,
3216
+ children: [
3217
+ /* @__PURE__ */ jsx16("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
3218
+ /* @__PURE__ */ jsx16(TaskList, { tasks: group.tasks })
3219
+ ]
3220
+ },
3221
+ group.loopSessionId
3222
+ )) }) }) : null
3223
+ ] });
3224
+ }
3225
+
3226
+ // src/view/workflow/WorkflowSession.tsx
3227
+ import { useStore as useStore5 } from "zustand";
3228
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
3229
+ function WorkflowSession({
248
3230
  source,
249
3231
  initialEvents,
250
3232
  registry,
251
- tierOverrides: _tierOverrides,
3233
+ tierOverrides,
252
3234
  onError,
253
- autoReconnect: _autoReconnect,
254
- debug = false,
255
- virtualized = false,
256
- virtualizeThreshold,
257
3235
  interactionBus,
258
3236
  toolDisplayRegistry,
259
3237
  defaultBodyMode = "preview",
260
- groupParallelTools = false,
261
3238
  permissionUiMode = "standalone",
262
3239
  markdownRenderer,
263
3240
  displayMode = "normal",
@@ -265,39 +3242,43 @@ function AgentSession({
265
3242
  exploreFullscreenBash = true,
266
3243
  memoryDir = null,
267
3244
  workspaceRoot = null,
3245
+ virtualized = false,
3246
+ virtualizeThreshold,
3247
+ groupParallelTools = false,
3248
+ workflowScale,
3249
+ onRequestLoopReplay,
268
3250
  children
269
3251
  }) {
270
- const storeRef = useRef(null);
3252
+ const storeRef = useRef4(null);
271
3253
  if (storeRef.current === null) {
272
- const replayOpts = {
273
- tierOverrides: _tierOverrides,
274
- collectDebug: debug
275
- };
276
- const initialTree = initialEvents && initialEvents.length > 0 ? replayEvents(initialEvents, replayOpts) : void 0;
277
- storeRef.current = createSessionStore(initialTree, {
278
- tierOverrides: _tierOverrides,
279
- debug,
280
- initialEventCount: initialEvents?.length ?? 0
3254
+ const initialState = initialEvents && initialEvents.length > 0 ? reduceWorkflowEvents(initialEvents, { tierOverrides }) : void 0;
3255
+ storeRef.current = createWorkflowSessionStore(initialState, {
3256
+ tierOverrides,
3257
+ initialEventCount: initialEvents?.length ?? 0,
3258
+ evictCompletedLoops: workflowScale?.evictCompletedLoops ?? DEFAULT_WORKFLOW_SCALE_OPTIONS.evictCompletedLoops
281
3259
  });
282
3260
  }
283
- const registryRef = useMemo(() => registry ?? createDefaultRegistry(), [registry]);
284
- const busRef = useMemo(
3261
+ const scaleOptions = useMemo9(
3262
+ () => ({ ...DEFAULT_WORKFLOW_SCALE_OPTIONS, ...workflowScale }),
3263
+ [workflowScale]
3264
+ );
3265
+ const registryRef = useMemo9(() => registry ?? createDefaultRegistry(), [registry]);
3266
+ const busRef = useMemo9(
285
3267
  () => interactionBus ?? getNoopInteractionBus(),
286
3268
  [interactionBus]
287
3269
  );
288
- const toolDisplayRef = useMemo(
3270
+ const toolDisplayRef = useMemo9(
289
3271
  () => ({
290
3272
  registry: toolDisplayRegistry ?? createDefaultToolRegistry(),
291
3273
  defaultBodyMode
292
3274
  }),
293
3275
  [toolDisplayRegistry, defaultBodyMode]
294
3276
  );
295
- const sessionViewRef = useMemo(
3277
+ const sessionViewRef = useMemo9(
296
3278
  () => ({
297
3279
  ...DEFAULT_SESSION_VIEW_OPTIONS,
298
3280
  groupParallelTools,
299
3281
  permissionUiMode,
300
- verboseReasoning: debug,
301
3282
  displayMode,
302
3283
  verbose,
303
3284
  exploreFullscreenBash,
@@ -307,7 +3288,6 @@ function AgentSession({
307
3288
  [
308
3289
  groupParallelTools,
309
3290
  permissionUiMode,
310
- debug,
311
3291
  displayMode,
312
3292
  verbose,
313
3293
  exploreFullscreenBash,
@@ -315,31 +3295,52 @@ function AgentSession({
315
3295
  workspaceRoot
316
3296
  ]
317
3297
  );
318
- useEffect(() => {
3298
+ const containerTree = useStore5(
3299
+ storeRef.current,
3300
+ (s) => s.state.containerTree
3301
+ );
3302
+ const loopTreesBySessionId = useStore5(
3303
+ storeRef.current,
3304
+ (s) => s.state.loopTreesBySessionId
3305
+ );
3306
+ useEffect8(() => {
319
3307
  const store = storeRef.current;
320
3308
  const controller = new AbortController();
321
3309
  void source.start((event, ctx) => {
322
3310
  store.getState().applyEvent(event, ctx);
323
3311
  }, controller.signal).catch((err) => {
324
- const error = err instanceof Error ? err : new Error(String(err));
325
- store.getState().markAsError(error);
326
- onError?.(error);
3312
+ if (err instanceof SseTransportTerminalError) {
3313
+ const { state } = store.getState();
3314
+ if (state.status === "running") {
3315
+ store.setState({ state: { ...state, status: "error" } });
3316
+ }
3317
+ }
327
3318
  });
328
3319
  return () => {
329
3320
  controller.abort();
330
3321
  };
331
3322
  }, [source, onError]);
332
- return /* @__PURE__ */ jsx(SessionStoreContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs("div", { className: "lax-agent-session", "data-testid": "lax-agent-session", children: [
333
- children ?? /* @__PURE__ */ jsx(
334
- SessionTimeline,
335
- {
336
- virtualized,
337
- virtualizeThreshold,
338
- groupParallelTools
339
- }
340
- ),
341
- debug ? /* @__PURE__ */ jsx(DebugPanel, {}) : null
342
- ] }) }) }) }) }) }) });
3323
+ return /* @__PURE__ */ jsx17(WorkflowSessionStoreContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx17(WorkflowScaleContext.Provider, { value: scaleOptions, children: /* @__PURE__ */ jsx17(WorkflowLoopReplayProvider, { onRequestLoopReplay, children: /* @__PURE__ */ jsx17(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx17(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx17(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx17(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx17(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs12(
3324
+ "div",
3325
+ {
3326
+ className: "lax-agent-session lax-workflow-session",
3327
+ "data-testid": "lax-workflow-session",
3328
+ children: [
3329
+ /* @__PURE__ */ jsx17(
3330
+ WorkflowChrome,
3331
+ {
3332
+ containerTree,
3333
+ loopTreesBySessionId,
3334
+ virtualized,
3335
+ virtualizeThreshold,
3336
+ groupParallelTools
3337
+ }
3338
+ ),
3339
+ /* @__PURE__ */ jsx17(WorkflowTaskListFooter, {}),
3340
+ children
3341
+ ]
3342
+ }
3343
+ ) }) }) }) }) }) }) }) });
343
3344
  }
344
3345
 
345
3346
  // src/view/tools/groupParallelTools.ts
@@ -379,202 +3380,21 @@ function buildTimelineEntries(rootIds, byId, options = {}) {
379
3380
  }
380
3381
 
381
3382
  // src/view/nodes/SubAgentNode.tsx
382
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
3383
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
383
3384
  function SubAgentBlock({ nodeId }) {
384
3385
  const node = useNodeTyped(nodeId, "subagent");
385
3386
  const childIds = useChildren(nodeId);
386
3387
  const registry = useNodeRegistry();
387
3388
  if (!node) return null;
388
- return /* @__PURE__ */ jsxs2("div", { className: "lax-subagent-block", "data-status": node.status, children: [
389
- /* @__PURE__ */ jsxs2("div", { className: "lax-subagent-block__header", children: [
3389
+ return /* @__PURE__ */ jsxs13("div", { className: "lax-subagent-block", "data-status": node.status, children: [
3390
+ /* @__PURE__ */ jsxs13("div", { className: "lax-subagent-block__header", children: [
390
3391
  "SubAgent: ",
391
3392
  node.subagentId
392
3393
  ] }),
393
- /* @__PURE__ */ jsx2("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx2(TimelineItem, { nodeId: childId, registry }, childId)) })
3394
+ /* @__PURE__ */ jsx18("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx18(TimelineItem, { nodeId: childId, registry }, childId)) })
394
3395
  ] });
395
3396
  }
396
3397
 
397
- // src/transport/createAgentxSseSource.ts
398
- var EXPECTED_PROTOCOL_VERSION = "1";
399
- function defaultOnError(error) {
400
- console.error("[langchain_agentx_stream_ui] SSE error:", error);
401
- }
402
- function buildResumeStreamUrl(baseUrl, lastEventId, queryParam = "last_event_id") {
403
- if (!lastEventId) return baseUrl;
404
- const sep = baseUrl.includes("?") ? "&" : "?";
405
- return `${baseUrl}${sep}${encodeURIComponent(queryParam)}=${encodeURIComponent(lastEventId)}`;
406
- }
407
- function createAgentxSseSource(options) {
408
- const {
409
- url,
410
- onError = defaultOnError,
411
- autoReconnect = true,
412
- reconnectDelayMs = 1e3,
413
- onLastEventId,
414
- EventSourceImpl = EventSource
415
- } = options;
416
- return {
417
- start(handler, signal) {
418
- return new Promise((resolve, reject) => {
419
- let es = null;
420
- let handshakeOk = false;
421
- let lastEventId = null;
422
- let fatal = false;
423
- let bufferedDelta = null;
424
- let flushTimer = null;
425
- const flushBufferedDelta = () => {
426
- if (flushTimer) {
427
- clearTimeout(flushTimer);
428
- flushTimer = null;
429
- }
430
- if (!bufferedDelta) return;
431
- handler(bufferedDelta.event, bufferedDelta.ctx);
432
- bufferedDelta = null;
433
- };
434
- const isBufferedDeltaEvent = (event) => event.event_type === "text-delta" || event.event_type === "reasoning-delta";
435
- const mergeDeltaEvent = (prev, next) => {
436
- if (prev.event_type !== next.event_type) return null;
437
- if (prev.step_index !== next.step_index) return null;
438
- if (prev.tool_name !== next.tool_name) return null;
439
- if (prev.session_id !== next.session_id) return null;
440
- const prevData = prev.data;
441
- const nextData = next.data;
442
- const prevChunk = prevData.delta ?? prevData.text ?? prevData.content ?? "";
443
- const nextChunk = nextData.delta ?? nextData.text ?? nextData.content ?? "";
444
- return {
445
- ...next,
446
- data: {
447
- ...next.data,
448
- text: `${prevChunk}${nextChunk}`
449
- }
450
- };
451
- };
452
- const cleanup = () => {
453
- es?.close();
454
- es = null;
455
- };
456
- const finish = (error) => {
457
- flushBufferedDelta();
458
- cleanup();
459
- if (error) reject(error);
460
- else resolve();
461
- };
462
- const connect = (resumeFromId) => {
463
- if (signal.aborted || fatal) {
464
- finish();
465
- return;
466
- }
467
- const connectUrl = resumeFromId != null && resumeFromId !== "" ? buildResumeStreamUrl(url, resumeFromId) : url;
468
- es = new EventSourceImpl(connectUrl);
469
- handshakeOk = false;
470
- es.addEventListener("meta", (event) => {
471
- try {
472
- const meta = JSON.parse(event.data);
473
- if (meta.protocol_version !== EXPECTED_PROTOCOL_VERSION) {
474
- fatal = true;
475
- const error = new Error(
476
- `Unsupported protocol_version: ${meta.protocol_version}`
477
- );
478
- onError(error);
479
- cleanup();
480
- finish(error);
481
- return;
482
- }
483
- handshakeOk = true;
484
- } catch (err) {
485
- fatal = true;
486
- const error = err instanceof Error ? err : new Error("Failed to decode meta frame");
487
- onError(error);
488
- cleanup();
489
- finish(error);
490
- }
491
- });
492
- es.addEventListener("agentx", (event) => {
493
- if (!handshakeOk) {
494
- fatal = true;
495
- const error = new Error("Received agentx frame before meta handshake");
496
- onError(error);
497
- cleanup();
498
- finish(error);
499
- return;
500
- }
501
- try {
502
- const agentEvent = JSON.parse(event.data);
503
- const sseEventId = event.lastEventId || void 0;
504
- if (sseEventId) {
505
- lastEventId = sseEventId;
506
- onLastEventId?.(sseEventId);
507
- }
508
- const ctx = sseEventId ? { sseEventId } : void 0;
509
- if (isBufferedDeltaEvent(agentEvent)) {
510
- if (bufferedDelta) {
511
- const merged = mergeDeltaEvent(bufferedDelta.event, agentEvent);
512
- if (merged) {
513
- bufferedDelta = { event: merged, ctx };
514
- } else {
515
- flushBufferedDelta();
516
- bufferedDelta = { event: agentEvent, ctx };
517
- }
518
- } else {
519
- bufferedDelta = { event: agentEvent, ctx };
520
- }
521
- if (!flushTimer) {
522
- flushTimer = setTimeout(() => {
523
- flushBufferedDelta();
524
- }, 16);
525
- }
526
- } else {
527
- flushBufferedDelta();
528
- handler(agentEvent, ctx);
529
- }
530
- } catch (err) {
531
- onError(
532
- new Error(
533
- err instanceof Error ? `Failed to decode agentx frame: ${err.message}` : "Failed to decode agentx frame"
534
- )
535
- );
536
- }
537
- });
538
- es.onerror = () => {
539
- flushBufferedDelta();
540
- if (signal.aborted || fatal) {
541
- cleanup();
542
- finish();
543
- return;
544
- }
545
- if (!autoReconnect) {
546
- fatal = true;
547
- const error = new Error("SSE connection error");
548
- onError(error);
549
- cleanup();
550
- finish(error);
551
- return;
552
- }
553
- cleanup();
554
- setTimeout(() => connect(lastEventId), reconnectDelayMs);
555
- };
556
- };
557
- signal.addEventListener("abort", () => {
558
- flushBufferedDelta();
559
- fatal = true;
560
- finish();
561
- }, { once: true });
562
- connect();
563
- });
564
- }
565
- };
566
- }
567
- function createMockSource(events) {
568
- return {
569
- async start(handler, signal) {
570
- for (const event of events) {
571
- if (signal.aborted) break;
572
- handler(event);
573
- }
574
- }
575
- };
576
- }
577
-
578
3398
  // src/transport/createReplaySource.ts
579
3399
  function sleep(ms, signal) {
580
3400
  if (ms <= 0) return Promise.resolve();
@@ -606,8 +3426,10 @@ function createReplaySource(events, opts) {
606
3426
  }
607
3427
  export {
608
3428
  Agent,
3429
+ AgentLoopView,
609
3430
  AgentSession,
610
3431
  AgentToolBody,
3432
+ ApplicationKindMismatchError,
611
3433
  AskUserQuestion,
612
3434
  AskUserQuestionToolBody,
613
3435
  Bash,
@@ -619,6 +3441,7 @@ export {
619
3441
  DEFAULT_EVENT_TIERS,
620
3442
  DEFAULT_SESSION_VIEW_OPTIONS,
621
3443
  DEFAULT_VIRTUALIZE_THRESHOLD,
3444
+ DEFAULT_WORKFLOW_SCALE_OPTIONS,
622
3445
  DefaultToolBody,
623
3446
  DiffView,
624
3447
  EXPECTED_PROTOCOL_VERSION,
@@ -635,19 +3458,25 @@ export {
635
3458
  MAX_PREVIEW_LINES,
636
3459
  MIN_HINT_DISPLAY_MS,
637
3460
  MarkdownBlock,
3461
+ MermaidDiagram,
3462
+ MissingApplicationKindError,
638
3463
  MultiAgentSession,
639
3464
  NodeRegistry,
640
3465
  PermissionCard,
3466
+ PlantUMLBlock,
3467
+ PlantUMLDiagram,
641
3468
  ProjectionContext,
642
3469
  Read,
643
3470
  ReadToolBody,
644
3471
  ReasoningBubble,
645
3472
  RenderableTimelineProjector,
3473
+ RichMarkdown,
646
3474
  SHELL_PROGRESS_MIN_ELAPSED_SECONDS,
647
3475
  SessionTimeline,
648
3476
  Skill,
649
3477
  SkillToolBody,
650
3478
  Spinner,
3479
+ SseTransportTerminalError,
651
3480
  StepCard,
652
3481
  SubAgentBlock,
653
3482
  SystemSummaryAccumulator,
@@ -663,10 +3492,16 @@ export {
663
3492
  TruncatedContent,
664
3493
  UnknownCard,
665
3494
  VirtualTimeline,
3495
+ WORKFLOW_STRUCTURE_EVENT_TYPES,
666
3496
  WebFetch,
667
3497
  WebFetchToolBody,
668
3498
  WebSearch,
669
3499
  WebSearchToolBody,
3500
+ WorkflowChrome,
3501
+ WorkflowLoopReplayProvider,
3502
+ WorkflowScaleContext,
3503
+ WorkflowSession,
3504
+ WorkflowTaskListFooter,
670
3505
  Write,
671
3506
  WriteToolBody,
672
3507
  buildBashBodyBlocks,
@@ -679,17 +3514,21 @@ export {
679
3514
  buildTimelineEntries,
680
3515
  classifyEvent,
681
3516
  commandAsHint,
3517
+ configureMermaid,
3518
+ configurePlantumlServer,
682
3519
  createActiveSessionBridge,
683
3520
  createAgentxSseSource,
684
3521
  createDefaultRegistry,
685
3522
  createDefaultToolRegistry,
686
3523
  createEmptyTree,
3524
+ createEmptyWorkflowSessionState,
687
3525
  createInteractionBus,
688
3526
  createMockSource,
689
3527
  createMultiSessionStore,
690
3528
  createProjectionContext,
691
3529
  createReplaySource,
692
3530
  createSessionStore,
3531
+ createWorkflowSessionStore,
693
3532
  displayPath,
694
3533
  entryKey,
695
3534
  formatAgentTitle,
@@ -708,12 +3547,17 @@ export {
708
3547
  isGitOperationCommand,
709
3548
  isGroupableToolName,
710
3549
  isMcpToolName,
3550
+ isWorkflowStructureEventType,
711
3551
  iterMountCandidates,
712
3552
  parseDiffText,
3553
+ partitionWorkflowStages,
713
3554
  projectCanonical,
714
3555
  projectTimeline,
3556
+ reduceAgentLoopEvent,
715
3557
  reduceEvents,
716
3558
  reduceTree,
3559
+ reduceWorkflowEvents,
3560
+ reduceWorkflowSession,
717
3561
  replayEvents,
718
3562
  replayTree,
719
3563
  resolveEffectiveToolBodyMode,
@@ -738,6 +3582,8 @@ export {
738
3582
  useSessionIds,
739
3583
  useSessionMeta,
740
3584
  useSessionStatus,
741
- useTimeline
3585
+ useTimeline,
3586
+ useWorkflowSessionStatus,
3587
+ useWorkflowSessionStoreApi
742
3588
  };
743
3589
  //# sourceMappingURL=index.js.map