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/{chunk-G2KYJCMM.js → chunk-3B4WC67E.js} +209 -83
- package/dist/chunk-3B4WC67E.js.map +1 -0
- package/dist/chunk-A4W6B7C3.js +1429 -0
- package/dist/chunk-A4W6B7C3.js.map +1 -0
- package/dist/default.css +822 -0
- package/dist/index.d.ts +427 -7
- package/dist/index.js +3072 -229
- package/dist/index.js.map +1 -1
- package/dist/{multi-session-CviqerRl.d.ts → multi-session-DAHwfTNb.d.ts} +52 -30
- package/dist/multi-session.d.ts +1 -1
- package/dist/multi-session.js +2 -2
- package/dist/virtual.d.ts +4 -11
- package/dist/virtual.js +1 -1
- package/package.json +14 -2
- package/dist/chunk-G2KYJCMM.js.map +0 -1
- package/dist/chunk-W6QQHTJ4.js +0 -785
- package/dist/chunk-W6QQHTJ4.js.map +0 -1
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-
|
|
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-
|
|
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__ */
|
|
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
|
|
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 =
|
|
3248
|
+
const storeRef = useRef3(null);
|
|
271
3249
|
if (storeRef.current === null) {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
284
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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__ */
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
|
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__ */
|
|
389
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|