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