langchain_agentx_stream_ui 0.1.0 → 0.1.2

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