langchain_agentx_stream_ui 0.3.7 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  fixDiagramSvg,
24
24
  markEventIdSeen,
25
25
  shouldSkipDuplicateEvent
26
- } from "./chunk-7QYE5PN5.js";
26
+ } from "./chunk-7X7IF2A4.js";
27
27
  import {
28
28
  DefaultToolBody,
29
29
  InteractionBusContext,
@@ -62,6 +62,7 @@ import {
62
62
  ToolDisplayOptionsContext,
63
63
  ToolProjectionClassifier,
64
64
  VirtualTimeline,
65
+ applyAuthoritySnapshotToState,
65
66
  buildCanonicalFromTree,
66
67
  buildProjectionContext,
67
68
  buildProjectionViewContext,
@@ -76,7 +77,10 @@ import {
76
77
  formatShellProgressSuffix,
77
78
  formatTaskFooterLabel,
78
79
  getChildIds,
80
+ getDisplayProjectionStatus,
79
81
  getMainStageIds,
82
+ isAuthorityTerminal,
83
+ isAuthorityTerminalStatus,
80
84
  isGitOperationCommand,
81
85
  isGroupableToolName,
82
86
  isMcpToolName,
@@ -92,11 +96,16 @@ import {
92
96
  resolveEventTier,
93
97
  resolveResponseId,
94
98
  resolveShellProgressFromPayload,
99
+ resolveWorkflowBanner,
95
100
  shouldApplyGrouping,
101
+ shouldShowAgentSpinner,
96
102
  shouldShowExploreDetail,
103
+ shouldShowGlobalSpinner,
97
104
  shouldShowTaskListFooter,
98
105
  streamChunk,
99
106
  useActiveSession,
107
+ useAgentAuthority,
108
+ useAgentAuthoritySubscribeFailed,
100
109
  useChildren,
101
110
  useInternalErrors,
102
111
  useMinDisplayTime,
@@ -110,7 +119,7 @@ import {
110
119
  useSessionStatus,
111
120
  useSessionViewOptions,
112
121
  useTimeline
113
- } from "./chunk-24FLS4TF.js";
122
+ } from "./chunk-AY7QLXFB.js";
114
123
  import {
115
124
  Agent,
116
125
  AgentToolBody,
@@ -157,1629 +166,1806 @@ import {
157
166
  } from "./chunk-4RIOBLGB.js";
158
167
 
159
168
  // src/view/AgentSession.tsx
160
- import { useEffect as useEffect2, useMemo, useRef as useRef2 } from "react";
169
+ import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "react";
161
170
 
162
171
  // src/core/store.ts
163
172
  import { createStore } from "zustand/vanilla";
164
- function safeReduce(tree, event, eventIndex, options) {
165
- try {
166
- return { tree: reduceTree(tree, event, eventIndex, options) };
167
- } catch (err) {
168
- const message = err instanceof Error ? err.message : String(err);
169
- const stack = err instanceof Error ? err.stack : void 0;
170
- if (import.meta.env?.DEV) {
171
- console.error("[langchain_agentx_stream_ui] reducer error:", err);
172
- }
173
- return {
174
- tree: {
175
- ...tree,
176
- internalErrors: [
177
- ...tree.internalErrors,
178
- { eventIndex, eventType: event.event_type, message, stack }
179
- ]
180
- }
181
- };
182
- }
183
- }
184
- function createSessionStore(initialTree = createEmptyTree(), storeOptions) {
185
- const reduceOpts = {
186
- tierOverrides: storeOptions?.tierOverrides,
187
- collectDebug: storeOptions?.debug === true,
188
- subagentLeakGate: storeOptions?.subagentLeakGate === true
173
+
174
+ // src/types/workflowContainer.ts
175
+ function createEmptyWorkflowContainerTreeState() {
176
+ return {
177
+ rootContainerIds: [],
178
+ activeContainerIds: [],
179
+ containersById: {}
189
180
  };
190
- const initialEventCount = storeOptions?.initialEventCount ?? 0;
191
- const seenEventIds = new Set(storeOptions?.initialSeenEventIds);
192
- return createStore((set, get) => ({
193
- tree: initialTree,
194
- eventCount: initialEventCount,
195
- applyEvent(event, ctx) {
196
- const sseEventId = ctx?.sseEventId;
197
- if (shouldSkipDuplicateEvent(seenEventIds, sseEventId)) return;
198
- const { tree, eventCount } = get();
199
- const { tree: reduced } = safeReduce(tree, event, eventCount, reduceOpts);
200
- const nextTree = sseEventId ? {
201
- ...reduced,
202
- meta: { ...reduced.meta, lastEventId: sseEventId }
203
- } : reduced;
204
- markEventIdSeen(seenEventIds, sseEventId);
205
- set({ tree: nextTree, eventCount: eventCount + 1 });
206
- },
207
- applyEvents(events) {
208
- let { tree, eventCount } = get();
209
- for (const event of events) {
210
- const result = safeReduce(tree, event, eventCount, reduceOpts);
211
- tree = result.tree;
212
- eventCount += 1;
213
- }
214
- set({ tree, eventCount });
215
- },
216
- reset() {
217
- seenEventIds.clear();
218
- set({ tree: createEmptyTree(), eventCount: 0 });
219
- },
220
- markAsError(error) {
221
- const { tree } = get();
222
- set({
223
- tree: {
224
- ...tree,
225
- status: "error",
226
- internalErrors: error ? [
227
- ...tree.internalErrors,
228
- {
229
- eventIndex: -1,
230
- eventType: "sse_connection_failed",
231
- message: error.message,
232
- stack: error.stack
233
- }
234
- ] : tree.internalErrors
235
- }
236
- });
237
- }
238
- }));
239
181
  }
240
-
241
- // src/transport/applicationKind.ts
242
- var ApplicationKindMismatchError = class extends Error {
243
- name = "ApplicationKindMismatchError";
244
- expected;
245
- actual;
246
- constructor(expected, actual) {
247
- super(
248
- `application_kind mismatch: expected ${expected}, got ${actual ?? "(missing)"}`
249
- );
250
- this.expected = expected;
251
- this.actual = actual;
252
- }
253
- };
254
- var MissingApplicationKindError = class extends Error {
255
- name = "MissingApplicationKindError";
256
- constructor() {
257
- super("application_kind missing in SSE meta frame");
258
- }
259
- };
260
-
261
- // src/transport/sseTransportPolicy.ts
262
- var BOUNDED_DEFAULTS = {
263
- maxReconnects: 5,
264
- reconnectTimeoutMs: 6e4,
265
- reconnectDelayMs: 1e3
266
- };
267
- var legacyAutoReconnectWarned = false;
268
- function warnLegacyAutoReconnectOnce() {
269
- if (legacyAutoReconnectWarned) return;
270
- if (typeof process !== "undefined" && process.env.NODE_ENV === "production") {
271
- return;
182
+ function containerTreeToSnapshot(tree) {
183
+ const containers = {};
184
+ for (const [id, node] of Object.entries(tree.containersById)) {
185
+ const { loopSessionId: _loop, ...projection } = node;
186
+ containers[id] = projection;
272
187
  }
273
- legacyAutoReconnectWarned = true;
274
- console.warn(
275
- "[langchain_agentx_stream_ui] autoReconnect is deprecated; use profile or policy.reconnect instead."
276
- );
188
+ return {
189
+ root_container_ids: [...tree.rootContainerIds],
190
+ active_container_ids: [...tree.activeContainerIds],
191
+ containers
192
+ };
277
193
  }
278
- var AGENT_TERMINAL = {
279
- primary: ["finish"],
280
- allowFinishFallback: false
281
- };
282
- var WORKFLOW_TERMINAL_STRICT = {
283
- primary: ["workflow-end", "workflow-failed"],
284
- allowFinishFallback: false
285
- };
286
- var WORKFLOW_TERMINAL_WITH_FALLBACK = {
287
- primary: ["workflow-end", "workflow-failed"],
288
- allowFinishFallback: true
289
- };
290
- var PROFILE_PRESETS = {
291
- oneshot: {
292
- profile: "oneshot",
293
- replay: "none",
294
- reconnect: {
295
- mode: "off",
296
- maxReconnects: 0,
297
- reconnectTimeoutMs: 0,
298
- reconnectDelayMs: BOUNDED_DEFAULTS.reconnectDelayMs
299
- },
300
- terminal: AGENT_TERMINAL
301
- },
302
- "oneshot-workflow": {
303
- profile: "oneshot-workflow",
304
- replay: "none",
305
- reconnect: {
306
- mode: "off",
307
- maxReconnects: 0,
308
- reconnectTimeoutMs: 0,
309
- reconnectDelayMs: BOUNDED_DEFAULTS.reconnectDelayMs
310
- },
311
- terminal: WORKFLOW_TERMINAL_WITH_FALLBACK
312
- },
313
- "replayable-workflow": {
314
- profile: "replayable-workflow",
315
- replay: "history",
316
- reconnect: {
317
- mode: "bounded",
318
- ...BOUNDED_DEFAULTS
319
- },
320
- terminal: WORKFLOW_TERMINAL_WITH_FALLBACK
321
- },
322
- "workflow-tail": {
323
- profile: "workflow-tail",
324
- replay: "none",
325
- reconnect: {
326
- mode: "bounded",
327
- ...BOUNDED_DEFAULTS
328
- },
329
- terminal: WORKFLOW_TERMINAL_WITH_FALLBACK
330
- }
331
- };
332
- function legacyTerminal(expectedApplicationKind) {
333
- if (expectedApplicationKind === "workflow") {
334
- return WORKFLOW_TERMINAL_STRICT;
335
- }
336
- return AGENT_TERMINAL;
194
+
195
+ // src/types/workflowSession.ts
196
+ function createEmptyWorkflowDisplayProjection() {
197
+ return {
198
+ streamEnded: false,
199
+ projectionStatus: "connecting",
200
+ terminalKind: null,
201
+ openChildIdentities: [],
202
+ awaitingAuthority: false,
203
+ authoritySyncFailed: false,
204
+ authoritySubscribeFailed: false
205
+ };
337
206
  }
338
- function legacyReconnect(autoReconnect, reconnectDelayMs) {
339
- if (autoReconnect === false) {
340
- return {
341
- mode: "off",
342
- maxReconnects: 0,
343
- reconnectTimeoutMs: 0,
344
- reconnectDelayMs: reconnectDelayMs ?? BOUNDED_DEFAULTS.reconnectDelayMs
345
- };
346
- }
207
+ function createEmptyWorkflowProgress() {
347
208
  return {
348
- mode: "unbounded",
349
- maxReconnects: Number.POSITIVE_INFINITY,
350
- reconnectTimeoutMs: Number.POSITIVE_INFINITY,
351
- reconnectDelayMs: reconnectDelayMs ?? BOUNDED_DEFAULTS.reconnectDelayMs
209
+ workflowId: null,
210
+ workflowPath: null,
211
+ status: "running",
212
+ stages: [],
213
+ parallelItems: {}
352
214
  };
353
215
  }
354
- function mergePolicy(base, override) {
355
- if (!override) return base;
216
+ function createEmptyWorkflowSessionState() {
356
217
  return {
357
- profile: base.profile,
358
- replay: override.replay ?? base.replay,
359
- reconnect: {
360
- ...base.reconnect,
361
- ...override.reconnect
362
- },
363
- terminal: {
364
- primary: override.terminal?.primary ?? base.terminal.primary,
365
- allowFinishFallback: override.terminal?.allowFinishFallback ?? base.terminal.allowFinishFallback
366
- }
218
+ authority: null,
219
+ display: createEmptyWorkflowDisplayProjection(),
220
+ meta: { workflowRunId: null, startedAt: null, lastEventId: null },
221
+ containerTree: createEmptyWorkflowContainerTreeState(),
222
+ workflowProgress: createEmptyWorkflowProgress(),
223
+ loopTreesBySessionId: {},
224
+ stageDoneSummariesByContainerId: {},
225
+ hotCompletedLoopSessionIds: [],
226
+ activeLoopSessionId: null,
227
+ projectionCursor: null,
228
+ snapshotVersion: null,
229
+ reconnectSuppressedReason: null,
230
+ snapshotHydrateFailed: false,
231
+ openScopeStack: [],
232
+ internalErrors: []
367
233
  };
368
234
  }
369
- function resolveSseTransportPolicy(input) {
370
- const {
371
- profile,
372
- policy: policyOverride,
373
- autoReconnect,
374
- reconnectDelayMs,
375
- expectedApplicationKind
376
- } = input;
377
- if (profile === void 0 && policyOverride === void 0 && autoReconnect !== void 0) {
378
- warnLegacyAutoReconnectOnce();
379
- }
380
- let base;
381
- if (profile) {
382
- base = { ...PROFILE_PRESETS[profile] };
383
- } else {
384
- base = {
385
- replay: "history",
386
- reconnect: legacyReconnect(autoReconnect, reconnectDelayMs),
387
- terminal: legacyTerminal(expectedApplicationKind)
388
- };
235
+
236
+ // src/types/workflowDisplay.ts
237
+ function readDisplayPayload(data) {
238
+ const display = data.display;
239
+ if (display != null && typeof display === "object" && !Array.isArray(display)) {
240
+ return display;
389
241
  }
390
- return mergePolicy(base, policyOverride);
242
+ return null;
391
243
  }
392
- function isSseStreamTerminalEvent(agentEvent, terminal, expectedApplicationKind) {
393
- const eventType = agentEvent.event_type;
394
- if (eventType === "error") return true;
395
- if (terminal.primary.includes(eventType)) return true;
396
- if (terminal.allowFinishFallback && eventType === "finish" && expectedApplicationKind === "workflow") {
397
- return true;
244
+
245
+ // src/core/workflow/workflowContainerProjector.ts
246
+ var OPEN_EVENT_TYPES = /* @__PURE__ */ new Set([
247
+ "workflow-start",
248
+ "subworkflow-start",
249
+ "stage-start",
250
+ "parallel-item-start",
251
+ "parallel-aggregate-start",
252
+ "route-branch-start"
253
+ ]);
254
+ var CLOSE_EVENT_TYPES = /* @__PURE__ */ new Set([
255
+ "workflow-end",
256
+ "workflow-failed",
257
+ "subworkflow-end",
258
+ "stage-done",
259
+ "stage-failed",
260
+ "parallel-item-done",
261
+ "parallel-item-failed",
262
+ "parallel-aggregate-done",
263
+ "parallel-aggregate-failed",
264
+ "route-branch-done",
265
+ "route-branch-failed"
266
+ ]);
267
+ var EVENT_TYPE_TO_SCOPE = {
268
+ "workflow-start": "workflow",
269
+ "workflow-end": "workflow",
270
+ "workflow-failed": "workflow",
271
+ "subworkflow-start": "subworkflow",
272
+ "subworkflow-end": "subworkflow",
273
+ "stage-start": "stage",
274
+ "stage-done": "stage",
275
+ "stage-failed": "stage",
276
+ "parallel-item-start": "item",
277
+ "parallel-item-done": "item",
278
+ "parallel-item-failed": "item",
279
+ "parallel-aggregate-start": "aggregate",
280
+ "parallel-aggregate-done": "aggregate",
281
+ "parallel-aggregate-failed": "aggregate",
282
+ "route-branch-start": "branch",
283
+ "route-branch-done": "branch",
284
+ "route-branch-failed": "branch"
285
+ };
286
+ var CONTENT_EVENT_TYPES = /* @__PURE__ */ new Set([
287
+ "reasoning-start",
288
+ "reasoning-delta",
289
+ "reasoning-end",
290
+ "text-start",
291
+ "text-delta",
292
+ "text-end",
293
+ "tool-input",
294
+ "tool-input-start",
295
+ "tool-input-delta",
296
+ "tool-call",
297
+ "tool-result",
298
+ "tool-error",
299
+ "tool-progress"
300
+ ]);
301
+ var SCOPE_PRIORITY = {
302
+ workflow: 0,
303
+ subworkflow: 1,
304
+ aggregate: 2,
305
+ stage: 3,
306
+ item: 3,
307
+ branch: 3
308
+ };
309
+ var FAILED_CLOSE_TYPES = /* @__PURE__ */ new Set([
310
+ "workflow-failed",
311
+ "stage-failed",
312
+ "parallel-item-failed",
313
+ "parallel-aggregate-failed",
314
+ "route-branch-failed"
315
+ ]);
316
+ function buildContainerId(workflowPath, scope, scopeKey) {
317
+ return `${workflowPath}|${scope}:${scopeKey}`;
318
+ }
319
+ function readString(data, key) {
320
+ const value = data[key];
321
+ return typeof value === "string" ? value : void 0;
322
+ }
323
+ function resolveScopeKey(eventType, data) {
324
+ if (eventType === "workflow-start" || eventType === "workflow-end" || eventType === "workflow-failed") {
325
+ const workflowId = readString(data, "workflow_id");
326
+ if (workflowId) return workflowId;
327
+ return readString(data, "workflow_path") ?? "workflow";
398
328
  }
399
- if (!profileUsesWorkflowTerminal(terminal) && expectedApplicationKind !== "workflow" && eventType === "finish") {
400
- return true;
329
+ if (eventType === "subworkflow-start" || eventType === "subworkflow-end") {
330
+ return readString(data, "child_workflow_id") ?? "subworkflow";
401
331
  }
402
- return false;
332
+ if (eventType === "stage-start" || eventType === "stage-done" || eventType === "stage-failed") {
333
+ const stageKey = readString(data, "stage_key");
334
+ if (stageKey) return stageKey;
335
+ const stageIndex = data.stage_index;
336
+ return typeof stageIndex === "number" ? String(stageIndex) : "stage";
337
+ }
338
+ if (eventType === "parallel-item-start" || eventType === "parallel-item-done" || eventType === "parallel-item-failed") {
339
+ const itemKey = readString(data, "item_key");
340
+ if (itemKey) return itemKey;
341
+ const itemIndex = data.item_index;
342
+ return typeof itemIndex === "number" ? String(itemIndex) : "item";
343
+ }
344
+ if (eventType === "parallel-aggregate-start" || eventType === "parallel-aggregate-done" || eventType === "parallel-aggregate-failed") {
345
+ return readString(data, "aggregate_key") ?? "aggregate";
346
+ }
347
+ if (eventType === "route-branch-start" || eventType === "route-branch-done" || eventType === "route-branch-failed") {
348
+ return readString(data, "branch_key") ?? readString(data, "branch_node") ?? "branch";
349
+ }
350
+ const display = readDisplayPayload(data);
351
+ if (display?.debug_key) return display.debug_key;
352
+ return "unknown";
403
353
  }
404
- function profileUsesWorkflowTerminal(terminal) {
405
- return terminal.primary.includes("workflow-end") || terminal.primary.includes("workflow-failed");
354
+ function displayStatus(data, fallback) {
355
+ const display = readDisplayPayload(data);
356
+ if (display?.status) return display.status;
357
+ return fallback;
406
358
  }
407
- var ReconnectBudget = class {
408
- constructor(maxReconnects, timeoutMs, startedAtMs = Date.now()) {
409
- this.maxReconnects = maxReconnects;
410
- this.timeoutMs = timeoutMs;
411
- this.startedAtMs = startedAtMs;
359
+ function closeStatus(eventType) {
360
+ return FAILED_CLOSE_TYPES.has(eventType) ? "failed" : "completed";
361
+ }
362
+ function readPreview(data, ...keys) {
363
+ for (const key of keys) {
364
+ const value = data[key];
365
+ if (typeof value === "string") return value;
412
366
  }
413
- maxReconnects;
414
- timeoutMs;
415
- startedAtMs;
416
- attemptCount = 0;
417
- get attempts() {
418
- return this.attemptCount;
367
+ return "";
368
+ }
369
+ function contentBlockFromEvent(event) {
370
+ const { event_type: eventType } = event;
371
+ const payload = event.data ?? {};
372
+ if (eventType === "reasoning-start" || eventType === "reasoning-delta" || eventType === "reasoning-end") {
373
+ let preview = readPreview(payload, "delta", "text", "reasoning");
374
+ if (!preview && eventType === "reasoning-start") preview = "Thinking\u2026";
375
+ return { kind: "reasoning", preview, tool_name: null };
419
376
  }
420
- canRetry(nowMs = Date.now()) {
421
- if (this.maxReconnects <= 0) return false;
422
- if (!Number.isFinite(this.maxReconnects) && !Number.isFinite(this.timeoutMs)) {
423
- return true;
424
- }
425
- if (Number.isFinite(this.maxReconnects) && this.attemptCount >= this.maxReconnects) {
426
- return false;
427
- }
428
- if (Number.isFinite(this.timeoutMs) && nowMs - this.startedAtMs > this.timeoutMs) {
429
- return false;
430
- }
431
- return true;
377
+ if (eventType === "text-start" || eventType === "text-delta" || eventType === "text-end") {
378
+ const preview = readPreview(payload, "delta", "text");
379
+ return { kind: "text", preview, tool_name: null };
432
380
  }
433
- recordAttempt() {
434
- this.attemptCount += 1;
381
+ if (CONTENT_EVENT_TYPES.has(eventType)) {
382
+ const toolName = event.tool_name ?? readString(payload, "tool_name");
383
+ const preview = readPreview(payload, "summary", "output", "input", "delta") || toolName || eventType;
384
+ return {
385
+ kind: "tool",
386
+ preview,
387
+ tool_name: toolName ?? null
388
+ };
435
389
  }
436
- };
437
- function shouldAutoReconnect(reconnect) {
438
- return reconnect.mode !== "off";
390
+ return null;
439
391
  }
440
-
441
- // src/transport/createAgentxSseSource.ts
442
- var EXPECTED_PROTOCOL_VERSION = "1";
443
- var SseTransportTerminalError = class extends Error {
444
- kind;
445
- constructor(kind, message) {
446
- super(message);
447
- this.name = "SseTransportTerminalError";
448
- this.kind = kind;
392
+ function wouldAssignParentCreateCycle(containersById, containerId, parentId) {
393
+ if (!parentId) return false;
394
+ if (parentId === containerId) return true;
395
+ const seen = /* @__PURE__ */ new Set();
396
+ let current = parentId;
397
+ while (current) {
398
+ if (current === containerId) return true;
399
+ if (seen.has(current)) return true;
400
+ seen.add(current);
401
+ current = containersById[current]?.parent_container_id ?? null;
449
402
  }
450
- };
451
- function transportLog(message, level = "warn") {
452
- const line = `[langchain_agentx_stream_ui] SSE transport: ${message}`;
453
- if (level === "error") {
454
- console.error(line);
455
- } else {
456
- console.warn(line);
403
+ return false;
404
+ }
405
+ function sanitizeContainerTreeParentLinks(containersById) {
406
+ const next = { ...containersById };
407
+ for (const [containerId, node] of Object.entries(next)) {
408
+ const seen = /* @__PURE__ */ new Set([containerId]);
409
+ let current = node.parent_container_id;
410
+ while (current) {
411
+ if (seen.has(current)) {
412
+ next[containerId] = { ...node, parent_container_id: null };
413
+ break;
414
+ }
415
+ seen.add(current);
416
+ current = next[current]?.parent_container_id ?? null;
417
+ }
457
418
  }
419
+ return next;
458
420
  }
459
- function buildResumeStreamUrl(baseUrl, lastEventId, queryParam = "last_event_id") {
460
- if (!lastEventId) return baseUrl;
461
- const sep = baseUrl.includes("?") ? "&" : "?";
462
- return `${baseUrl}${sep}${encodeURIComponent(queryParam)}=${encodeURIComponent(lastEventId)}`;
421
+ function normalizeSnapshotContainerContentBlocks(containersById) {
422
+ const next = {};
423
+ for (const [containerId, node] of Object.entries(containersById)) {
424
+ const loopSessionId = node.loopSessionId ?? null;
425
+ next[containerId] = {
426
+ ...node,
427
+ content_blocks: loopSessionId ? [] : node.content_blocks ?? []
428
+ };
429
+ }
430
+ return next;
463
431
  }
464
- function createAgentxSseSource(options) {
465
- const {
466
- url,
467
- profile,
468
- policy: policyOverride,
469
- autoReconnect,
470
- reconnectDelayMs,
471
- onLastEventId,
472
- EventSourceImpl = EventSource,
473
- expectedApplicationKind
474
- } = options;
475
- const transportPolicy = resolveSseTransportPolicy({
476
- profile,
477
- policy: policyOverride,
478
- autoReconnect,
479
- reconnectDelayMs,
480
- expectedApplicationKind
481
- });
482
- const reconnectDelay = transportPolicy.reconnect.reconnectDelayMs;
483
- const reconnectBudget = transportPolicy.reconnect.mode === "bounded" ? new ReconnectBudget(
484
- transportPolicy.reconnect.maxReconnects,
485
- transportPolicy.reconnect.reconnectTimeoutMs
486
- ) : null;
487
- return {
488
- start(handler, signal) {
489
- return new Promise((resolve, reject) => {
490
- let es = null;
491
- let handshakeOk = false;
492
- let lastEventId = null;
493
- let fatal = false;
494
- let streamTerminal = false;
495
- let bufferedDelta = null;
496
- let flushTimer = null;
497
- const flushBufferedDelta = () => {
498
- if (flushTimer) {
499
- clearTimeout(flushTimer);
500
- flushTimer = null;
501
- }
502
- if (!bufferedDelta) return;
503
- handler(bufferedDelta.event, bufferedDelta.ctx);
504
- bufferedDelta = null;
505
- };
506
- const isBufferedDeltaEvent = (event) => event.event_type === "text-delta" || event.event_type === "reasoning-delta";
507
- const mergeDeltaEvent = (prev, next) => {
508
- if (prev.event_type !== next.event_type) return null;
509
- if (prev.step_index !== next.step_index) return null;
510
- if (prev.tool_name !== next.tool_name) return null;
511
- if (prev.session_id !== next.session_id) return null;
512
- const prevData = prev.data;
513
- const nextData = next.data;
514
- const prevChunk = prevData.delta ?? prevData.text ?? prevData.content ?? "";
515
- const nextChunk = nextData.delta ?? nextData.text ?? nextData.content ?? "";
516
- return {
517
- ...next,
518
- data: {
519
- ...next.data,
520
- text: `${prevChunk}${nextChunk}`
521
- }
522
- };
523
- };
524
- const cleanup = () => {
525
- es?.close();
526
- es = null;
527
- };
528
- const finish = (error) => {
529
- flushBufferedDelta();
530
- cleanup();
531
- if (error) reject(error);
532
- else resolve();
533
- };
534
- const failHandshake = (error) => {
535
- fatal = true;
536
- transportLog(error.message, "error");
537
- cleanup();
538
- finish(error);
539
- };
540
- const failPrematureClose = () => {
541
- const error = new SseTransportTerminalError(
542
- "premature_close",
543
- "SSE connection lost before stream finished"
544
- );
545
- transportLog(error.message, "error");
546
- cleanup();
547
- finish(error);
548
- };
549
- const connect = (resumeFromId) => {
550
- if (signal.aborted || fatal) {
551
- finish();
552
- return;
553
- }
554
- const connectUrl = resumeFromId != null && resumeFromId !== "" ? buildResumeStreamUrl(url, resumeFromId) : url;
555
- es = new EventSourceImpl(connectUrl);
556
- handshakeOk = false;
557
- es.addEventListener("meta", (event) => {
558
- try {
559
- const meta = JSON.parse(event.data);
560
- if (meta.protocol_version !== EXPECTED_PROTOCOL_VERSION) {
561
- failHandshake(
562
- new Error(`Unsupported protocol_version: ${meta.protocol_version}`)
563
- );
564
- return;
565
- }
566
- if (expectedApplicationKind) {
567
- const actual = meta.application_kind;
568
- if (actual === void 0 || actual === null) {
569
- failHandshake(new MissingApplicationKindError());
570
- return;
571
- }
572
- if (actual !== expectedApplicationKind) {
573
- failHandshake(
574
- new ApplicationKindMismatchError(expectedApplicationKind, actual)
575
- );
576
- return;
577
- }
578
- }
579
- handshakeOk = true;
580
- } catch (err) {
581
- const message = err instanceof Error ? err.message : "Failed to decode meta frame";
582
- failHandshake(new Error(message));
583
- }
584
- });
585
- es.addEventListener("agentx", (event) => {
586
- if (!handshakeOk) {
587
- failHandshake(new Error("Received agentx frame before meta handshake"));
588
- return;
589
- }
590
- try {
591
- const agentEvent = JSON.parse(event.data);
592
- const sseEventId = event.lastEventId || void 0;
593
- if (sseEventId) {
594
- lastEventId = sseEventId;
595
- onLastEventId?.(sseEventId);
596
- }
597
- const ctx = sseEventId ? { sseEventId } : void 0;
598
- if (isBufferedDeltaEvent(agentEvent)) {
599
- if (bufferedDelta) {
600
- const merged = mergeDeltaEvent(bufferedDelta.event, agentEvent);
601
- if (merged) {
602
- bufferedDelta = { event: merged, ctx };
603
- } else {
604
- flushBufferedDelta();
605
- bufferedDelta = { event: agentEvent, ctx };
606
- }
607
- } else {
608
- bufferedDelta = { event: agentEvent, ctx };
609
- }
610
- if (!flushTimer) {
611
- flushTimer = setTimeout(() => {
612
- flushBufferedDelta();
613
- }, 16);
614
- }
615
- } else {
616
- flushBufferedDelta();
617
- handler(agentEvent, ctx);
618
- if (isSseStreamTerminalEvent(
619
- agentEvent,
620
- transportPolicy.terminal,
621
- expectedApplicationKind
622
- )) {
623
- streamTerminal = true;
624
- }
625
- }
626
- } catch (err) {
627
- const message = err instanceof Error ? err.message : "Failed to decode agentx frame";
628
- transportLog(`Dropped agentx frame: ${message}`, "warn");
629
- }
630
- });
631
- es.onerror = () => {
632
- flushBufferedDelta();
633
- if (signal.aborted || fatal || streamTerminal) {
634
- cleanup();
635
- finish();
636
- return;
637
- }
638
- if (!handshakeOk) {
639
- const error = new SseTransportTerminalError(
640
- "handshake_failed",
641
- "SSE connection failed before meta handshake"
642
- );
643
- transportLog(error.message, "error");
644
- cleanup();
645
- finish(error);
646
- return;
647
- }
648
- if (shouldAutoReconnect(transportPolicy.reconnect)) {
649
- if (transportPolicy.reconnect.mode === "bounded" && reconnectBudget && !reconnectBudget.canRetry()) {
650
- const error = new SseTransportTerminalError(
651
- "reconnect_budget_exhausted",
652
- "SSE reconnect budget exhausted"
653
- );
654
- transportLog(error.message, "error");
655
- cleanup();
656
- finish(error);
657
- return;
658
- }
659
- if (reconnectBudget) {
660
- reconnectBudget.recordAttempt();
661
- }
662
- cleanup();
663
- setTimeout(() => connect(lastEventId), reconnectDelay);
664
- return;
665
- }
666
- failPrematureClose();
667
- };
668
- };
669
- signal.addEventListener("abort", () => {
670
- flushBufferedDelta();
671
- fatal = true;
672
- finish();
673
- }, { once: true });
674
- connect();
675
- });
676
- }
677
- };
678
- }
679
- function createMockSource(events) {
680
- return {
681
- async start(handler, signal) {
682
- for (const event of events) {
683
- if (signal.aborted) break;
684
- handler(event);
685
- }
686
- }
687
- };
688
- }
689
-
690
- // src/view/agentLoop/ScopedSessionStore.tsx
691
- import { useEffect, useRef } from "react";
692
- import { jsx } from "react/jsx-runtime";
693
- function ScopedSessionStoreProvider({
694
- tree,
695
- children
696
- }) {
697
- const storeRef = useRef(createSessionStore(tree, { initialEventCount: 1 }));
698
- useEffect(() => {
699
- storeRef.current.setState({ tree });
700
- }, [tree]);
701
- return /* @__PURE__ */ jsx(SessionStoreContext.Provider, { value: storeRef.current, children });
702
- }
703
-
704
- // src/view/agentLoop/AgentLoopView.tsx
705
- import { jsx as jsx2 } from "react/jsx-runtime";
706
- function AgentLoopViewBody({
707
- showTaskListFooter = true,
708
- virtualized,
709
- virtualizeThreshold,
710
- groupParallelTools
711
- }) {
712
- return /* @__PURE__ */ jsx2("div", { className: "lax-agent-loop-view", "data-testid": "lax-agent-loop-view", children: /* @__PURE__ */ jsx2(
713
- SessionTimeline,
714
- {
715
- virtualized,
716
- virtualizeThreshold,
717
- groupParallelTools,
718
- showTaskListFooter
719
- }
720
- ) });
721
- }
722
- function AgentLoopView({
723
- tree,
724
- showTaskListFooter = true,
725
- virtualized,
726
- virtualizeThreshold,
727
- groupParallelTools
728
- }) {
729
- if (tree) {
730
- return /* @__PURE__ */ jsx2(ScopedSessionStoreProvider, { tree, children: /* @__PURE__ */ jsx2(
731
- AgentLoopViewBody,
732
- {
733
- showTaskListFooter,
734
- virtualized,
735
- virtualizeThreshold,
736
- groupParallelTools
737
- }
738
- ) });
432
+ function resolveContentScopeKey(data) {
433
+ for (const keyName of ["stage_key", "item_key", "branch_key", "aggregate_key", "debug_key"]) {
434
+ const value = readString(data, keyName);
435
+ if (value) return value;
739
436
  }
740
- return /* @__PURE__ */ jsx2(
741
- AgentLoopViewBody,
742
- {
743
- showTaskListFooter,
744
- virtualized,
745
- virtualizeThreshold,
746
- groupParallelTools
747
- }
748
- );
749
- }
750
-
751
- // src/view/AgentSession.tsx
752
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
753
- function DebugPanel() {
754
- const status = useSessionStatus();
755
- const errors = useInternalErrors();
756
- if (errors.length === 0) return null;
757
- return /* @__PURE__ */ jsxs("div", { className: "lax-debug-panel", "data-testid": "lax-debug-panel", children: [
758
- /* @__PURE__ */ jsxs("div", { children: [
759
- "status: ",
760
- status
761
- ] }),
762
- /* @__PURE__ */ jsx3("ul", { children: errors.map((err, i) => /* @__PURE__ */ jsxs("li", { children: [
763
- "[",
764
- err.eventType,
765
- "] ",
766
- err.message
767
- ] }, `${err.eventIndex}-${i}`)) })
768
- ] });
437
+ const display = readDisplayPayload(data);
438
+ return display?.debug_key ?? null;
769
439
  }
770
- function AgentSession({
771
- source,
772
- initialEvents,
773
- registry,
774
- tierOverrides: _tierOverrides,
775
- onError,
776
- autoReconnect: _autoReconnect,
777
- debug = false,
778
- virtualized = false,
779
- virtualizeThreshold,
780
- interactionBus,
781
- toolDisplayRegistry,
782
- defaultBodyMode = "preview",
783
- groupParallelTools = false,
784
- permissionUiMode = "standalone",
785
- markdownRenderer,
786
- displayMode = "normal",
787
- verbose = false,
788
- exploreFullscreenBash = true,
789
- memoryDir = null,
790
- workspaceRoot = null,
791
- subagentLeakGate = false,
792
- children
793
- }) {
794
- const storeRef = useRef2(null);
795
- if (storeRef.current === null) {
796
- const replayOpts = {
797
- tierOverrides: _tierOverrides,
798
- collectDebug: debug,
799
- subagentLeakGate
800
- };
801
- const initialTree = initialEvents && initialEvents.length > 0 ? replayEvents(initialEvents, replayOpts) : void 0;
802
- storeRef.current = createSessionStore(initialTree, {
803
- tierOverrides: _tierOverrides,
804
- debug,
805
- initialEventCount: initialEvents?.length ?? 0,
806
- subagentLeakGate
807
- });
440
+ var WorkflowContainerActiveSet = class {
441
+ openOrder = [];
442
+ nodes = {};
443
+ restoreNode(node) {
444
+ this.nodes[node.container_id] = { ...node };
445
+ if (node.is_open && !this.openOrder.includes(node.container_id)) {
446
+ this.openOrder.push(node.container_id);
447
+ }
808
448
  }
809
- const registryRef = useMemo(() => registry ?? createDefaultRegistry(), [registry]);
810
- const busRef = useMemo(
811
- () => interactionBus ?? getNoopInteractionBus(),
812
- [interactionBus]
813
- );
814
- const toolDisplayRef = useMemo(
815
- () => ({
816
- registry: toolDisplayRegistry ?? createDefaultToolRegistry(),
817
- defaultBodyMode
818
- }),
819
- [toolDisplayRegistry, defaultBodyMode]
820
- );
821
- const sessionViewRef = useMemo(
822
- () => ({
823
- ...DEFAULT_SESSION_VIEW_OPTIONS,
824
- groupParallelTools,
825
- permissionUiMode,
826
- verboseReasoning: debug,
827
- displayMode,
828
- verbose,
829
- exploreFullscreenBash,
830
- memoryDir,
831
- workspaceRoot,
832
- subagentLeakGate
833
- }),
834
- [
835
- groupParallelTools,
836
- permissionUiMode,
837
- debug,
838
- displayMode,
839
- verbose,
840
- exploreFullscreenBash,
841
- memoryDir,
842
- workspaceRoot,
843
- subagentLeakGate
844
- ]
845
- );
846
- const onErrorRef = useRef2(onError);
847
- onErrorRef.current = onError;
848
- useEffect2(() => {
849
- const store = storeRef.current;
850
- const controller = new AbortController();
851
- void source.start((event, ctx) => {
852
- store.getState().applyEvent(event, ctx);
853
- }, controller.signal).catch((err) => {
854
- if (err instanceof SseTransportTerminalError) {
855
- const { tree } = store.getState();
856
- if (tree.status === "running" || tree.status === "connecting") {
857
- store.setState({ tree: { ...tree, status: "error" } });
449
+ open(node) {
450
+ this.nodes[node.container_id] = node;
451
+ if (!this.openOrder.includes(node.container_id)) {
452
+ this.openOrder.push(node.container_id);
453
+ }
454
+ }
455
+ close(containerId, status) {
456
+ const node = this.nodes[containerId];
457
+ if (!node) return;
458
+ node.status = status;
459
+ node.is_open = false;
460
+ const idx = this.openOrder.indexOf(containerId);
461
+ if (idx !== -1) this.openOrder.splice(idx, 1);
462
+ }
463
+ get activeIds() {
464
+ return [...this.openOrder];
465
+ }
466
+ getNode(containerId) {
467
+ return this.nodes[containerId];
468
+ }
469
+ get nodesById() {
470
+ return this.nodes;
471
+ }
472
+ rankContainer(containerId) {
473
+ const node = this.nodes[containerId];
474
+ if (!node) return [-1, -1, -1, -1];
475
+ const openIndex = this.openOrder.indexOf(containerId);
476
+ const scopePriority = SCOPE_PRIORITY[node.scope] ?? 0;
477
+ return [node.workflow_depth, scopePriority, node.workflow_path.length, openIndex];
478
+ }
479
+ deepestActive() {
480
+ if (this.openOrder.length === 0) return null;
481
+ return this.openOrder.reduce(
482
+ (best, cid) => this.compareRank(cid, best) > 0 ? cid : best
483
+ );
484
+ }
485
+ resolveContentTarget(workflowPath, scopeKey) {
486
+ if (scopeKey) {
487
+ for (let i = this.openOrder.length - 1; i >= 0; i -= 1) {
488
+ const containerId = this.openOrder[i];
489
+ const node = this.nodes[containerId];
490
+ if (node.scope_key === scopeKey && node.is_open) {
491
+ if (workflowPath === null || node.workflow_path === workflowPath) {
492
+ return containerId;
493
+ }
858
494
  }
859
- onErrorRef.current?.(err);
860
- } else if (err instanceof Error) {
861
- onErrorRef.current?.(err);
862
495
  }
863
- });
864
- return () => {
865
- controller.abort();
866
- };
867
- }, [source]);
868
- 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: [
869
- children ?? /* @__PURE__ */ jsx3(
870
- AgentLoopView,
871
- {
872
- virtualized,
873
- virtualizeThreshold,
874
- groupParallelTools
496
+ }
497
+ if (workflowPath) {
498
+ const candidates = this.openOrder.filter((cid) => {
499
+ const node = this.nodes[cid];
500
+ if (!node.is_open) return false;
501
+ return node.workflow_path === workflowPath || workflowPath.startsWith(`${node.workflow_path}>`);
502
+ });
503
+ if (candidates.length > 0) {
504
+ return candidates.reduce(
505
+ (best, cid) => this.compareRank(cid, best) > 0 ? cid : best
506
+ );
875
507
  }
876
- ),
877
- debug ? /* @__PURE__ */ jsx3(DebugPanel, {}) : null
878
- ] }) }) }) }) }) }) });
879
- }
880
-
881
- // src/view/workflow/WorkflowSession.tsx
882
- import { useEffect as useEffect10, useMemo as useMemo9, useRef as useRef4 } from "react";
883
-
884
- // src/core/context/WorkflowSessionContext.tsx
885
- import { createContext, useContext } from "react";
886
- import { useStore } from "zustand";
887
-
888
- // src/core/workflow/workflowAuthoritySelectors.ts
889
- var AUTHORITY_TERMINAL = /* @__PURE__ */ new Set([
890
- "done",
891
- "error",
892
- "canceled",
893
- "partial_success"
894
- ]);
895
- function isAuthorityTerminalStatus(status) {
896
- return status != null && AUTHORITY_TERMINAL.has(status);
897
- }
898
- function isAuthorityTerminal(state) {
899
- return isAuthorityTerminalStatus(state.authority?.status);
900
- }
901
- function shouldShowGlobalSpinner(state) {
902
- const auth = state.authority?.status;
903
- if (auth === "canceled") return false;
904
- if (isAuthorityTerminalStatus(auth)) return false;
905
- if (state.display.awaitingAuthority) return false;
906
- if (auth == null || auth === "pending" || auth === "running") {
907
- return true;
508
+ }
509
+ const openNonWorkflow = this.openOrder.map((cid) => this.nodes[cid]).filter((node) => node.is_open && node.scope !== "workflow");
510
+ if (openNonWorkflow.length === 1) {
511
+ return openNonWorkflow[0].container_id;
512
+ }
513
+ return null;
908
514
  }
909
- const proj = state.display.projectionStatus;
910
- return proj === "running" || proj === "connecting";
911
- }
912
- function resolveWorkflowBanner(state) {
913
- if (state.display.authoritySubscribeFailed) {
914
- return { kind: "sync_failed", message: "\u72B6\u6001\u540C\u6B65\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" };
515
+ closeDescendantsOfPath(workflowPath, status) {
516
+ const toClose = this.openOrder.filter((cid) => {
517
+ const node = this.nodes[cid];
518
+ return node.workflow_path === workflowPath || node.workflow_path.startsWith(`${workflowPath}>`);
519
+ });
520
+ for (const containerId of toClose) {
521
+ this.close(containerId, status);
522
+ }
915
523
  }
916
- if (state.display.authoritySyncFailed) {
917
- return { kind: "sync_failed", message: "\u540C\u6B65\u72B6\u6001\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0" };
524
+ compareRank(a, b) {
525
+ const ra = this.rankContainer(a);
526
+ const rb = this.rankContainer(b);
527
+ for (let i = 0; i < ra.length; i += 1) {
528
+ if (ra[i] > rb[i]) return 1;
529
+ if (ra[i] < rb[i]) return -1;
530
+ }
531
+ return 0;
918
532
  }
919
- const auth = state.authority?.status;
920
- if (auth === "error") {
921
- return { kind: "authority_error", message: "Workflow \u6267\u884C\u5931\u8D25" };
533
+ /** StateMachine 比较容器深度 rank */
534
+ compareContainerRank(a, b) {
535
+ return this.compareRank(a, b);
922
536
  }
923
- if (state.snapshotHydrateFailed) {
924
- return {
925
- kind: "snapshot_unavailable",
926
- message: "\u65E0\u6CD5\u52A0\u8F7D projection snapshot\uFF0C\u5C55\u793A\u53EF\u80FD\u4E0D\u5B8C\u6574"
927
- };
537
+ };
538
+ var WorkflowContainerStateMachine = class {
539
+ active = new WorkflowContainerActiveSet();
540
+ rootIds = [];
541
+ suppressContentContainerIds = /* @__PURE__ */ new Set();
542
+ get activeSet() {
543
+ return this.active;
544
+ }
545
+ setSuppressContentContainerIds(ids) {
546
+ this.suppressContentContainerIds = ids;
547
+ }
548
+ rehydrate(snapshot) {
549
+ this.rootIds.length = 0;
550
+ this.rootIds.push(...snapshot.root_container_ids);
551
+ for (const node of Object.values(snapshot.containers)) {
552
+ this.active.restoreNode(node);
553
+ }
928
554
  }
929
- if (state.display.awaitingAuthority) {
930
- return { kind: "awaiting_authority", message: "\u6536\u5C3E\u4E2D\u2026" };
555
+ apply(event) {
556
+ const eventType = event.event_type;
557
+ const data = event.data ?? {};
558
+ if (OPEN_EVENT_TYPES.has(eventType)) {
559
+ this.openContainer(eventType, data);
560
+ return "open";
561
+ }
562
+ if (CLOSE_EVENT_TYPES.has(eventType)) {
563
+ this.closeContainer(eventType, data);
564
+ return "close";
565
+ }
566
+ if (CONTENT_EVENT_TYPES.has(eventType)) {
567
+ this.appendContent(event);
568
+ return "append-content";
569
+ }
570
+ return null;
931
571
  }
932
- if (auth === "partial_success") {
933
- const forced = state.display.terminalKind === "forced_closeout";
572
+ snapshot() {
934
573
  return {
935
- kind: "partial_success",
936
- message: forced ? "\u90E8\u5206\u5B8C\u6210\uFF08\u90E8\u5206\u5BB9\u5668\u672A\u6B63\u5E38\u7ED3\u675F\uFF09" : "\u90E8\u5206\u5B8C\u6210\uFF08\u6240\u6709\u5BB9\u5668\u6B63\u5E38\u7ED3\u675F\uFF09"
574
+ root_container_ids: [...this.rootIds],
575
+ containers: { ...this.active.nodesById },
576
+ active_container_ids: this.active.activeIds
937
577
  };
938
578
  }
939
- const meta = state.authority?.metadata;
940
- if (auth === "done" && meta?.projection_status === "degraded") {
941
- const dropped = meta.projection_dropped_count;
942
- const suffix = dropped != null && dropped > 0 ? `\uFF08\u7EA6 ${dropped} \u6761\u4E8B\u4EF6\u672A\u5B8C\u6574\u5C55\u793A\uFF09` : "";
943
- return {
944
- kind: "projection_degraded",
945
- message: `\u5C55\u793A\u53EF\u80FD\u4E0D\u5B8C\u6574${suffix}`
579
+ openContainer(eventType, data) {
580
+ const scope = EVENT_TYPE_TO_SCOPE[eventType];
581
+ if (!scope) return;
582
+ const workflowPath = readString(data, "workflow_path") ?? "";
583
+ const workflowDepth = typeof data.workflow_depth === "number" ? data.workflow_depth : 0;
584
+ const scopeKey = resolveScopeKey(eventType, data);
585
+ const containerId = buildContainerId(workflowPath, scope, scopeKey);
586
+ const display = readDisplayPayload(data);
587
+ const title = display?.title ?? scopeKey;
588
+ const subtitle = display?.description ?? "";
589
+ const pattern = display?.pattern ?? "";
590
+ const status = displayStatus(data, "running");
591
+ const existing = this.active.getNode(containerId);
592
+ const computedParentId = this.resolveParentId(workflowPath, scope);
593
+ let parentId = existing != null ? existing.parent_container_id : computedParentId;
594
+ if (wouldAssignParentCreateCycle(this.active.nodesById, containerId, parentId)) {
595
+ parentId = existing?.parent_container_id ?? null;
596
+ }
597
+ const node = {
598
+ container_id: containerId,
599
+ scope,
600
+ workflow_path: workflowPath,
601
+ workflow_depth: workflowDepth,
602
+ scope_key: scopeKey,
603
+ title,
604
+ subtitle,
605
+ status,
606
+ pattern,
607
+ parent_container_id: parentId,
608
+ display,
609
+ content_blocks: existing?.content_blocks ?? [],
610
+ is_open: true
946
611
  };
612
+ this.active.open(node);
613
+ if (scope === "workflow" && !this.rootIds.includes(containerId)) {
614
+ this.rootIds.push(containerId);
615
+ }
947
616
  }
948
- if (state.display.terminalKind === "forced_closeout" && auth === "done") {
949
- return { kind: "forced_closeout", message: "Workflow \u5DF2\u7ED3\u675F\uFF08\u90E8\u5206\u5BB9\u5668\u672A\u6B63\u5E38\u5173\u95ED\uFF09" };
950
- }
951
- return { kind: "none", message: "" };
952
- }
953
- function getDisplayProjectionStatus(state) {
954
- return state.display.projectionStatus;
955
- }
956
- function applyAuthoritySnapshotToState(state, snapshot) {
957
- return { ...state, authority: snapshot };
958
- }
959
-
960
- // src/core/context/WorkflowSessionContext.tsx
961
- var WorkflowSessionStoreContext = createContext(null);
962
- function useWorkflowSessionStoreApi() {
963
- const store = useContext(WorkflowSessionStoreContext);
964
- if (!store) {
965
- throw new Error("useWorkflowSessionStoreApi must be used within WorkflowSession");
966
- }
967
- return store;
968
- }
969
- function useWorkflowSessionStatus() {
970
- const store = useWorkflowSessionStoreApi();
971
- return useStore(store, (s) => getDisplayProjectionStatus(s.state));
972
- }
973
-
974
- // src/core/context/WorkflowLoopReplayContext.tsx
975
- import { createContext as createContext2, useContext as useContext2 } from "react";
976
- import { jsx as jsx4 } from "react/jsx-runtime";
977
- var WorkflowLoopReplayContext = createContext2(
978
- void 0
979
- );
980
- function WorkflowLoopReplayProvider({
981
- onRequestLoopReplay,
982
- children
983
- }) {
984
- return /* @__PURE__ */ jsx4(WorkflowLoopReplayContext.Provider, { value: onRequestLoopReplay, children });
985
- }
986
- function useWorkflowLoopReplayHandler() {
987
- return useContext2(WorkflowLoopReplayContext);
988
- }
989
-
990
- // src/core/context/WorkflowScaleContext.tsx
991
- import { createContext as createContext3, useContext as useContext3 } from "react";
992
-
993
- // src/view/workflow/workflowScaleOptions.ts
994
- var DEFAULT_WORKFLOW_SCALE_OPTIONS = {
995
- stageVirtualizeThreshold: 40,
996
- completedStagePageSize: 25,
997
- evictCompletedLoops: true,
998
- onlyExpandActiveRunning: true,
999
- parallelItemVirtualizeThreshold: 40,
1000
- maxHydratedCompletedLoops: 4
1001
- };
1002
-
1003
- // src/core/context/WorkflowScaleContext.tsx
1004
- var WorkflowScaleContext = createContext3(
1005
- DEFAULT_WORKFLOW_SCALE_OPTIONS
1006
- );
1007
- function useWorkflowScaleOptions() {
1008
- return useContext3(WorkflowScaleContext);
1009
- }
1010
-
1011
- // src/core/workflowSessionStore.ts
1012
- import { createStore as createStore2 } from "zustand/vanilla";
1013
-
1014
- // src/types/workflowContainer.ts
1015
- function createEmptyWorkflowContainerTreeState() {
1016
- return {
1017
- rootContainerIds: [],
1018
- activeContainerIds: [],
1019
- containersById: {}
1020
- };
1021
- }
1022
- function containerTreeToSnapshot(tree) {
1023
- const containers = {};
1024
- for (const [id, node] of Object.entries(tree.containersById)) {
1025
- const { loopSessionId: _loop, ...projection } = node;
1026
- containers[id] = projection;
617
+ closeContainer(eventType, data) {
618
+ const scope = EVENT_TYPE_TO_SCOPE[eventType];
619
+ if (!scope) return;
620
+ const workflowPath = readString(data, "workflow_path") ?? "";
621
+ const scopeKey = resolveScopeKey(eventType, data);
622
+ const containerId = buildContainerId(workflowPath, scope, scopeKey);
623
+ const status = displayStatus(data, closeStatus(eventType));
624
+ this.active.close(containerId, status);
625
+ if (scope === "workflow") {
626
+ this.active.closeDescendantsOfPath(workflowPath, status);
627
+ }
1027
628
  }
1028
- return {
1029
- root_container_ids: [...tree.rootContainerIds],
1030
- active_container_ids: [...tree.activeContainerIds],
1031
- containers
1032
- };
1033
- }
1034
-
1035
- // src/types/workflowSession.ts
1036
- function createEmptyWorkflowDisplayProjection() {
1037
- return {
1038
- streamEnded: false,
1039
- projectionStatus: "connecting",
1040
- terminalKind: null,
1041
- openChildIdentities: [],
1042
- awaitingAuthority: false,
1043
- authoritySyncFailed: false,
1044
- authoritySubscribeFailed: false
1045
- };
1046
- }
1047
- function createEmptyWorkflowProgress() {
1048
- return {
1049
- workflowId: null,
1050
- workflowPath: null,
1051
- status: "running",
1052
- stages: [],
1053
- parallelItems: {}
1054
- };
1055
- }
1056
- function createEmptyWorkflowSessionState() {
1057
- return {
1058
- authority: null,
1059
- display: createEmptyWorkflowDisplayProjection(),
1060
- meta: { workflowRunId: null, startedAt: null, lastEventId: null },
1061
- containerTree: createEmptyWorkflowContainerTreeState(),
1062
- workflowProgress: createEmptyWorkflowProgress(),
1063
- loopTreesBySessionId: {},
1064
- stageDoneSummariesByContainerId: {},
1065
- hotCompletedLoopSessionIds: [],
1066
- activeLoopSessionId: null,
1067
- projectionCursor: null,
1068
- snapshotVersion: null,
1069
- reconnectSuppressedReason: null,
1070
- snapshotHydrateFailed: false,
1071
- openScopeStack: [],
1072
- internalErrors: []
1073
- };
1074
- }
1075
-
1076
- // src/core/workflow/workflowLoopTreeUtils.ts
1077
- function isWorkflowLoopTreeActive(tree) {
1078
- return tree?.status === "running" || tree?.status === "connecting";
1079
- }
1080
- function createWorkflowRunningLoopTree(seedMs = Date.now()) {
1081
- const tree = createEmptyTree();
1082
- tree.status = "running";
1083
- tree.meta.startedAt = seedMs;
1084
- return tree;
1085
- }
1086
- function finalizeWorkflowLoopTree(tree, terminalStatus = "done") {
1087
- if (tree.status === "done" || tree.status === "error") {
1088
- return tree;
629
+ appendContent(event) {
630
+ const data = event.data ?? {};
631
+ const workflowPath = readString(data, "workflow_path") ?? null;
632
+ const scopeKey = resolveContentScopeKey(data);
633
+ const targetId = this.active.resolveContentTarget(workflowPath, scopeKey);
634
+ if (!targetId) return;
635
+ if (this.suppressContentContainerIds.has(targetId)) return;
636
+ const block = contentBlockFromEvent(event);
637
+ if (!block) return;
638
+ const node = this.active.getNode(targetId);
639
+ if (!node) return;
640
+ const last = node.content_blocks[node.content_blocks.length - 1];
641
+ if (last && last.kind === block.kind && (block.kind === "reasoning" || block.kind === "text")) {
642
+ if (block.preview) {
643
+ if (block.kind === "reasoning" && last.preview === "Thinking\u2026") {
644
+ last.preview = block.preview;
645
+ } else {
646
+ last.preview = `${last.preview}${block.preview}`;
647
+ }
648
+ }
649
+ return;
650
+ }
651
+ node.content_blocks.push(block);
1089
652
  }
1090
- const byId = { ...tree.byId };
1091
- for (const [id, node] of Object.entries(byId)) {
1092
- if (!node) continue;
1093
- if ((node.kind === "text" || node.kind === "reasoning") && node.status === "streaming") {
1094
- byId[id] = { ...node, status: "done" };
653
+ resolveParentId(workflowPath, scope) {
654
+ if (scope === "workflow") return null;
655
+ const openNodes = this.active.activeIds.map((cid) => this.active.getNode(cid)).filter((node) => node.is_open);
656
+ if (openNodes.length === 0) return null;
657
+ const prefixCandidates = openNodes.filter(
658
+ (node) => workflowPath === node.workflow_path || workflowPath.startsWith(`${node.workflow_path}>`)
659
+ );
660
+ if (prefixCandidates.length > 0) {
661
+ const parentCandidates = prefixCandidates.filter((node) => {
662
+ if (scope === "item") return node.scope !== "item";
663
+ if (scope === "aggregate") return node.scope !== "item" && node.scope !== "aggregate";
664
+ return node.scope !== scope;
665
+ });
666
+ if (parentCandidates.length > 0) {
667
+ const parent = parentCandidates.reduce(
668
+ (best, node) => this.active.compareContainerRank(node.container_id, best.container_id) > 0 ? node : best
669
+ );
670
+ if (parent.workflow_path === workflowPath && parent.scope === "workflow") {
671
+ return parent.container_id;
672
+ }
673
+ if (workflowPath.startsWith(`${parent.workflow_path}>`)) {
674
+ return parent.container_id;
675
+ }
676
+ if (parent.workflow_path === workflowPath && parent.scope !== scope) {
677
+ return parent.container_id;
678
+ }
679
+ }
1095
680
  }
1096
- if (node.kind === "tool_call" && node.status === "running") {
1097
- byId[id] = {
1098
- ...node,
1099
- status: terminalStatus === "error" ? "failed" : "done"
1100
- };
681
+ const workflowCandidates = openNodes.filter((node) => node.scope === "workflow");
682
+ if (workflowCandidates.length > 0) {
683
+ return workflowCandidates.reduce(
684
+ (best, node) => this.active.compareContainerRank(node.container_id, best.container_id) > 0 ? node : best
685
+ ).container_id;
1101
686
  }
687
+ return null;
1102
688
  }
1103
- return {
1104
- ...tree,
1105
- byId,
1106
- activeRoundId: null,
1107
- status: terminalStatus
1108
- };
1109
- }
1110
-
1111
- // src/core/workflow/stageDoneSummary.ts
1112
- function countToolUses(tree) {
1113
- return Object.values(tree.byId).filter((node) => node.kind === "tool_call").length;
1114
- }
1115
- function computeDurationSeconds(tree) {
1116
- const started = tree.meta.startedAt;
1117
- if (started == null) return null;
1118
- let maxTs = started;
1119
- for (const node of Object.values(tree.byId)) {
1120
- if ("endedAt" in node && typeof node.endedAt === "number") {
1121
- maxTs = Math.max(maxTs, node.endedAt);
1122
- }
1123
- if ("startedAt" in node && typeof node.startedAt === "number") {
1124
- maxTs = Math.max(maxTs, node.startedAt);
689
+ };
690
+ var WorkflowContainerTreeProjector = class {
691
+ machine = new WorkflowContainerStateMachine();
692
+ rehydrate(snapshot) {
693
+ this.machine.rehydrate(snapshot);
694
+ }
695
+ setSuppressContentContainerIds(ids) {
696
+ this.machine.setSuppressContentContainerIds(ids);
697
+ }
698
+ consume(event) {
699
+ return this.machine.apply(event);
700
+ }
701
+ consumeAll(events) {
702
+ for (const event of events) {
703
+ this.consume(event);
1125
704
  }
705
+ return this.snapshot();
1126
706
  }
1127
- const seconds = Math.round((maxTs - started) / 1e3);
1128
- return seconds > 0 ? seconds : null;
707
+ snapshot() {
708
+ return this.machine.snapshot();
709
+ }
710
+ };
711
+ function reduceContainerSnapshot(snapshot, event, options) {
712
+ const projector = new WorkflowContainerTreeProjector();
713
+ projector.rehydrate(snapshot);
714
+ if (options?.suppressContentContainerIds?.size) {
715
+ projector.setSuppressContentContainerIds(options.suppressContentContainerIds);
716
+ }
717
+ projector.consume(event);
718
+ return projector.snapshot();
1129
719
  }
1130
- function lastLoopTextContent(tree) {
1131
- const textNodes = Object.values(tree.byId).filter((node) => node.kind === "text");
1132
- if (textNodes.length === 0) return "";
1133
- return textNodes[textNodes.length - 1].accumulated.trim();
720
+ function resolveContentTargetFromSnapshot(snapshot, workflowPath, scopeKey) {
721
+ const active = new WorkflowContainerActiveSet();
722
+ for (const containerId of snapshot.active_container_ids) {
723
+ const node = snapshot.containers[containerId];
724
+ if (node?.is_open) active.restoreNode(node);
725
+ }
726
+ return active.resolveContentTarget(workflowPath, scopeKey);
1134
727
  }
1135
- function pickTeaserLine(fullText) {
1136
- const lines = fullText.split("\n").map((line) => line.trim()).filter(Boolean);
1137
- if (lines.length === 0) return "";
1138
- const nonTableLine = lines.find((line) => !line.startsWith("|"));
1139
- return nonTableLine ?? lines[0] ?? "";
728
+ function mergeRuntimeContainerNodes(prev, snapshot) {
729
+ const out = {};
730
+ for (const [containerId, node] of Object.entries(snapshot.containers)) {
731
+ const loopSessionId = prev[containerId]?.loopSessionId ?? null;
732
+ out[containerId] = {
733
+ ...node,
734
+ loopSessionId,
735
+ // D24:已绑定 loop 的容器不得保留 content_blocks 预览(过程层 SSOT 在 loop tree)。
736
+ content_blocks: loopSessionId ? [] : node.content_blocks
737
+ };
738
+ }
739
+ return out;
1140
740
  }
1141
- function buildStageDoneSummary(tree) {
1142
- if (tree.status !== "done" && tree.status !== "error") return null;
1143
- const toolUses = countToolUses(tree);
1144
- const duration = computeDurationSeconds(tree);
1145
- const durationSuffix = duration != null ? ` \xB7 ${duration}s` : "";
1146
- const label = toolUses > 0 ? `Done (${toolUses} tool use${toolUses === 1 ? "" : "s"}${durationSuffix})` : `Done (1 agent turn${durationSuffix})`;
1147
- const fullText = lastLoopTextContent(tree);
1148
- const lines = fullText.split("\n").map((line) => line.trim()).filter(Boolean);
1149
- const teaser = pickTeaserLine(fullText);
1150
- const extraLines = Math.max(0, lines.length - 1);
1151
- return { label, teaser, extraLines };
741
+
742
+ // src/core/workflow/hydrateWorkflowSnapshot.ts
743
+ function isAtOrBeforeProjectionCursor(projectionCursor, eventId) {
744
+ if (!projectionCursor || !eventId) return false;
745
+ const parseSeq = (id) => {
746
+ const prefixed = /^evt_(\d+)$/.exec(id);
747
+ if (prefixed) return Number.parseInt(prefixed[1], 10);
748
+ const numeric = Number.parseInt(id, 10);
749
+ return Number.isNaN(numeric) ? null : numeric;
750
+ };
751
+ const cursorSeq = parseSeq(projectionCursor);
752
+ const eventSeq = parseSeq(eventId);
753
+ if (cursorSeq != null && eventSeq != null) {
754
+ return eventSeq <= cursorSeq;
755
+ }
756
+ return eventId <= projectionCursor;
1152
757
  }
1153
- function formatTeaserExpandHint(extraLines, verbose) {
1154
- if (extraLines <= 0) return null;
1155
- const shown = verbose ? Math.min(extraLines, 2) : 0;
1156
- const hidden = extraLines - shown;
1157
- if (hidden <= 0) return null;
1158
- return `\u2026 +${hidden} lines (ctrl+o to expand)`;
758
+ function hydrateWorkflowFromSnapshot(state, snapshot, options = {}) {
759
+ const authority = options.preserveAuthority ? state.authority : null;
760
+ const containerTree = structuredClone(snapshot.containerTree);
761
+ containerTree.containersById = normalizeSnapshotContainerContentBlocks(
762
+ sanitizeContainerTreeParentLinks(containerTree.containersById)
763
+ );
764
+ return {
765
+ ...createEmptyWorkflowSessionState(),
766
+ authority,
767
+ display: {
768
+ ...snapshot.display,
769
+ streamEnded: false
770
+ },
771
+ containerTree,
772
+ projectionCursor: snapshot.cursor,
773
+ snapshotVersion: snapshot.snapshotVersion,
774
+ meta: {
775
+ ...state.meta,
776
+ workflowRunId: snapshot.taskId,
777
+ lastEventId: snapshot.cursor,
778
+ startedAt: state.meta.startedAt ?? snapshot.capturedAt
779
+ },
780
+ loopTreesBySessionId: {},
781
+ internalErrors: [],
782
+ reconnectSuppressedReason: null,
783
+ snapshotHydrateFailed: false
784
+ };
1159
785
  }
1160
786
 
1161
- // src/core/workflow/workflowLoopEviction.ts
1162
- function containersByLoopSessionId(tree) {
1163
- const map = /* @__PURE__ */ new Map();
1164
- for (const node of Object.values(tree.containersById)) {
1165
- if (node.loopSessionId) map.set(node.loopSessionId, node);
1166
- }
1167
- return map;
787
+ // src/core/hydrateAskSnapshot.ts
788
+ function sessionTreeSnapshotToTree(snapshot) {
789
+ return {
790
+ ...structuredClone(snapshot),
791
+ internalErrors: [],
792
+ debugEvents: []
793
+ };
1168
794
  }
1169
- function hasRunningParallelSibling(node, tree) {
1170
- if (node.scope !== "item" || !node.parent_container_id) return false;
1171
- for (const sibling of Object.values(tree.containersById)) {
1172
- if (sibling.container_id === node.container_id) continue;
1173
- if (sibling.scope !== "item") continue;
1174
- if (sibling.parent_container_id !== node.parent_container_id) continue;
1175
- if (sibling.status === "running" || sibling.status === "retrying") return true;
1176
- }
1177
- return false;
795
+ function hydrateAskFromSnapshot(state, snapshot) {
796
+ return {
797
+ tree: sessionTreeSnapshotToTree(snapshot.sessionTree),
798
+ eventCount: snapshot.metadata.reducerEventCount,
799
+ projectionCursor: snapshot.cursor,
800
+ snapshotVersion: snapshot.snapshotVersion,
801
+ snapshotHydrateFailed: false,
802
+ reconnectSuppressedReason: null,
803
+ authority: state.authority,
804
+ authoritySubscribeFailed: state.authoritySubscribeFailed,
805
+ authorityBindingActive: state.authorityBindingActive
806
+ };
1178
807
  }
1179
- function collectProtectedLoopSessionIds(state, pinnedLoopSessionIds) {
1180
- const protectedIds = new Set(Object.keys(pinnedLoopSessionIds));
1181
- const byLoop = containersByLoopSessionId(state.containerTree);
1182
- if (state.activeLoopSessionId) {
1183
- const activeContainer = byLoop.get(state.activeLoopSessionId);
1184
- if (activeContainer && (activeContainer.status === "running" || activeContainer.status === "retrying")) {
1185
- protectedIds.add(state.activeLoopSessionId);
1186
- }
1187
- }
1188
- for (const containerId of state.containerTree.activeContainerIds) {
1189
- const node = state.containerTree.containersById[containerId];
1190
- if (!node?.loopSessionId) continue;
1191
- if (node.status === "running" || node.status === "retrying") {
1192
- protectedIds.add(node.loopSessionId);
1193
- }
1194
- }
1195
- for (const node of Object.values(state.containerTree.containersById)) {
1196
- if (!node.loopSessionId) continue;
1197
- if (node.status === "running" || node.status === "retrying") {
1198
- protectedIds.add(node.loopSessionId);
1199
- }
1200
- if ((node.status === "completed" || node.status === "failed") && hasRunningParallelSibling(node, state.containerTree)) {
1201
- protectedIds.add(node.loopSessionId);
808
+
809
+ // src/core/store.ts
810
+ function safeReduce(tree, event, eventIndex, options) {
811
+ try {
812
+ return { tree: reduceTree(tree, event, eventIndex, options) };
813
+ } catch (err) {
814
+ const message = err instanceof Error ? err.message : String(err);
815
+ const stack = err instanceof Error ? err.stack : void 0;
816
+ if (import.meta.env?.DEV) {
817
+ console.error("[langchain_agentx_stream_ui] reducer error:", err);
1202
818
  }
819
+ return {
820
+ tree: {
821
+ ...tree,
822
+ internalErrors: [
823
+ ...tree.internalErrors,
824
+ { eventIndex, eventType: event.event_type, message, stack }
825
+ ]
826
+ }
827
+ };
1203
828
  }
1204
- return protectedIds;
1205
829
  }
1206
- function evictInactiveLoopTrees(state, pinnedLoopSessionIds) {
1207
- const protectedIds = collectProtectedLoopSessionIds(state, pinnedLoopSessionIds);
1208
- for (const id of state.hotCompletedLoopSessionIds) {
1209
- protectedIds.add(id);
1210
- }
1211
- const byLoop = containersByLoopSessionId(state.containerTree);
1212
- const nextLoops = { ...state.loopTreesBySessionId };
1213
- const nextSummaries = {
1214
- ...state.stageDoneSummariesByContainerId
830
+ function createSessionStore(initialTree = createEmptyTree(), storeOptions) {
831
+ const reduceOpts = {
832
+ tierOverrides: storeOptions?.tierOverrides,
833
+ collectDebug: storeOptions?.debug === true,
834
+ subagentLeakGate: storeOptions?.subagentLeakGate === true
1215
835
  };
1216
- for (const [loopSessionId, tree] of Object.entries(state.loopTreesBySessionId)) {
1217
- if (protectedIds.has(loopSessionId)) continue;
1218
- const container = byLoop.get(loopSessionId);
1219
- if (!container) continue;
1220
- if (container.status !== "completed" && container.status !== "failed") continue;
1221
- const summary = buildStageDoneSummary(tree);
1222
- if (summary) {
1223
- nextSummaries[container.container_id] = container.scope === "aggregate" ? { ...summary, fullMarkdown: lastLoopTextContent(tree) } : summary;
836
+ const initialEventCount = storeOptions?.initialEventCount ?? 0;
837
+ const seenEventIds = new Set(storeOptions?.initialSeenEventIds);
838
+ const authorityBindingActive = storeOptions?.authorityBindingActive === true;
839
+ return createStore((set, get) => ({
840
+ tree: initialTree,
841
+ eventCount: initialEventCount,
842
+ projectionCursor: null,
843
+ snapshotVersion: null,
844
+ snapshotHydrateFailed: false,
845
+ reconnectSuppressedReason: null,
846
+ authority: null,
847
+ authoritySubscribeFailed: false,
848
+ authorityBindingActive,
849
+ applyEvent(event, ctx) {
850
+ const sseEventId = ctx?.sseEventId;
851
+ if (shouldSkipDuplicateEvent(seenEventIds, sseEventId)) return;
852
+ const { projectionCursor } = get();
853
+ if (isAtOrBeforeProjectionCursor(projectionCursor, sseEventId)) return;
854
+ const { tree, eventCount } = get();
855
+ const { tree: reduced } = safeReduce(tree, event, eventCount, reduceOpts);
856
+ const nextTree = sseEventId ? {
857
+ ...reduced,
858
+ meta: { ...reduced.meta, lastEventId: sseEventId }
859
+ } : reduced;
860
+ markEventIdSeen(seenEventIds, sseEventId);
861
+ set({ tree: nextTree, eventCount: eventCount + 1 });
862
+ },
863
+ applyEvents(events) {
864
+ let { tree, eventCount } = get();
865
+ for (const event of events) {
866
+ const result = safeReduce(tree, event, eventCount, reduceOpts);
867
+ tree = result.tree;
868
+ eventCount += 1;
869
+ }
870
+ set({ tree, eventCount });
871
+ },
872
+ applyAuthoritySnapshot(snapshot) {
873
+ set({ authority: snapshot });
874
+ },
875
+ setAuthoritySubscribeFailed(failed) {
876
+ set({ authoritySubscribeFailed: failed });
877
+ },
878
+ hydrateFromSnapshot(snapshot) {
879
+ const patch = hydrateAskFromSnapshot(get(), snapshot);
880
+ seenEventIds.clear();
881
+ markEventIdSeen(seenEventIds, snapshot.cursor);
882
+ set(patch);
883
+ },
884
+ markSnapshotHydrateFailed() {
885
+ set({
886
+ snapshotHydrateFailed: true,
887
+ reconnectSuppressedReason: "snapshot_unavailable"
888
+ });
889
+ },
890
+ reset() {
891
+ seenEventIds.clear();
892
+ set({
893
+ tree: createEmptyTree(),
894
+ eventCount: 0,
895
+ projectionCursor: null,
896
+ snapshotVersion: null,
897
+ snapshotHydrateFailed: false,
898
+ reconnectSuppressedReason: null,
899
+ authority: null,
900
+ authoritySubscribeFailed: false
901
+ });
902
+ },
903
+ markAsError(error) {
904
+ const { tree } = get();
905
+ set({
906
+ tree: {
907
+ ...tree,
908
+ status: "error",
909
+ internalErrors: error ? [
910
+ ...tree.internalErrors,
911
+ {
912
+ eventIndex: -1,
913
+ eventType: "sse_connection_failed",
914
+ message: error.message,
915
+ stack: error.stack
916
+ }
917
+ ] : tree.internalErrors
918
+ }
919
+ });
1224
920
  }
1225
- delete nextLoops[loopSessionId];
1226
- }
1227
- return {
1228
- ...state,
1229
- loopTreesBySessionId: nextLoops,
1230
- stageDoneSummariesByContainerId: nextSummaries
1231
- };
1232
- }
1233
- function touchCompletedLoopSession(state, loopSessionId) {
1234
- const ids = state.hotCompletedLoopSessionIds.filter((id) => id !== loopSessionId);
1235
- ids.push(loopSessionId);
1236
- return {
1237
- ...state,
1238
- hotCompletedLoopSessionIds: ids
1239
- };
921
+ }));
1240
922
  }
1241
- function evictCompletedLoopOverflow(state, maxHydratedCompletedLoops) {
1242
- if (state.hotCompletedLoopSessionIds.length <= maxHydratedCompletedLoops) {
1243
- return state;
923
+
924
+ // src/core/agent/useAgentAuthorityBinding.ts
925
+ import { useEffect, useRef } from "react";
926
+
927
+ // src/core/workflow/normalizeAuthoritySnapshot.ts
928
+ var KNOWN = /* @__PURE__ */ new Set([
929
+ "pending",
930
+ "running",
931
+ "done",
932
+ "error",
933
+ "canceled",
934
+ "partial_success"
935
+ ]);
936
+ function normalizeAuthoritySnapshot(snapshot) {
937
+ if (KNOWN.has(snapshot.status)) {
938
+ return snapshot;
1244
939
  }
1245
- const overflow = state.hotCompletedLoopSessionIds.length - maxHydratedCompletedLoops;
1246
- const candidateEvictIds = state.hotCompletedLoopSessionIds.slice(0, overflow);
1247
- let trimmedIds = state.hotCompletedLoopSessionIds.slice(overflow);
1248
- const byLoop = containersByLoopSessionId(state.containerTree);
1249
- const nextLoops = { ...state.loopTreesBySessionId };
1250
- const nextSummaries = {
1251
- ...state.stageDoneSummariesByContainerId
1252
- };
1253
- for (const id of candidateEvictIds) {
1254
- const tree = nextLoops[id];
1255
- if (!tree) continue;
1256
- const container = byLoop.get(id);
1257
- if (container && hasRunningParallelSibling(container, state.containerTree)) {
1258
- if (!trimmedIds.includes(id)) {
1259
- trimmedIds = [id, ...trimmedIds];
940
+ if (import.meta.env?.DEV) {
941
+ console.warn(
942
+ `[langchain_agentx_stream_ui] unknown authority.status "${String(snapshot.status)}"; defaulting to running`
943
+ );
944
+ }
945
+ return { ...snapshot, status: "running" };
946
+ }
947
+
948
+ // src/core/agent/useAgentAuthorityBinding.ts
949
+ function useAgentAuthorityBinding(store, authoritySource, callbacks) {
950
+ const onAuthorityTerminalRef = useRef(callbacks.onAuthorityTerminal);
951
+ onAuthorityTerminalRef.current = callbacks.onAuthorityTerminal;
952
+ const terminalFiredRef = useRef(false);
953
+ useEffect(() => {
954
+ terminalFiredRef.current = false;
955
+ store.getState().setAuthoritySubscribeFailed(false);
956
+ const apply = (raw) => {
957
+ const snapshot = normalizeAuthoritySnapshot(raw);
958
+ store.getState().applyAuthoritySnapshot(snapshot);
959
+ if (!terminalFiredRef.current && isAuthorityTerminalStatus(snapshot.status)) {
960
+ terminalFiredRef.current = true;
961
+ onAuthorityTerminalRef.current?.(snapshot);
1260
962
  }
1261
- continue;
963
+ };
964
+ const initial = authoritySource.getSnapshot();
965
+ if (initial) {
966
+ apply(initial);
1262
967
  }
1263
- if (container) {
1264
- const summary = buildStageDoneSummary(tree);
1265
- if (summary) {
1266
- nextSummaries[container.container_id] = container.scope === "aggregate" ? { ...summary, fullMarkdown: lastLoopTextContent(tree) } : summary;
968
+ let unsubscribe;
969
+ try {
970
+ unsubscribe = authoritySource.subscribe(apply);
971
+ } catch (err) {
972
+ if (import.meta.env?.DEV) {
973
+ console.error("[langchain_agentx_stream_ui] authoritySource.subscribe failed:", err);
1267
974
  }
975
+ store.getState().setAuthoritySubscribeFailed(true);
1268
976
  }
1269
- delete nextLoops[id];
1270
- }
1271
- return {
1272
- ...state,
1273
- loopTreesBySessionId: nextLoops,
1274
- stageDoneSummariesByContainerId: nextSummaries,
1275
- hotCompletedLoopSessionIds: trimmedIds
1276
- };
977
+ return () => {
978
+ unsubscribe?.();
979
+ };
980
+ }, [store, authoritySource]);
1277
981
  }
1278
982
 
1279
- // src/types/workflowDisplay.ts
1280
- function readDisplayPayload(data) {
1281
- const display = data.display;
1282
- if (display != null && typeof display === "object" && !Array.isArray(display)) {
1283
- return display;
983
+ // src/transport/applicationKind.ts
984
+ var ApplicationKindMismatchError = class extends Error {
985
+ name = "ApplicationKindMismatchError";
986
+ expected;
987
+ actual;
988
+ constructor(expected, actual) {
989
+ super(
990
+ `application_kind mismatch: expected ${expected}, got ${actual ?? "(missing)"}`
991
+ );
992
+ this.expected = expected;
993
+ this.actual = actual;
1284
994
  }
1285
- return null;
1286
- }
1287
-
1288
- // src/core/workflow/workflowContainerProjector.ts
1289
- var OPEN_EVENT_TYPES = /* @__PURE__ */ new Set([
1290
- "workflow-start",
1291
- "subworkflow-start",
1292
- "stage-start",
1293
- "parallel-item-start",
1294
- "parallel-aggregate-start",
1295
- "route-branch-start"
1296
- ]);
1297
- var CLOSE_EVENT_TYPES = /* @__PURE__ */ new Set([
1298
- "workflow-end",
1299
- "workflow-failed",
1300
- "subworkflow-end",
1301
- "stage-done",
1302
- "stage-failed",
1303
- "parallel-item-done",
1304
- "parallel-item-failed",
1305
- "parallel-aggregate-done",
1306
- "parallel-aggregate-failed",
1307
- "route-branch-done",
1308
- "route-branch-failed"
1309
- ]);
1310
- var EVENT_TYPE_TO_SCOPE = {
1311
- "workflow-start": "workflow",
1312
- "workflow-end": "workflow",
1313
- "workflow-failed": "workflow",
1314
- "subworkflow-start": "subworkflow",
1315
- "subworkflow-end": "subworkflow",
1316
- "stage-start": "stage",
1317
- "stage-done": "stage",
1318
- "stage-failed": "stage",
1319
- "parallel-item-start": "item",
1320
- "parallel-item-done": "item",
1321
- "parallel-item-failed": "item",
1322
- "parallel-aggregate-start": "aggregate",
1323
- "parallel-aggregate-done": "aggregate",
1324
- "parallel-aggregate-failed": "aggregate",
1325
- "route-branch-start": "branch",
1326
- "route-branch-done": "branch",
1327
- "route-branch-failed": "branch"
1328
- };
1329
- var CONTENT_EVENT_TYPES = /* @__PURE__ */ new Set([
1330
- "reasoning-start",
1331
- "reasoning-delta",
1332
- "reasoning-end",
1333
- "text-start",
1334
- "text-delta",
1335
- "text-end",
1336
- "tool-input",
1337
- "tool-input-start",
1338
- "tool-input-delta",
1339
- "tool-call",
1340
- "tool-result",
1341
- "tool-error",
1342
- "tool-progress"
1343
- ]);
1344
- var SCOPE_PRIORITY = {
1345
- workflow: 0,
1346
- subworkflow: 1,
1347
- aggregate: 2,
1348
- stage: 3,
1349
- item: 3,
1350
- branch: 3
1351
995
  };
1352
- var FAILED_CLOSE_TYPES = /* @__PURE__ */ new Set([
1353
- "workflow-failed",
1354
- "stage-failed",
1355
- "parallel-item-failed",
1356
- "parallel-aggregate-failed",
1357
- "route-branch-failed"
1358
- ]);
1359
- function buildContainerId(workflowPath, scope, scopeKey) {
1360
- return `${workflowPath}|${scope}:${scopeKey}`;
1361
- }
1362
- function readString(data, key) {
1363
- const value = data[key];
1364
- return typeof value === "string" ? value : void 0;
1365
- }
1366
- function resolveScopeKey(eventType, data) {
1367
- if (eventType === "workflow-start" || eventType === "workflow-end" || eventType === "workflow-failed") {
1368
- const workflowId = readString(data, "workflow_id");
1369
- if (workflowId) return workflowId;
1370
- return readString(data, "workflow_path") ?? "workflow";
1371
- }
1372
- if (eventType === "subworkflow-start" || eventType === "subworkflow-end") {
1373
- return readString(data, "child_workflow_id") ?? "subworkflow";
1374
- }
1375
- if (eventType === "stage-start" || eventType === "stage-done" || eventType === "stage-failed") {
1376
- const stageKey = readString(data, "stage_key");
1377
- if (stageKey) return stageKey;
1378
- const stageIndex = data.stage_index;
1379
- return typeof stageIndex === "number" ? String(stageIndex) : "stage";
996
+ var MissingApplicationKindError = class extends Error {
997
+ name = "MissingApplicationKindError";
998
+ constructor() {
999
+ super("application_kind missing in SSE meta frame");
1380
1000
  }
1381
- if (eventType === "parallel-item-start" || eventType === "parallel-item-done" || eventType === "parallel-item-failed") {
1382
- const itemKey = readString(data, "item_key");
1383
- if (itemKey) return itemKey;
1384
- const itemIndex = data.item_index;
1385
- return typeof itemIndex === "number" ? String(itemIndex) : "item";
1001
+ };
1002
+
1003
+ // src/transport/sseTransportPolicy.ts
1004
+ var BOUNDED_DEFAULTS = {
1005
+ maxReconnects: 5,
1006
+ reconnectTimeoutMs: 6e4,
1007
+ reconnectDelayMs: 1e3
1008
+ };
1009
+ var legacyAutoReconnectWarned = false;
1010
+ function warnLegacyAutoReconnectOnce() {
1011
+ if (legacyAutoReconnectWarned) return;
1012
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") {
1013
+ return;
1386
1014
  }
1387
- if (eventType === "parallel-aggregate-start" || eventType === "parallel-aggregate-done" || eventType === "parallel-aggregate-failed") {
1388
- return readString(data, "aggregate_key") ?? "aggregate";
1015
+ legacyAutoReconnectWarned = true;
1016
+ console.warn(
1017
+ "[langchain_agentx_stream_ui] autoReconnect is deprecated; use profile or policy.reconnect instead."
1018
+ );
1019
+ }
1020
+ var AGENT_TERMINAL = {
1021
+ primary: ["finish"],
1022
+ allowFinishFallback: false
1023
+ };
1024
+ var WORKFLOW_TERMINAL_STRICT = {
1025
+ primary: ["workflow-end", "workflow-failed"],
1026
+ allowFinishFallback: false
1027
+ };
1028
+ var WORKFLOW_TERMINAL_WITH_FALLBACK = {
1029
+ primary: ["workflow-end", "workflow-failed"],
1030
+ allowFinishFallback: true
1031
+ };
1032
+ var PROFILE_PRESETS = {
1033
+ oneshot: {
1034
+ profile: "oneshot",
1035
+ replay: "none",
1036
+ reconnect: {
1037
+ mode: "off",
1038
+ maxReconnects: 0,
1039
+ reconnectTimeoutMs: 0,
1040
+ reconnectDelayMs: BOUNDED_DEFAULTS.reconnectDelayMs
1041
+ },
1042
+ terminal: AGENT_TERMINAL
1043
+ },
1044
+ "oneshot-workflow": {
1045
+ profile: "oneshot-workflow",
1046
+ replay: "none",
1047
+ reconnect: {
1048
+ mode: "off",
1049
+ maxReconnects: 0,
1050
+ reconnectTimeoutMs: 0,
1051
+ reconnectDelayMs: BOUNDED_DEFAULTS.reconnectDelayMs
1052
+ },
1053
+ terminal: WORKFLOW_TERMINAL_WITH_FALLBACK
1054
+ },
1055
+ "replayable-workflow": {
1056
+ profile: "replayable-workflow",
1057
+ replay: "history",
1058
+ reconnect: {
1059
+ mode: "bounded",
1060
+ ...BOUNDED_DEFAULTS
1061
+ },
1062
+ terminal: WORKFLOW_TERMINAL_WITH_FALLBACK
1063
+ },
1064
+ "workflow-tail": {
1065
+ profile: "workflow-tail",
1066
+ replay: "none",
1067
+ reconnect: {
1068
+ mode: "bounded",
1069
+ ...BOUNDED_DEFAULTS
1070
+ },
1071
+ terminal: WORKFLOW_TERMINAL_WITH_FALLBACK
1072
+ },
1073
+ "replayable-ask": {
1074
+ profile: "replayable-ask",
1075
+ replay: "history",
1076
+ reconnect: {
1077
+ mode: "bounded",
1078
+ ...BOUNDED_DEFAULTS
1079
+ },
1080
+ terminal: AGENT_TERMINAL
1081
+ },
1082
+ "ask-tail": {
1083
+ profile: "ask-tail",
1084
+ replay: "none",
1085
+ reconnect: {
1086
+ mode: "bounded",
1087
+ ...BOUNDED_DEFAULTS
1088
+ },
1089
+ terminal: AGENT_TERMINAL
1389
1090
  }
1390
- if (eventType === "route-branch-start" || eventType === "route-branch-done" || eventType === "route-branch-failed") {
1391
- return readString(data, "branch_key") ?? readString(data, "branch_node") ?? "branch";
1091
+ };
1092
+ function legacyTerminal(expectedApplicationKind) {
1093
+ if (expectedApplicationKind === "workflow") {
1094
+ return WORKFLOW_TERMINAL_STRICT;
1392
1095
  }
1393
- const display = readDisplayPayload(data);
1394
- if (display?.debug_key) return display.debug_key;
1395
- return "unknown";
1396
- }
1397
- function displayStatus(data, fallback) {
1398
- const display = readDisplayPayload(data);
1399
- if (display?.status) return display.status;
1400
- return fallback;
1401
- }
1402
- function closeStatus(eventType) {
1403
- return FAILED_CLOSE_TYPES.has(eventType) ? "failed" : "completed";
1096
+ return AGENT_TERMINAL;
1404
1097
  }
1405
- function readPreview(data, ...keys) {
1406
- for (const key of keys) {
1407
- const value = data[key];
1408
- if (typeof value === "string") return value;
1098
+ function legacyReconnect(autoReconnect, reconnectDelayMs) {
1099
+ if (autoReconnect === false) {
1100
+ return {
1101
+ mode: "off",
1102
+ maxReconnects: 0,
1103
+ reconnectTimeoutMs: 0,
1104
+ reconnectDelayMs: reconnectDelayMs ?? BOUNDED_DEFAULTS.reconnectDelayMs
1105
+ };
1409
1106
  }
1410
- return "";
1107
+ return {
1108
+ mode: "unbounded",
1109
+ maxReconnects: Number.POSITIVE_INFINITY,
1110
+ reconnectTimeoutMs: Number.POSITIVE_INFINITY,
1111
+ reconnectDelayMs: reconnectDelayMs ?? BOUNDED_DEFAULTS.reconnectDelayMs
1112
+ };
1411
1113
  }
1412
- function contentBlockFromEvent(event) {
1413
- const { event_type: eventType } = event;
1414
- const payload = event.data ?? {};
1415
- if (eventType === "reasoning-start" || eventType === "reasoning-delta" || eventType === "reasoning-end") {
1416
- let preview = readPreview(payload, "delta", "text", "reasoning");
1417
- if (!preview && eventType === "reasoning-start") preview = "Thinking\u2026";
1418
- return { kind: "reasoning", preview, tool_name: null };
1419
- }
1420
- if (eventType === "text-start" || eventType === "text-delta" || eventType === "text-end") {
1421
- const preview = readPreview(payload, "delta", "text");
1422
- return { kind: "text", preview, tool_name: null };
1114
+ function mergePolicy(base, override) {
1115
+ if (!override) return base;
1116
+ return {
1117
+ profile: base.profile,
1118
+ replay: override.replay ?? base.replay,
1119
+ reconnect: {
1120
+ ...base.reconnect,
1121
+ ...override.reconnect
1122
+ },
1123
+ terminal: {
1124
+ primary: override.terminal?.primary ?? base.terminal.primary,
1125
+ allowFinishFallback: override.terminal?.allowFinishFallback ?? base.terminal.allowFinishFallback
1126
+ }
1127
+ };
1128
+ }
1129
+ function resolveSseTransportPolicy(input) {
1130
+ const {
1131
+ profile,
1132
+ policy: policyOverride,
1133
+ autoReconnect,
1134
+ reconnectDelayMs,
1135
+ expectedApplicationKind
1136
+ } = input;
1137
+ if (profile === void 0 && policyOverride === void 0 && autoReconnect !== void 0) {
1138
+ warnLegacyAutoReconnectOnce();
1423
1139
  }
1424
- if (CONTENT_EVENT_TYPES.has(eventType)) {
1425
- const toolName = event.tool_name ?? readString(payload, "tool_name");
1426
- const preview = readPreview(payload, "summary", "output", "input", "delta") || toolName || eventType;
1427
- return {
1428
- kind: "tool",
1429
- preview,
1430
- tool_name: toolName ?? null
1140
+ let base;
1141
+ if (profile) {
1142
+ base = { ...PROFILE_PRESETS[profile] };
1143
+ } else {
1144
+ base = {
1145
+ replay: "history",
1146
+ reconnect: legacyReconnect(autoReconnect, reconnectDelayMs),
1147
+ terminal: legacyTerminal(expectedApplicationKind)
1431
1148
  };
1432
1149
  }
1433
- return null;
1150
+ return mergePolicy(base, policyOverride);
1434
1151
  }
1435
- function wouldAssignParentCreateCycle(containersById, containerId, parentId) {
1436
- if (!parentId) return false;
1437
- if (parentId === containerId) return true;
1438
- const seen = /* @__PURE__ */ new Set();
1439
- let current = parentId;
1440
- while (current) {
1441
- if (current === containerId) return true;
1442
- if (seen.has(current)) return true;
1443
- seen.add(current);
1444
- current = containersById[current]?.parent_container_id ?? null;
1152
+ function isSseStreamTerminalEvent(agentEvent, terminal, expectedApplicationKind) {
1153
+ const eventType = agentEvent.event_type;
1154
+ if (eventType === "error") return true;
1155
+ if (terminal.primary.includes(eventType)) return true;
1156
+ if (terminal.allowFinishFallback && eventType === "finish" && expectedApplicationKind === "workflow") {
1157
+ return true;
1158
+ }
1159
+ if (!profileUsesWorkflowTerminal(terminal) && expectedApplicationKind !== "workflow" && eventType === "finish") {
1160
+ return true;
1445
1161
  }
1446
1162
  return false;
1447
1163
  }
1448
- function sanitizeContainerTreeParentLinks(containersById) {
1449
- const next = { ...containersById };
1450
- for (const [containerId, node] of Object.entries(next)) {
1451
- const seen = /* @__PURE__ */ new Set([containerId]);
1452
- let current = node.parent_container_id;
1453
- while (current) {
1454
- if (seen.has(current)) {
1455
- next[containerId] = { ...node, parent_container_id: null };
1456
- break;
1457
- }
1458
- seen.add(current);
1459
- current = next[current]?.parent_container_id ?? null;
1460
- }
1461
- }
1462
- return next;
1164
+ function profileUsesWorkflowTerminal(terminal) {
1165
+ return terminal.primary.includes("workflow-end") || terminal.primary.includes("workflow-failed");
1463
1166
  }
1464
- function normalizeSnapshotContainerContentBlocks(containersById) {
1465
- const next = {};
1466
- for (const [containerId, node] of Object.entries(containersById)) {
1467
- const loopSessionId = node.loopSessionId ?? null;
1468
- next[containerId] = {
1469
- ...node,
1470
- content_blocks: loopSessionId ? [] : node.content_blocks ?? []
1471
- };
1167
+ var ReconnectBudget = class {
1168
+ constructor(maxReconnects, timeoutMs, startedAtMs = Date.now()) {
1169
+ this.maxReconnects = maxReconnects;
1170
+ this.timeoutMs = timeoutMs;
1171
+ this.startedAtMs = startedAtMs;
1472
1172
  }
1473
- return next;
1474
- }
1475
- function resolveContentScopeKey(data) {
1476
- for (const keyName of ["stage_key", "item_key", "branch_key", "aggregate_key", "debug_key"]) {
1477
- const value = readString(data, keyName);
1478
- if (value) return value;
1173
+ maxReconnects;
1174
+ timeoutMs;
1175
+ startedAtMs;
1176
+ attemptCount = 0;
1177
+ get attempts() {
1178
+ return this.attemptCount;
1479
1179
  }
1480
- const display = readDisplayPayload(data);
1481
- return display?.debug_key ?? null;
1482
- }
1483
- var WorkflowContainerActiveSet = class {
1484
- openOrder = [];
1485
- nodes = {};
1486
- restoreNode(node) {
1487
- this.nodes[node.container_id] = { ...node };
1488
- if (node.is_open && !this.openOrder.includes(node.container_id)) {
1489
- this.openOrder.push(node.container_id);
1180
+ canRetry(nowMs = Date.now()) {
1181
+ if (this.maxReconnects <= 0) return false;
1182
+ if (!Number.isFinite(this.maxReconnects) && !Number.isFinite(this.timeoutMs)) {
1183
+ return true;
1490
1184
  }
1491
- }
1492
- open(node) {
1493
- this.nodes[node.container_id] = node;
1494
- if (!this.openOrder.includes(node.container_id)) {
1495
- this.openOrder.push(node.container_id);
1185
+ if (Number.isFinite(this.maxReconnects) && this.attemptCount >= this.maxReconnects) {
1186
+ return false;
1496
1187
  }
1188
+ if (Number.isFinite(this.timeoutMs) && nowMs - this.startedAtMs > this.timeoutMs) {
1189
+ return false;
1190
+ }
1191
+ return true;
1497
1192
  }
1498
- close(containerId, status) {
1499
- const node = this.nodes[containerId];
1500
- if (!node) return;
1501
- node.status = status;
1502
- node.is_open = false;
1503
- const idx = this.openOrder.indexOf(containerId);
1504
- if (idx !== -1) this.openOrder.splice(idx, 1);
1505
- }
1506
- get activeIds() {
1507
- return [...this.openOrder];
1508
- }
1509
- getNode(containerId) {
1510
- return this.nodes[containerId];
1511
- }
1512
- get nodesById() {
1513
- return this.nodes;
1193
+ recordAttempt() {
1194
+ this.attemptCount += 1;
1514
1195
  }
1515
- rankContainer(containerId) {
1516
- const node = this.nodes[containerId];
1517
- if (!node) return [-1, -1, -1, -1];
1518
- const openIndex = this.openOrder.indexOf(containerId);
1519
- const scopePriority = SCOPE_PRIORITY[node.scope] ?? 0;
1520
- return [node.workflow_depth, scopePriority, node.workflow_path.length, openIndex];
1196
+ };
1197
+ function shouldAutoReconnect(reconnect) {
1198
+ return reconnect.mode !== "off";
1199
+ }
1200
+
1201
+ // src/transport/createAgentxSseSource.ts
1202
+ var EXPECTED_PROTOCOL_VERSION = "1";
1203
+ var SseTransportTerminalError = class extends Error {
1204
+ kind;
1205
+ constructor(kind, message) {
1206
+ super(message);
1207
+ this.name = "SseTransportTerminalError";
1208
+ this.kind = kind;
1521
1209
  }
1522
- deepestActive() {
1523
- if (this.openOrder.length === 0) return null;
1524
- return this.openOrder.reduce(
1525
- (best, cid) => this.compareRank(cid, best) > 0 ? cid : best
1526
- );
1210
+ };
1211
+ function transportLog(message, level = "warn") {
1212
+ const line = `[langchain_agentx_stream_ui] SSE transport: ${message}`;
1213
+ if (level === "error") {
1214
+ console.error(line);
1215
+ } else {
1216
+ console.warn(line);
1527
1217
  }
1528
- resolveContentTarget(workflowPath, scopeKey) {
1529
- if (scopeKey) {
1530
- for (let i = this.openOrder.length - 1; i >= 0; i -= 1) {
1531
- const containerId = this.openOrder[i];
1532
- const node = this.nodes[containerId];
1533
- if (node.scope_key === scopeKey && node.is_open) {
1534
- if (workflowPath === null || node.workflow_path === workflowPath) {
1535
- return containerId;
1218
+ }
1219
+ function buildResumeStreamUrl(baseUrl, lastEventId, queryParam = "last_event_id") {
1220
+ if (!lastEventId) return baseUrl;
1221
+ const sep = baseUrl.includes("?") ? "&" : "?";
1222
+ return `${baseUrl}${sep}${encodeURIComponent(queryParam)}=${encodeURIComponent(lastEventId)}`;
1223
+ }
1224
+ function createAgentxSseSource(options) {
1225
+ const {
1226
+ url,
1227
+ profile,
1228
+ policy: policyOverride,
1229
+ autoReconnect,
1230
+ reconnectDelayMs,
1231
+ onLastEventId,
1232
+ EventSourceImpl = EventSource,
1233
+ expectedApplicationKind
1234
+ } = options;
1235
+ const transportPolicy = resolveSseTransportPolicy({
1236
+ profile,
1237
+ policy: policyOverride,
1238
+ autoReconnect,
1239
+ reconnectDelayMs,
1240
+ expectedApplicationKind
1241
+ });
1242
+ const reconnectDelay = transportPolicy.reconnect.reconnectDelayMs;
1243
+ const reconnectBudget = transportPolicy.reconnect.mode === "bounded" ? new ReconnectBudget(
1244
+ transportPolicy.reconnect.maxReconnects,
1245
+ transportPolicy.reconnect.reconnectTimeoutMs
1246
+ ) : null;
1247
+ return {
1248
+ start(handler, signal) {
1249
+ return new Promise((resolve, reject) => {
1250
+ let es = null;
1251
+ let handshakeOk = false;
1252
+ let lastEventId = null;
1253
+ let fatal = false;
1254
+ let streamTerminal = false;
1255
+ let bufferedDelta = null;
1256
+ let flushTimer = null;
1257
+ const flushBufferedDelta = () => {
1258
+ if (flushTimer) {
1259
+ clearTimeout(flushTimer);
1260
+ flushTimer = null;
1536
1261
  }
1537
- }
1538
- }
1539
- }
1540
- if (workflowPath) {
1541
- const candidates = this.openOrder.filter((cid) => {
1542
- const node = this.nodes[cid];
1543
- if (!node.is_open) return false;
1544
- return node.workflow_path === workflowPath || workflowPath.startsWith(`${node.workflow_path}>`);
1545
- });
1546
- if (candidates.length > 0) {
1547
- return candidates.reduce(
1548
- (best, cid) => this.compareRank(cid, best) > 0 ? cid : best
1549
- );
1550
- }
1551
- }
1552
- const openNonWorkflow = this.openOrder.map((cid) => this.nodes[cid]).filter((node) => node.is_open && node.scope !== "workflow");
1553
- if (openNonWorkflow.length === 1) {
1554
- return openNonWorkflow[0].container_id;
1262
+ if (!bufferedDelta) return;
1263
+ handler(bufferedDelta.event, bufferedDelta.ctx);
1264
+ bufferedDelta = null;
1265
+ };
1266
+ const isBufferedDeltaEvent = (event) => event.event_type === "text-delta" || event.event_type === "reasoning-delta";
1267
+ const mergeDeltaEvent = (prev, next) => {
1268
+ if (prev.event_type !== next.event_type) return null;
1269
+ if (prev.step_index !== next.step_index) return null;
1270
+ if (prev.tool_name !== next.tool_name) return null;
1271
+ if (prev.session_id !== next.session_id) return null;
1272
+ const prevData = prev.data;
1273
+ const nextData = next.data;
1274
+ const prevChunk = prevData.delta ?? prevData.text ?? prevData.content ?? "";
1275
+ const nextChunk = nextData.delta ?? nextData.text ?? nextData.content ?? "";
1276
+ return {
1277
+ ...next,
1278
+ data: {
1279
+ ...next.data,
1280
+ text: `${prevChunk}${nextChunk}`
1281
+ }
1282
+ };
1283
+ };
1284
+ const cleanup = () => {
1285
+ es?.close();
1286
+ es = null;
1287
+ };
1288
+ const finish = (error) => {
1289
+ flushBufferedDelta();
1290
+ cleanup();
1291
+ if (error) reject(error);
1292
+ else resolve();
1293
+ };
1294
+ const failHandshake = (error) => {
1295
+ fatal = true;
1296
+ transportLog(error.message, "error");
1297
+ cleanup();
1298
+ finish(error);
1299
+ };
1300
+ const failPrematureClose = () => {
1301
+ const error = new SseTransportTerminalError(
1302
+ "premature_close",
1303
+ "SSE connection lost before stream finished"
1304
+ );
1305
+ transportLog(error.message, "error");
1306
+ cleanup();
1307
+ finish(error);
1308
+ };
1309
+ const connect = (resumeFromId) => {
1310
+ if (signal.aborted || fatal) {
1311
+ finish();
1312
+ return;
1313
+ }
1314
+ const connectUrl = resumeFromId != null && resumeFromId !== "" ? buildResumeStreamUrl(url, resumeFromId) : url;
1315
+ es = new EventSourceImpl(connectUrl);
1316
+ handshakeOk = false;
1317
+ es.addEventListener("meta", (event) => {
1318
+ try {
1319
+ const meta = JSON.parse(event.data);
1320
+ if (meta.protocol_version !== EXPECTED_PROTOCOL_VERSION) {
1321
+ failHandshake(
1322
+ new Error(`Unsupported protocol_version: ${meta.protocol_version}`)
1323
+ );
1324
+ return;
1325
+ }
1326
+ if (expectedApplicationKind) {
1327
+ const actual = meta.application_kind;
1328
+ if (actual === void 0 || actual === null) {
1329
+ failHandshake(new MissingApplicationKindError());
1330
+ return;
1331
+ }
1332
+ if (actual !== expectedApplicationKind) {
1333
+ failHandshake(
1334
+ new ApplicationKindMismatchError(expectedApplicationKind, actual)
1335
+ );
1336
+ return;
1337
+ }
1338
+ }
1339
+ handshakeOk = true;
1340
+ } catch (err) {
1341
+ const message = err instanceof Error ? err.message : "Failed to decode meta frame";
1342
+ failHandshake(new Error(message));
1343
+ }
1344
+ });
1345
+ es.addEventListener("agentx", (event) => {
1346
+ if (!handshakeOk) {
1347
+ failHandshake(new Error("Received agentx frame before meta handshake"));
1348
+ return;
1349
+ }
1350
+ try {
1351
+ const agentEvent = JSON.parse(event.data);
1352
+ const sseEventId = event.lastEventId || void 0;
1353
+ if (sseEventId) {
1354
+ lastEventId = sseEventId;
1355
+ onLastEventId?.(sseEventId);
1356
+ }
1357
+ const ctx = sseEventId ? { sseEventId } : void 0;
1358
+ if (isBufferedDeltaEvent(agentEvent)) {
1359
+ if (bufferedDelta) {
1360
+ const merged = mergeDeltaEvent(bufferedDelta.event, agentEvent);
1361
+ if (merged) {
1362
+ bufferedDelta = { event: merged, ctx };
1363
+ } else {
1364
+ flushBufferedDelta();
1365
+ bufferedDelta = { event: agentEvent, ctx };
1366
+ }
1367
+ } else {
1368
+ bufferedDelta = { event: agentEvent, ctx };
1369
+ }
1370
+ if (!flushTimer) {
1371
+ flushTimer = setTimeout(() => {
1372
+ flushBufferedDelta();
1373
+ }, 16);
1374
+ }
1375
+ } else {
1376
+ flushBufferedDelta();
1377
+ handler(agentEvent, ctx);
1378
+ if (isSseStreamTerminalEvent(
1379
+ agentEvent,
1380
+ transportPolicy.terminal,
1381
+ expectedApplicationKind
1382
+ )) {
1383
+ streamTerminal = true;
1384
+ }
1385
+ }
1386
+ } catch (err) {
1387
+ const message = err instanceof Error ? err.message : "Failed to decode agentx frame";
1388
+ transportLog(`Dropped agentx frame: ${message}`, "warn");
1389
+ }
1390
+ });
1391
+ es.onerror = () => {
1392
+ flushBufferedDelta();
1393
+ if (signal.aborted || fatal || streamTerminal) {
1394
+ cleanup();
1395
+ finish();
1396
+ return;
1397
+ }
1398
+ if (!handshakeOk) {
1399
+ const error = new SseTransportTerminalError(
1400
+ "handshake_failed",
1401
+ "SSE connection failed before meta handshake"
1402
+ );
1403
+ transportLog(error.message, "error");
1404
+ cleanup();
1405
+ finish(error);
1406
+ return;
1407
+ }
1408
+ if (shouldAutoReconnect(transportPolicy.reconnect)) {
1409
+ if (transportPolicy.reconnect.mode === "bounded" && reconnectBudget && !reconnectBudget.canRetry()) {
1410
+ const error = new SseTransportTerminalError(
1411
+ "reconnect_budget_exhausted",
1412
+ "SSE reconnect budget exhausted"
1413
+ );
1414
+ transportLog(error.message, "error");
1415
+ cleanup();
1416
+ finish(error);
1417
+ return;
1418
+ }
1419
+ if (reconnectBudget) {
1420
+ reconnectBudget.recordAttempt();
1421
+ }
1422
+ cleanup();
1423
+ setTimeout(() => connect(lastEventId), reconnectDelay);
1424
+ return;
1425
+ }
1426
+ failPrematureClose();
1427
+ };
1428
+ };
1429
+ signal.addEventListener("abort", () => {
1430
+ flushBufferedDelta();
1431
+ fatal = true;
1432
+ finish();
1433
+ }, { once: true });
1434
+ connect();
1435
+ });
1555
1436
  }
1556
- return null;
1557
- }
1558
- closeDescendantsOfPath(workflowPath, status) {
1559
- const toClose = this.openOrder.filter((cid) => {
1560
- const node = this.nodes[cid];
1561
- return node.workflow_path === workflowPath || node.workflow_path.startsWith(`${workflowPath}>`);
1562
- });
1563
- for (const containerId of toClose) {
1564
- this.close(containerId, status);
1437
+ };
1438
+ }
1439
+ function createMockSource(events) {
1440
+ return {
1441
+ async start(handler, signal) {
1442
+ for (const event of events) {
1443
+ if (signal.aborted) break;
1444
+ handler(event);
1445
+ }
1565
1446
  }
1566
- }
1567
- compareRank(a, b) {
1568
- const ra = this.rankContainer(a);
1569
- const rb = this.rankContainer(b);
1570
- for (let i = 0; i < ra.length; i += 1) {
1571
- if (ra[i] > rb[i]) return 1;
1572
- if (ra[i] < rb[i]) return -1;
1447
+ };
1448
+ }
1449
+
1450
+ // src/view/agentLoop/ScopedSessionStore.tsx
1451
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
1452
+ import { jsx } from "react/jsx-runtime";
1453
+ function ScopedSessionStoreProvider({
1454
+ tree,
1455
+ children
1456
+ }) {
1457
+ const storeRef = useRef2(createSessionStore(tree, { initialEventCount: 1 }));
1458
+ useEffect2(() => {
1459
+ storeRef.current.setState({ tree });
1460
+ }, [tree]);
1461
+ return /* @__PURE__ */ jsx(SessionStoreContext.Provider, { value: storeRef.current, children });
1462
+ }
1463
+
1464
+ // src/view/agentLoop/AgentLoopView.tsx
1465
+ import { jsx as jsx2 } from "react/jsx-runtime";
1466
+ function AgentLoopViewBody({
1467
+ showTaskListFooter = true,
1468
+ virtualized,
1469
+ virtualizeThreshold,
1470
+ groupParallelTools
1471
+ }) {
1472
+ return /* @__PURE__ */ jsx2("div", { className: "lax-agent-loop-view", "data-testid": "lax-agent-loop-view", children: /* @__PURE__ */ jsx2(
1473
+ SessionTimeline,
1474
+ {
1475
+ virtualized,
1476
+ virtualizeThreshold,
1477
+ groupParallelTools,
1478
+ showTaskListFooter
1573
1479
  }
1574
- return 0;
1575
- }
1576
- /** 供 StateMachine 比较容器深度 rank */
1577
- compareContainerRank(a, b) {
1578
- return this.compareRank(a, b);
1579
- }
1580
- };
1581
- var WorkflowContainerStateMachine = class {
1582
- active = new WorkflowContainerActiveSet();
1583
- rootIds = [];
1584
- suppressContentContainerIds = /* @__PURE__ */ new Set();
1585
- get activeSet() {
1586
- return this.active;
1587
- }
1588
- setSuppressContentContainerIds(ids) {
1589
- this.suppressContentContainerIds = ids;
1480
+ ) });
1481
+ }
1482
+ function AgentLoopView({
1483
+ tree,
1484
+ showTaskListFooter = true,
1485
+ virtualized,
1486
+ virtualizeThreshold,
1487
+ groupParallelTools
1488
+ }) {
1489
+ if (tree) {
1490
+ return /* @__PURE__ */ jsx2(ScopedSessionStoreProvider, { tree, children: /* @__PURE__ */ jsx2(
1491
+ AgentLoopViewBody,
1492
+ {
1493
+ showTaskListFooter,
1494
+ virtualized,
1495
+ virtualizeThreshold,
1496
+ groupParallelTools
1497
+ }
1498
+ ) });
1590
1499
  }
1591
- rehydrate(snapshot) {
1592
- this.rootIds.length = 0;
1593
- this.rootIds.push(...snapshot.root_container_ids);
1594
- for (const node of Object.values(snapshot.containers)) {
1595
- this.active.restoreNode(node);
1500
+ return /* @__PURE__ */ jsx2(
1501
+ AgentLoopViewBody,
1502
+ {
1503
+ showTaskListFooter,
1504
+ virtualized,
1505
+ virtualizeThreshold,
1506
+ groupParallelTools
1596
1507
  }
1597
- }
1598
- apply(event) {
1599
- const eventType = event.event_type;
1600
- const data = event.data ?? {};
1601
- if (OPEN_EVENT_TYPES.has(eventType)) {
1602
- this.openContainer(eventType, data);
1603
- return "open";
1508
+ );
1509
+ }
1510
+
1511
+ // src/view/AgentSessionBanners.tsx
1512
+ import { jsx as jsx3 } from "react/jsx-runtime";
1513
+ function AgentSessionBanners({ snapshotHydrateFailed }) {
1514
+ if (!snapshotHydrateFailed) return null;
1515
+ return /* @__PURE__ */ jsx3(
1516
+ "div",
1517
+ {
1518
+ className: "lax-agent-banner lax-agent-banner--snapshot-unavailable",
1519
+ "data-testid": "lax-agent-snapshot-unavailable-banner",
1520
+ role: "status",
1521
+ children: "\u5C55\u793A\u6001\u4E0D\u53EF\u7528 / \u4F1A\u8BDD\u5DF2\u8FC7\u671F"
1604
1522
  }
1605
- if (CLOSE_EVENT_TYPES.has(eventType)) {
1606
- this.closeContainer(eventType, data);
1607
- return "close";
1523
+ );
1524
+ }
1525
+
1526
+ // src/view/AgentSession.tsx
1527
+ import { useStore } from "zustand";
1528
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
1529
+ function DebugPanel() {
1530
+ const status = useSessionStatus();
1531
+ const errors = useInternalErrors();
1532
+ if (errors.length === 0) return null;
1533
+ return /* @__PURE__ */ jsxs("div", { className: "lax-debug-panel", "data-testid": "lax-debug-panel", children: [
1534
+ /* @__PURE__ */ jsxs("div", { children: [
1535
+ "status: ",
1536
+ status
1537
+ ] }),
1538
+ /* @__PURE__ */ jsx4("ul", { children: errors.map((err, i) => /* @__PURE__ */ jsxs("li", { children: [
1539
+ "[",
1540
+ err.eventType,
1541
+ "] ",
1542
+ err.message
1543
+ ] }, `${err.eventIndex}-${i}`)) })
1544
+ ] });
1545
+ }
1546
+ function AgentAuthorityBinder({
1547
+ store,
1548
+ authoritySource,
1549
+ onAuthorityTerminal
1550
+ }) {
1551
+ useAgentAuthorityBinding(store, authoritySource, { onAuthorityTerminal });
1552
+ return null;
1553
+ }
1554
+ function AgentSnapshotBootstrap({
1555
+ store,
1556
+ snapshotBootstrap,
1557
+ snapshotHydrateFailed = false
1558
+ }) {
1559
+ useEffect3(() => {
1560
+ if (snapshotBootstrap) {
1561
+ store.getState().hydrateFromSnapshot(snapshotBootstrap);
1608
1562
  }
1609
- if (CONTENT_EVENT_TYPES.has(eventType)) {
1610
- this.appendContent(event);
1611
- return "append-content";
1563
+ }, [snapshotBootstrap, store]);
1564
+ useEffect3(() => {
1565
+ if (snapshotHydrateFailed) {
1566
+ store.getState().markSnapshotHydrateFailed();
1612
1567
  }
1613
- return null;
1568
+ }, [snapshotHydrateFailed, store]);
1569
+ const showBanner = useStore(store, (s) => s.snapshotHydrateFailed);
1570
+ return /* @__PURE__ */ jsx4(AgentSessionBanners, { snapshotHydrateFailed: showBanner });
1571
+ }
1572
+ function AgentSession({
1573
+ source,
1574
+ authoritySource,
1575
+ onAuthorityTerminal,
1576
+ initialEvents,
1577
+ registry,
1578
+ tierOverrides: _tierOverrides,
1579
+ onError,
1580
+ autoReconnect: _autoReconnect,
1581
+ debug = false,
1582
+ virtualized = false,
1583
+ virtualizeThreshold,
1584
+ interactionBus,
1585
+ toolDisplayRegistry,
1586
+ defaultBodyMode = "preview",
1587
+ groupParallelTools = false,
1588
+ permissionUiMode = "standalone",
1589
+ markdownRenderer,
1590
+ displayMode = "normal",
1591
+ verbose = false,
1592
+ exploreFullscreenBash = true,
1593
+ memoryDir = null,
1594
+ workspaceRoot = null,
1595
+ subagentLeakGate = false,
1596
+ snapshotBootstrap,
1597
+ snapshotHydrateFailed = false,
1598
+ children
1599
+ }) {
1600
+ const storeRef = useRef3(null);
1601
+ if (storeRef.current === null) {
1602
+ const replayOpts = {
1603
+ tierOverrides: _tierOverrides,
1604
+ collectDebug: debug,
1605
+ subagentLeakGate
1606
+ };
1607
+ const initialTree = initialEvents && initialEvents.length > 0 ? replayEvents(initialEvents, replayOpts) : void 0;
1608
+ storeRef.current = createSessionStore(initialTree, {
1609
+ tierOverrides: _tierOverrides,
1610
+ debug,
1611
+ initialEventCount: initialEvents?.length ?? 0,
1612
+ subagentLeakGate,
1613
+ authorityBindingActive: authoritySource != null
1614
+ });
1614
1615
  }
1615
- snapshot() {
1616
- return {
1617
- root_container_ids: [...this.rootIds],
1618
- containers: { ...this.active.nodesById },
1619
- active_container_ids: this.active.activeIds
1616
+ const registryRef = useMemo(() => registry ?? createDefaultRegistry(), [registry]);
1617
+ const busRef = useMemo(
1618
+ () => interactionBus ?? getNoopInteractionBus(),
1619
+ [interactionBus]
1620
+ );
1621
+ const toolDisplayRef = useMemo(
1622
+ () => ({
1623
+ registry: toolDisplayRegistry ?? createDefaultToolRegistry(),
1624
+ defaultBodyMode
1625
+ }),
1626
+ [toolDisplayRegistry, defaultBodyMode]
1627
+ );
1628
+ const sessionViewRef = useMemo(
1629
+ () => ({
1630
+ ...DEFAULT_SESSION_VIEW_OPTIONS,
1631
+ groupParallelTools,
1632
+ permissionUiMode,
1633
+ verboseReasoning: debug,
1634
+ displayMode,
1635
+ verbose,
1636
+ exploreFullscreenBash,
1637
+ memoryDir,
1638
+ workspaceRoot,
1639
+ subagentLeakGate
1640
+ }),
1641
+ [
1642
+ groupParallelTools,
1643
+ permissionUiMode,
1644
+ debug,
1645
+ displayMode,
1646
+ verbose,
1647
+ exploreFullscreenBash,
1648
+ memoryDir,
1649
+ workspaceRoot,
1650
+ subagentLeakGate
1651
+ ]
1652
+ );
1653
+ const onErrorRef = useRef3(onError);
1654
+ onErrorRef.current = onError;
1655
+ useEffect3(() => {
1656
+ const store = storeRef.current;
1657
+ const controller = new AbortController();
1658
+ void source.start((event, ctx) => {
1659
+ store.getState().applyEvent(event, ctx);
1660
+ }, controller.signal).catch((err) => {
1661
+ if (err instanceof SseTransportTerminalError) {
1662
+ const sessionState = store.getState();
1663
+ if (sessionState.authorityBindingActive && !isAuthorityTerminalStatus(sessionState.authority?.status)) {
1664
+ onErrorRef.current?.(err);
1665
+ return;
1666
+ }
1667
+ const { tree } = sessionState;
1668
+ if (tree.status === "running" || tree.status === "connecting") {
1669
+ store.setState({ tree: { ...tree, status: "error" } });
1670
+ }
1671
+ onErrorRef.current?.(err);
1672
+ } else if (err instanceof Error) {
1673
+ onErrorRef.current?.(err);
1674
+ }
1675
+ });
1676
+ return () => {
1677
+ controller.abort();
1620
1678
  };
1679
+ }, [source]);
1680
+ return /* @__PURE__ */ jsx4(SessionStoreContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx4(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx4(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx4(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx4(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx4(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs("div", { className: "lax-agent-session", "data-testid": "lax-agent-session", children: [
1681
+ /* @__PURE__ */ jsx4(
1682
+ AgentSnapshotBootstrap,
1683
+ {
1684
+ store: storeRef.current,
1685
+ snapshotBootstrap,
1686
+ snapshotHydrateFailed
1687
+ }
1688
+ ),
1689
+ authoritySource ? /* @__PURE__ */ jsx4(
1690
+ AgentAuthorityBinder,
1691
+ {
1692
+ store: storeRef.current,
1693
+ authoritySource,
1694
+ onAuthorityTerminal
1695
+ }
1696
+ ) : null,
1697
+ children ?? /* @__PURE__ */ jsx4(
1698
+ AgentLoopView,
1699
+ {
1700
+ virtualized,
1701
+ virtualizeThreshold,
1702
+ groupParallelTools
1703
+ }
1704
+ ),
1705
+ debug ? /* @__PURE__ */ jsx4(DebugPanel, {}) : null
1706
+ ] }) }) }) }) }) }) });
1707
+ }
1708
+
1709
+ // src/view/workflow/WorkflowSession.tsx
1710
+ import { useEffect as useEffect11, useMemo as useMemo9, useRef as useRef5 } from "react";
1711
+
1712
+ // src/core/context/WorkflowSessionContext.tsx
1713
+ import { createContext, useContext } from "react";
1714
+ import { useStore as useStore2 } from "zustand";
1715
+ var WorkflowSessionStoreContext = createContext(null);
1716
+ function useWorkflowSessionStoreApi() {
1717
+ const store = useContext(WorkflowSessionStoreContext);
1718
+ if (!store) {
1719
+ throw new Error("useWorkflowSessionStoreApi must be used within WorkflowSession");
1621
1720
  }
1622
- openContainer(eventType, data) {
1623
- const scope = EVENT_TYPE_TO_SCOPE[eventType];
1624
- if (!scope) return;
1625
- const workflowPath = readString(data, "workflow_path") ?? "";
1626
- const workflowDepth = typeof data.workflow_depth === "number" ? data.workflow_depth : 0;
1627
- const scopeKey = resolveScopeKey(eventType, data);
1628
- const containerId = buildContainerId(workflowPath, scope, scopeKey);
1629
- const display = readDisplayPayload(data);
1630
- const title = display?.title ?? scopeKey;
1631
- const subtitle = display?.description ?? "";
1632
- const pattern = display?.pattern ?? "";
1633
- const status = displayStatus(data, "running");
1634
- const existing = this.active.getNode(containerId);
1635
- const computedParentId = this.resolveParentId(workflowPath, scope);
1636
- let parentId = existing != null ? existing.parent_container_id : computedParentId;
1637
- if (wouldAssignParentCreateCycle(this.active.nodesById, containerId, parentId)) {
1638
- parentId = existing?.parent_container_id ?? null;
1639
- }
1640
- const node = {
1641
- container_id: containerId,
1642
- scope,
1643
- workflow_path: workflowPath,
1644
- workflow_depth: workflowDepth,
1645
- scope_key: scopeKey,
1646
- title,
1647
- subtitle,
1648
- status,
1649
- pattern,
1650
- parent_container_id: parentId,
1651
- display,
1652
- content_blocks: existing?.content_blocks ?? [],
1653
- is_open: true
1654
- };
1655
- this.active.open(node);
1656
- if (scope === "workflow" && !this.rootIds.includes(containerId)) {
1657
- this.rootIds.push(containerId);
1658
- }
1721
+ return store;
1722
+ }
1723
+ function useWorkflowSessionStatus() {
1724
+ const store = useWorkflowSessionStoreApi();
1725
+ return useStore2(store, (s) => getDisplayProjectionStatus(s.state));
1726
+ }
1727
+
1728
+ // src/core/context/WorkflowLoopReplayContext.tsx
1729
+ import { createContext as createContext2, useContext as useContext2 } from "react";
1730
+ import { jsx as jsx5 } from "react/jsx-runtime";
1731
+ var WorkflowLoopReplayContext = createContext2(
1732
+ void 0
1733
+ );
1734
+ function WorkflowLoopReplayProvider({
1735
+ onRequestLoopReplay,
1736
+ children
1737
+ }) {
1738
+ return /* @__PURE__ */ jsx5(WorkflowLoopReplayContext.Provider, { value: onRequestLoopReplay, children });
1739
+ }
1740
+ function useWorkflowLoopReplayHandler() {
1741
+ return useContext2(WorkflowLoopReplayContext);
1742
+ }
1743
+
1744
+ // src/core/context/WorkflowScaleContext.tsx
1745
+ import { createContext as createContext3, useContext as useContext3 } from "react";
1746
+
1747
+ // src/view/workflow/workflowScaleOptions.ts
1748
+ var DEFAULT_WORKFLOW_SCALE_OPTIONS = {
1749
+ stageVirtualizeThreshold: 40,
1750
+ completedStagePageSize: 25,
1751
+ evictCompletedLoops: true,
1752
+ onlyExpandActiveRunning: true,
1753
+ parallelItemVirtualizeThreshold: 40,
1754
+ maxHydratedCompletedLoops: 4
1755
+ };
1756
+
1757
+ // src/core/context/WorkflowScaleContext.tsx
1758
+ var WorkflowScaleContext = createContext3(
1759
+ DEFAULT_WORKFLOW_SCALE_OPTIONS
1760
+ );
1761
+ function useWorkflowScaleOptions() {
1762
+ return useContext3(WorkflowScaleContext);
1763
+ }
1764
+
1765
+ // src/core/workflowSessionStore.ts
1766
+ import { createStore as createStore2 } from "zustand/vanilla";
1767
+
1768
+ // src/core/workflow/workflowLoopTreeUtils.ts
1769
+ function isWorkflowLoopTreeActive(tree) {
1770
+ return tree?.status === "running" || tree?.status === "connecting";
1771
+ }
1772
+ function createWorkflowRunningLoopTree(seedMs = Date.now()) {
1773
+ const tree = createEmptyTree();
1774
+ tree.status = "running";
1775
+ tree.meta.startedAt = seedMs;
1776
+ return tree;
1777
+ }
1778
+ function finalizeWorkflowLoopTree(tree, terminalStatus = "done") {
1779
+ if (tree.status === "done" || tree.status === "error") {
1780
+ return tree;
1659
1781
  }
1660
- closeContainer(eventType, data) {
1661
- const scope = EVENT_TYPE_TO_SCOPE[eventType];
1662
- if (!scope) return;
1663
- const workflowPath = readString(data, "workflow_path") ?? "";
1664
- const scopeKey = resolveScopeKey(eventType, data);
1665
- const containerId = buildContainerId(workflowPath, scope, scopeKey);
1666
- const status = displayStatus(data, closeStatus(eventType));
1667
- this.active.close(containerId, status);
1668
- if (scope === "workflow") {
1669
- this.active.closeDescendantsOfPath(workflowPath, status);
1782
+ const byId = { ...tree.byId };
1783
+ for (const [id, node] of Object.entries(byId)) {
1784
+ if (!node) continue;
1785
+ if ((node.kind === "text" || node.kind === "reasoning") && node.status === "streaming") {
1786
+ byId[id] = { ...node, status: "done" };
1670
1787
  }
1671
- }
1672
- appendContent(event) {
1673
- const data = event.data ?? {};
1674
- const workflowPath = readString(data, "workflow_path") ?? null;
1675
- const scopeKey = resolveContentScopeKey(data);
1676
- const targetId = this.active.resolveContentTarget(workflowPath, scopeKey);
1677
- if (!targetId) return;
1678
- if (this.suppressContentContainerIds.has(targetId)) return;
1679
- const block = contentBlockFromEvent(event);
1680
- if (!block) return;
1681
- const node = this.active.getNode(targetId);
1682
- if (!node) return;
1683
- const last = node.content_blocks[node.content_blocks.length - 1];
1684
- if (last && last.kind === block.kind && (block.kind === "reasoning" || block.kind === "text")) {
1685
- if (block.preview) {
1686
- if (block.kind === "reasoning" && last.preview === "Thinking\u2026") {
1687
- last.preview = block.preview;
1688
- } else {
1689
- last.preview = `${last.preview}${block.preview}`;
1690
- }
1691
- }
1692
- return;
1788
+ if (node.kind === "tool_call" && node.status === "running") {
1789
+ byId[id] = {
1790
+ ...node,
1791
+ status: terminalStatus === "error" ? "failed" : "done"
1792
+ };
1693
1793
  }
1694
- node.content_blocks.push(block);
1695
1794
  }
1696
- resolveParentId(workflowPath, scope) {
1697
- if (scope === "workflow") return null;
1698
- const openNodes = this.active.activeIds.map((cid) => this.active.getNode(cid)).filter((node) => node.is_open);
1699
- if (openNodes.length === 0) return null;
1700
- const prefixCandidates = openNodes.filter(
1701
- (node) => workflowPath === node.workflow_path || workflowPath.startsWith(`${node.workflow_path}>`)
1702
- );
1703
- if (prefixCandidates.length > 0) {
1704
- const parentCandidates = prefixCandidates.filter((node) => {
1705
- if (scope === "item") return node.scope !== "item";
1706
- if (scope === "aggregate") return node.scope !== "item" && node.scope !== "aggregate";
1707
- return node.scope !== scope;
1708
- });
1709
- if (parentCandidates.length > 0) {
1710
- const parent = parentCandidates.reduce(
1711
- (best, node) => this.active.compareContainerRank(node.container_id, best.container_id) > 0 ? node : best
1712
- );
1713
- if (parent.workflow_path === workflowPath && parent.scope === "workflow") {
1714
- return parent.container_id;
1715
- }
1716
- if (workflowPath.startsWith(`${parent.workflow_path}>`)) {
1717
- return parent.container_id;
1718
- }
1719
- if (parent.workflow_path === workflowPath && parent.scope !== scope) {
1720
- return parent.container_id;
1721
- }
1722
- }
1795
+ return {
1796
+ ...tree,
1797
+ byId,
1798
+ activeRoundId: null,
1799
+ status: terminalStatus
1800
+ };
1801
+ }
1802
+
1803
+ // src/core/workflow/stageDoneSummary.ts
1804
+ function countToolUses(tree) {
1805
+ return Object.values(tree.byId).filter((node) => node.kind === "tool_call").length;
1806
+ }
1807
+ function computeDurationSeconds(tree) {
1808
+ const started = tree.meta.startedAt;
1809
+ if (started == null) return null;
1810
+ let maxTs = started;
1811
+ for (const node of Object.values(tree.byId)) {
1812
+ if ("endedAt" in node && typeof node.endedAt === "number") {
1813
+ maxTs = Math.max(maxTs, node.endedAt);
1723
1814
  }
1724
- const workflowCandidates = openNodes.filter((node) => node.scope === "workflow");
1725
- if (workflowCandidates.length > 0) {
1726
- return workflowCandidates.reduce(
1727
- (best, node) => this.active.compareContainerRank(node.container_id, best.container_id) > 0 ? node : best
1728
- ).container_id;
1815
+ if ("startedAt" in node && typeof node.startedAt === "number") {
1816
+ maxTs = Math.max(maxTs, node.startedAt);
1729
1817
  }
1730
- return null;
1731
1818
  }
1732
- };
1733
- var WorkflowContainerTreeProjector = class {
1734
- machine = new WorkflowContainerStateMachine();
1735
- rehydrate(snapshot) {
1736
- this.machine.rehydrate(snapshot);
1819
+ const seconds = Math.round((maxTs - started) / 1e3);
1820
+ return seconds > 0 ? seconds : null;
1821
+ }
1822
+ function lastLoopTextContent(tree) {
1823
+ const textNodes = Object.values(tree.byId).filter((node) => node.kind === "text");
1824
+ if (textNodes.length === 0) return "";
1825
+ return textNodes[textNodes.length - 1].accumulated.trim();
1826
+ }
1827
+ function pickTeaserLine(fullText) {
1828
+ const lines = fullText.split("\n").map((line) => line.trim()).filter(Boolean);
1829
+ if (lines.length === 0) return "";
1830
+ const nonTableLine = lines.find((line) => !line.startsWith("|"));
1831
+ return nonTableLine ?? lines[0] ?? "";
1832
+ }
1833
+ function buildStageDoneSummary(tree) {
1834
+ if (tree.status !== "done" && tree.status !== "error") return null;
1835
+ const toolUses = countToolUses(tree);
1836
+ const duration = computeDurationSeconds(tree);
1837
+ const durationSuffix = duration != null ? ` \xB7 ${duration}s` : "";
1838
+ const label = toolUses > 0 ? `Done (${toolUses} tool use${toolUses === 1 ? "" : "s"}${durationSuffix})` : `Done (1 agent turn${durationSuffix})`;
1839
+ const fullText = lastLoopTextContent(tree);
1840
+ const lines = fullText.split("\n").map((line) => line.trim()).filter(Boolean);
1841
+ const teaser = pickTeaserLine(fullText);
1842
+ const extraLines = Math.max(0, lines.length - 1);
1843
+ return { label, teaser, extraLines };
1844
+ }
1845
+ function formatTeaserExpandHint(extraLines, verbose) {
1846
+ if (extraLines <= 0) return null;
1847
+ const shown = verbose ? Math.min(extraLines, 2) : 0;
1848
+ const hidden = extraLines - shown;
1849
+ if (hidden <= 0) return null;
1850
+ return `\u2026 +${hidden} lines (ctrl+o to expand)`;
1851
+ }
1852
+
1853
+ // src/core/workflow/workflowLoopEviction.ts
1854
+ function containersByLoopSessionId(tree) {
1855
+ const map = /* @__PURE__ */ new Map();
1856
+ for (const node of Object.values(tree.containersById)) {
1857
+ if (node.loopSessionId) map.set(node.loopSessionId, node);
1858
+ }
1859
+ return map;
1860
+ }
1861
+ function hasRunningParallelSibling(node, tree) {
1862
+ if (node.scope !== "item" || !node.parent_container_id) return false;
1863
+ for (const sibling of Object.values(tree.containersById)) {
1864
+ if (sibling.container_id === node.container_id) continue;
1865
+ if (sibling.scope !== "item") continue;
1866
+ if (sibling.parent_container_id !== node.parent_container_id) continue;
1867
+ if (sibling.status === "running" || sibling.status === "retrying") return true;
1737
1868
  }
1738
- setSuppressContentContainerIds(ids) {
1739
- this.machine.setSuppressContentContainerIds(ids);
1869
+ return false;
1870
+ }
1871
+ function collectProtectedLoopSessionIds(state, pinnedLoopSessionIds) {
1872
+ const protectedIds = new Set(Object.keys(pinnedLoopSessionIds));
1873
+ const byLoop = containersByLoopSessionId(state.containerTree);
1874
+ if (state.activeLoopSessionId) {
1875
+ const activeContainer = byLoop.get(state.activeLoopSessionId);
1876
+ if (activeContainer && (activeContainer.status === "running" || activeContainer.status === "retrying")) {
1877
+ protectedIds.add(state.activeLoopSessionId);
1878
+ }
1740
1879
  }
1741
- consume(event) {
1742
- return this.machine.apply(event);
1880
+ for (const containerId of state.containerTree.activeContainerIds) {
1881
+ const node = state.containerTree.containersById[containerId];
1882
+ if (!node?.loopSessionId) continue;
1883
+ if (node.status === "running" || node.status === "retrying") {
1884
+ protectedIds.add(node.loopSessionId);
1885
+ }
1743
1886
  }
1744
- consumeAll(events) {
1745
- for (const event of events) {
1746
- this.consume(event);
1887
+ for (const node of Object.values(state.containerTree.containersById)) {
1888
+ if (!node.loopSessionId) continue;
1889
+ if (node.status === "running" || node.status === "retrying") {
1890
+ protectedIds.add(node.loopSessionId);
1891
+ }
1892
+ if ((node.status === "completed" || node.status === "failed") && hasRunningParallelSibling(node, state.containerTree)) {
1893
+ protectedIds.add(node.loopSessionId);
1747
1894
  }
1748
- return this.snapshot();
1749
1895
  }
1750
- snapshot() {
1751
- return this.machine.snapshot();
1896
+ return protectedIds;
1897
+ }
1898
+ function evictInactiveLoopTrees(state, pinnedLoopSessionIds) {
1899
+ const protectedIds = collectProtectedLoopSessionIds(state, pinnedLoopSessionIds);
1900
+ for (const id of state.hotCompletedLoopSessionIds) {
1901
+ protectedIds.add(id);
1752
1902
  }
1753
- };
1754
- function reduceContainerSnapshot(snapshot, event, options) {
1755
- const projector = new WorkflowContainerTreeProjector();
1756
- projector.rehydrate(snapshot);
1757
- if (options?.suppressContentContainerIds?.size) {
1758
- projector.setSuppressContentContainerIds(options.suppressContentContainerIds);
1903
+ const byLoop = containersByLoopSessionId(state.containerTree);
1904
+ const nextLoops = { ...state.loopTreesBySessionId };
1905
+ const nextSummaries = {
1906
+ ...state.stageDoneSummariesByContainerId
1907
+ };
1908
+ for (const [loopSessionId, tree] of Object.entries(state.loopTreesBySessionId)) {
1909
+ if (protectedIds.has(loopSessionId)) continue;
1910
+ const container = byLoop.get(loopSessionId);
1911
+ if (!container) continue;
1912
+ if (container.status !== "completed" && container.status !== "failed") continue;
1913
+ const summary = buildStageDoneSummary(tree);
1914
+ if (summary) {
1915
+ nextSummaries[container.container_id] = container.scope === "aggregate" ? { ...summary, fullMarkdown: lastLoopTextContent(tree) } : summary;
1916
+ }
1917
+ delete nextLoops[loopSessionId];
1759
1918
  }
1760
- projector.consume(event);
1761
- return projector.snapshot();
1919
+ return {
1920
+ ...state,
1921
+ loopTreesBySessionId: nextLoops,
1922
+ stageDoneSummariesByContainerId: nextSummaries
1923
+ };
1762
1924
  }
1763
- function resolveContentTargetFromSnapshot(snapshot, workflowPath, scopeKey) {
1764
- const active = new WorkflowContainerActiveSet();
1765
- for (const containerId of snapshot.active_container_ids) {
1766
- const node = snapshot.containers[containerId];
1767
- if (node?.is_open) active.restoreNode(node);
1768
- }
1769
- return active.resolveContentTarget(workflowPath, scopeKey);
1925
+ function touchCompletedLoopSession(state, loopSessionId) {
1926
+ const ids = state.hotCompletedLoopSessionIds.filter((id) => id !== loopSessionId);
1927
+ ids.push(loopSessionId);
1928
+ return {
1929
+ ...state,
1930
+ hotCompletedLoopSessionIds: ids
1931
+ };
1770
1932
  }
1771
- function mergeRuntimeContainerNodes(prev, snapshot) {
1772
- const out = {};
1773
- for (const [containerId, node] of Object.entries(snapshot.containers)) {
1774
- const loopSessionId = prev[containerId]?.loopSessionId ?? null;
1775
- out[containerId] = {
1776
- ...node,
1777
- loopSessionId,
1778
- // D24:已绑定 loop 的容器不得保留 content_blocks 预览(过程层 SSOT 在 loop tree)。
1779
- content_blocks: loopSessionId ? [] : node.content_blocks
1780
- };
1933
+ function evictCompletedLoopOverflow(state, maxHydratedCompletedLoops) {
1934
+ if (state.hotCompletedLoopSessionIds.length <= maxHydratedCompletedLoops) {
1935
+ return state;
1781
1936
  }
1782
- return out;
1937
+ const overflow = state.hotCompletedLoopSessionIds.length - maxHydratedCompletedLoops;
1938
+ const candidateEvictIds = state.hotCompletedLoopSessionIds.slice(0, overflow);
1939
+ let trimmedIds = state.hotCompletedLoopSessionIds.slice(overflow);
1940
+ const byLoop = containersByLoopSessionId(state.containerTree);
1941
+ const nextLoops = { ...state.loopTreesBySessionId };
1942
+ const nextSummaries = {
1943
+ ...state.stageDoneSummariesByContainerId
1944
+ };
1945
+ for (const id of candidateEvictIds) {
1946
+ const tree = nextLoops[id];
1947
+ if (!tree) continue;
1948
+ const container = byLoop.get(id);
1949
+ if (container && hasRunningParallelSibling(container, state.containerTree)) {
1950
+ if (!trimmedIds.includes(id)) {
1951
+ trimmedIds = [id, ...trimmedIds];
1952
+ }
1953
+ continue;
1954
+ }
1955
+ if (container) {
1956
+ const summary = buildStageDoneSummary(tree);
1957
+ if (summary) {
1958
+ nextSummaries[container.container_id] = container.scope === "aggregate" ? { ...summary, fullMarkdown: lastLoopTextContent(tree) } : summary;
1959
+ }
1960
+ }
1961
+ delete nextLoops[id];
1962
+ }
1963
+ return {
1964
+ ...state,
1965
+ loopTreesBySessionId: nextLoops,
1966
+ stageDoneSummariesByContainerId: nextSummaries,
1967
+ hotCompletedLoopSessionIds: trimmedIds
1968
+ };
1783
1969
  }
1784
1970
 
1785
1971
  // src/core/workflow/loopSessionUtils.ts
@@ -2846,45 +3032,6 @@ function reduceWorkflowEvents(events, options) {
2846
3032
  );
2847
3033
  }
2848
3034
 
2849
- // src/core/workflow/hydrateWorkflowSnapshot.ts
2850
- function isAtOrBeforeProjectionCursor(projectionCursor, eventId) {
2851
- if (!projectionCursor || !eventId) return false;
2852
- const cursorNum = Number.parseInt(projectionCursor, 10);
2853
- const eventNum = Number.parseInt(eventId, 10);
2854
- if (!Number.isNaN(cursorNum) && !Number.isNaN(eventNum)) {
2855
- return eventNum <= cursorNum;
2856
- }
2857
- return eventId <= projectionCursor;
2858
- }
2859
- function hydrateWorkflowFromSnapshot(state, snapshot, options = {}) {
2860
- const authority = options.preserveAuthority ? state.authority : null;
2861
- const containerTree = structuredClone(snapshot.containerTree);
2862
- containerTree.containersById = normalizeSnapshotContainerContentBlocks(
2863
- sanitizeContainerTreeParentLinks(containerTree.containersById)
2864
- );
2865
- return {
2866
- ...createEmptyWorkflowSessionState(),
2867
- authority,
2868
- display: {
2869
- ...snapshot.display,
2870
- streamEnded: false
2871
- },
2872
- containerTree,
2873
- projectionCursor: snapshot.cursor,
2874
- snapshotVersion: snapshot.snapshotVersion,
2875
- meta: {
2876
- ...state.meta,
2877
- workflowRunId: snapshot.taskId,
2878
- lastEventId: snapshot.cursor,
2879
- startedAt: state.meta.startedAt ?? snapshot.capturedAt
2880
- },
2881
- loopTreesBySessionId: {},
2882
- internalErrors: [],
2883
- reconnectSuppressedReason: null,
2884
- snapshotHydrateFailed: false
2885
- };
2886
- }
2887
-
2888
3035
  // src/core/workflowSessionStore.ts
2889
3036
  var DEFAULT_HYDRATE_CHUNK_SIZE = 50;
2890
3037
  function nextAnimationFrame() {
@@ -3197,35 +3344,12 @@ function createWorkflowSessionStore(initialState = createEmptyWorkflowSessionSta
3197
3344
  }
3198
3345
 
3199
3346
  // src/core/workflow/useWorkflowAuthorityBinding.ts
3200
- import { useEffect as useEffect3, useRef as useRef3 } from "react";
3201
-
3202
- // src/core/workflow/normalizeAuthoritySnapshot.ts
3203
- var KNOWN = /* @__PURE__ */ new Set([
3204
- "pending",
3205
- "running",
3206
- "done",
3207
- "error",
3208
- "canceled",
3209
- "partial_success"
3210
- ]);
3211
- function normalizeAuthoritySnapshot(snapshot) {
3212
- if (KNOWN.has(snapshot.status)) {
3213
- return snapshot;
3214
- }
3215
- if (import.meta.env?.DEV) {
3216
- console.warn(
3217
- `[langchain_agentx_stream_ui] unknown authority.status "${String(snapshot.status)}"; defaulting to running`
3218
- );
3219
- }
3220
- return { ...snapshot, status: "running" };
3221
- }
3222
-
3223
- // src/core/workflow/useWorkflowAuthorityBinding.ts
3347
+ import { useEffect as useEffect4, useRef as useRef4 } from "react";
3224
3348
  function useWorkflowAuthorityBinding(store, authoritySource, callbacks) {
3225
- const onAuthorityTerminalRef = useRef3(callbacks.onAuthorityTerminal);
3349
+ const onAuthorityTerminalRef = useRef4(callbacks.onAuthorityTerminal);
3226
3350
  onAuthorityTerminalRef.current = callbacks.onAuthorityTerminal;
3227
- const terminalFiredRef = useRef3(false);
3228
- useEffect3(() => {
3351
+ const terminalFiredRef = useRef4(false);
3352
+ useEffect4(() => {
3229
3353
  terminalFiredRef.current = false;
3230
3354
  const apply = (raw) => {
3231
3355
  const snapshot = normalizeAuthoritySnapshot(raw);
@@ -3255,11 +3379,11 @@ function useWorkflowAuthorityBinding(store, authoritySource, callbacks) {
3255
3379
  }
3256
3380
 
3257
3381
  // src/core/workflow/useWorkflowAwaitingAuthorityTimers.ts
3258
- import { useEffect as useEffect4 } from "react";
3382
+ import { useEffect as useEffect5 } from "react";
3259
3383
  var AWAITING_MS = 3e4;
3260
3384
  var SYNC_FAILED_MS = 12e4;
3261
3385
  function useWorkflowAwaitingAuthorityTimers(store, streamEnded, authorityStatus) {
3262
- useEffect4(() => {
3386
+ useEffect5(() => {
3263
3387
  if (!streamEnded || authorityStatus !== "running") {
3264
3388
  store.getState().setAwaitingAuthority(false);
3265
3389
  store.getState().setAuthoritySyncFailed(false);
@@ -3282,11 +3406,11 @@ function useWorkflowAwaitingAuthorityTimers(store, streamEnded, authorityStatus)
3282
3406
  import { useMemo as useMemo7, useState as useState6 } from "react";
3283
3407
 
3284
3408
  // src/view/workflow/WorkflowAggregateContainer.tsx
3285
- import { useCallback, useEffect as useEffect5, useMemo as useMemo2, useState, memo } from "react";
3286
- import { useStore as useStore2 } from "zustand";
3409
+ import { useCallback, useEffect as useEffect6, useMemo as useMemo2, useState, memo } from "react";
3410
+ import { useStore as useStore3 } from "zustand";
3287
3411
 
3288
3412
  // src/view/workflow/WorkflowContainerLine.tsx
3289
- import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
3413
+ import { Fragment, jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
3290
3414
  function WorkflowContainerLine({
3291
3415
  title,
3292
3416
  description,
@@ -3313,7 +3437,7 @@ function WorkflowContainerLine({
3313
3437
  className
3314
3438
  ].filter(Boolean).join(" ");
3315
3439
  const content = /* @__PURE__ */ jsxs2(Fragment, { children: [
3316
- showDot ? /* @__PURE__ */ jsx5(
3440
+ showDot ? /* @__PURE__ */ jsx6(
3317
3441
  "span",
3318
3442
  {
3319
3443
  className: "lax-workflow-container-line__status-dot lax-workflow-stage-container__status-dot",
@@ -3321,7 +3445,7 @@ function WorkflowContainerLine({
3321
3445
  "aria-hidden": true
3322
3446
  }
3323
3447
  ) : null,
3324
- /* @__PURE__ */ jsx5("span", { className: "lax-workflow-container-line__title", "data-testid": titleTestId, children: title }),
3448
+ /* @__PURE__ */ jsx6("span", { className: "lax-workflow-container-line__title", "data-testid": titleTestId, children: title }),
3325
3449
  description ? /* @__PURE__ */ jsxs2(
3326
3450
  "span",
3327
3451
  {
@@ -3334,12 +3458,12 @@ function WorkflowContainerLine({
3334
3458
  ]
3335
3459
  }
3336
3460
  ) : null,
3337
- meta.length > 0 ? /* @__PURE__ */ jsx5("span", { className: "lax-workflow-container-line__meta", "data-testid": titleTestId ? `${titleTestId}-meta` : void 0, children: meta.join(" \xB7 ") }) : null,
3338
- verbose && debugScopeKey ? /* @__PURE__ */ jsx5("span", { className: "lax-workflow-container-line__debug", "data-testid": "lax-workflow-id", children: debugScopeKey }) : null,
3339
- /* @__PURE__ */ jsx5("span", { className: "lax-workflow-container-line__status", children: statusLabel })
3461
+ meta.length > 0 ? /* @__PURE__ */ jsx6("span", { className: "lax-workflow-container-line__meta", "data-testid": titleTestId ? `${titleTestId}-meta` : void 0, children: meta.join(" \xB7 ") }) : null,
3462
+ verbose && debugScopeKey ? /* @__PURE__ */ jsx6("span", { className: "lax-workflow-container-line__debug", "data-testid": "lax-workflow-id", children: debugScopeKey }) : null,
3463
+ /* @__PURE__ */ jsx6("span", { className: "lax-workflow-container-line__status", children: statusLabel })
3340
3464
  ] });
3341
3465
  if (onToggle && interactive !== false) {
3342
- return /* @__PURE__ */ jsx5(
3466
+ return /* @__PURE__ */ jsx6(
3343
3467
  "button",
3344
3468
  {
3345
3469
  type: "button",
@@ -3353,7 +3477,7 @@ function WorkflowContainerLine({
3353
3477
  );
3354
3478
  }
3355
3479
  if (onToggle) {
3356
- return /* @__PURE__ */ jsx5(
3480
+ return /* @__PURE__ */ jsx6(
3357
3481
  "div",
3358
3482
  {
3359
3483
  className: `${lineClass} lax-workflow-container-line--static-toggle`,
@@ -3373,11 +3497,11 @@ function WorkflowContainerLine({
3373
3497
  }
3374
3498
  );
3375
3499
  }
3376
- return /* @__PURE__ */ jsx5("div", { className: lineClass, style, children: content });
3500
+ return /* @__PURE__ */ jsx6("div", { className: lineClass, style, children: content });
3377
3501
  }
3378
3502
 
3379
3503
  // src/view/workflow/WorkflowExpandTrigger.tsx
3380
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
3504
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
3381
3505
  function WorkflowExpandTrigger({
3382
3506
  summary,
3383
3507
  summaryTestId,
@@ -3402,7 +3526,7 @@ function WorkflowExpandTrigger({
3402
3526
  "\u23BF ",
3403
3527
  summary
3404
3528
  ] }),
3405
- teaser ? /* @__PURE__ */ jsx6(
3529
+ teaser ? /* @__PURE__ */ jsx7(
3406
3530
  "span",
3407
3531
  {
3408
3532
  className: "lax-workflow-expand-trigger__teaser",
@@ -3410,21 +3534,21 @@ function WorkflowExpandTrigger({
3410
3534
  children: teaser
3411
3535
  }
3412
3536
  ) : null,
3413
- expandHint ? /* @__PURE__ */ jsx6("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
3537
+ expandHint ? /* @__PURE__ */ jsx7("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
3414
3538
  ]
3415
3539
  }
3416
3540
  );
3417
3541
  }
3418
3542
 
3419
3543
  // src/view/workflow/WorkflowMarkdownPreview.tsx
3420
- import { jsx as jsx7 } from "react/jsx-runtime";
3544
+ import { jsx as jsx8 } from "react/jsx-runtime";
3421
3545
  function WorkflowMarkdownPreview({
3422
3546
  content,
3423
3547
  testId,
3424
3548
  className = "lax-workflow-markdown-preview"
3425
3549
  }) {
3426
3550
  if (!content.trim()) return null;
3427
- return /* @__PURE__ */ jsx7("div", { className, "data-testid": testId, children: /* @__PURE__ */ jsx7(RichMarkdown, { content, deferDiagrams: true }) });
3551
+ return /* @__PURE__ */ jsx8("div", { className, "data-testid": testId, children: /* @__PURE__ */ jsx8(RichMarkdown, { content, deferDiagrams: true }) });
3428
3552
  }
3429
3553
 
3430
3554
  // src/core/workflow/loopTreeUtils.ts
@@ -3434,7 +3558,7 @@ function hasTimelineNodes(tree) {
3434
3558
  }
3435
3559
 
3436
3560
  // src/view/workflow/workflowStageDoneBody.tsx
3437
- import { jsx as jsx8 } from "react/jsx-runtime";
3561
+ import { jsx as jsx9 } from "react/jsx-runtime";
3438
3562
  function WorkflowStageDoneBody({
3439
3563
  loopTree,
3440
3564
  testId,
@@ -3444,7 +3568,7 @@ function WorkflowStageDoneBody({
3444
3568
  bodyClassName = "lax-workflow-stage-container__body"
3445
3569
  }) {
3446
3570
  if (loopTree && hasTimelineNodes(loopTree)) {
3447
- return /* @__PURE__ */ jsx8("div", { className: bodyClassName, "data-testid": testId, children: /* @__PURE__ */ jsx8(
3571
+ return /* @__PURE__ */ jsx9("div", { className: bodyClassName, "data-testid": testId, children: /* @__PURE__ */ jsx9(
3448
3572
  AgentLoopView,
3449
3573
  {
3450
3574
  tree: loopTree,
@@ -3455,12 +3579,12 @@ function WorkflowStageDoneBody({
3455
3579
  }
3456
3580
  ) });
3457
3581
  }
3458
- return /* @__PURE__ */ jsx8(
3582
+ return /* @__PURE__ */ jsx9(
3459
3583
  "div",
3460
3584
  {
3461
3585
  className: `${bodyClassName} lax-workflow-stage-container__empty-hint`,
3462
3586
  "data-testid": testId,
3463
- 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" })
3587
+ children: /* @__PURE__ */ jsx9("span", { className: "lax-workflow-stage-container__empty-hint-text", children: "\uFF08\u6B64\u9636\u6BB5\u6682\u65E0 Agent \u65F6\u95F4\u7EBF\u5185\u5BB9\uFF09" })
3464
3588
  }
3465
3589
  );
3466
3590
  }
@@ -3707,7 +3831,7 @@ function workflowContainerViewPropsEqual(prev, next) {
3707
3831
  }
3708
3832
 
3709
3833
  // src/view/workflow/WorkflowAggregateContainer.tsx
3710
- import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
3834
+ import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
3711
3835
  function mergeContentPreview(node) {
3712
3836
  const blocks = node.content_blocks;
3713
3837
  if (blocks.length === 0) return "";
@@ -3736,11 +3860,11 @@ function WorkflowAggregateContainerInner({
3736
3860
  const { verbose } = useSessionViewOptions();
3737
3861
  const store = useWorkflowSessionStoreApi();
3738
3862
  const replayHandler = useWorkflowLoopReplayHandler();
3739
- const cachedSummary = useStore2(
3863
+ const cachedSummary = useStore3(
3740
3864
  store,
3741
3865
  (s) => s.state.stageDoneSummariesByContainerId[node.container_id]
3742
3866
  );
3743
- const pinLoopSession = useStore2(store, (s) => s.pinLoopSession);
3867
+ const pinLoopSession = useStore3(store, (s) => s.pinLoopSession);
3744
3868
  const isRunning = node.status === "running" || node.status === "retrying";
3745
3869
  const isWaiting = node.status === "pending" || node.status === "blocked";
3746
3870
  const isDone = node.status === "completed" || node.status === "failed";
@@ -3765,7 +3889,7 @@ function WorkflowAggregateContainerInner({
3765
3889
  const uiStatus = mapAggregateUiStatus(node);
3766
3890
  const statusLabel = buildAggregateStatusLabel(node, siblingItems);
3767
3891
  const [expanded, setExpanded] = useState(isRunning);
3768
- useEffect5(() => {
3892
+ useEffect6(() => {
3769
3893
  if (isRunning) setExpanded(true);
3770
3894
  if (isDone) setExpanded(false);
3771
3895
  }, [isRunning, isDone]);
@@ -3784,7 +3908,7 @@ function WorkflowAggregateContainerInner({
3784
3908
  return next;
3785
3909
  });
3786
3910
  }, [isDone, node.loopSessionId, loopTree, requestLoopHydration]);
3787
- useEffect5(() => {
3911
+ useEffect6(() => {
3788
3912
  const onKeyDown = (event) => {
3789
3913
  if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "o") return;
3790
3914
  setExpanded(true);
@@ -3794,7 +3918,7 @@ function WorkflowAggregateContainerInner({
3794
3918
  return () => window.removeEventListener("keydown", onKeyDown);
3795
3919
  }, [requestLoopHydration]);
3796
3920
  const shellToggle = isRunning ? toggleExpanded : isDone ? toggleExpanded : void 0;
3797
- const markdownPreview = bodyMarkdown ? /* @__PURE__ */ jsx9(
3921
+ const markdownPreview = bodyMarkdown ? /* @__PURE__ */ jsx10(
3798
3922
  WorkflowMarkdownPreview,
3799
3923
  {
3800
3924
  content: bodyMarkdown,
@@ -3811,7 +3935,7 @@ function WorkflowAggregateContainerInner({
3811
3935
  "data-container-scope": "aggregate",
3812
3936
  style: { "--lax-workflow-depth": depth },
3813
3937
  children: [
3814
- /* @__PURE__ */ jsx9(
3938
+ /* @__PURE__ */ jsx10(
3815
3939
  WorkflowContainerLine,
3816
3940
  {
3817
3941
  title: node.title,
@@ -3827,8 +3951,8 @@ function WorkflowAggregateContainerInner({
3827
3951
  }
3828
3952
  ),
3829
3953
  isWaiting && waitingTeaser ? /* @__PURE__ */ jsxs4("div", { className: "lax-workflow-aggregate__waiting-teaser", "data-testid": `${testId}-waiting`, children: [
3830
- /* @__PURE__ */ jsx9("span", { className: "lax-workflow-aggregate__waiting-prefix", children: "\u23BF " }),
3831
- /* @__PURE__ */ jsx9(
3954
+ /* @__PURE__ */ jsx10("span", { className: "lax-workflow-aggregate__waiting-prefix", children: "\u23BF " }),
3955
+ /* @__PURE__ */ jsx10(
3832
3956
  WorkflowMarkdownPreview,
3833
3957
  {
3834
3958
  content: waitingTeaser,
@@ -3836,7 +3960,7 @@ function WorkflowAggregateContainerInner({
3836
3960
  }
3837
3961
  )
3838
3962
  ] }) : null,
3839
- expanded && isRunning && loopTree ? /* @__PURE__ */ jsx9("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx9(
3963
+ expanded && isRunning && loopTree ? /* @__PURE__ */ jsx10("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx10(
3840
3964
  AgentLoopView,
3841
3965
  {
3842
3966
  tree: loopTree,
@@ -3846,7 +3970,7 @@ function WorkflowAggregateContainerInner({
3846
3970
  groupParallelTools
3847
3971
  }
3848
3972
  ) }) : null,
3849
- expanded && isRunning && !loopTree && bodyMarkdown ? /* @__PURE__ */ jsx9(
3973
+ expanded && isRunning && !loopTree && bodyMarkdown ? /* @__PURE__ */ jsx10(
3850
3974
  "div",
3851
3975
  {
3852
3976
  className: "lax-workflow-stage-container__body lax-workflow-stage-container__content-preview",
@@ -3855,7 +3979,7 @@ function WorkflowAggregateContainerInner({
3855
3979
  }
3856
3980
  ) : null,
3857
3981
  isDone && !expanded ? /* @__PURE__ */ jsxs4("div", { className: "lax-workflow-aggregate__done", "data-testid": `${testId}-done`, children: [
3858
- canExpandDone ? /* @__PURE__ */ jsx9(
3982
+ canExpandDone ? /* @__PURE__ */ jsx10(
3859
3983
  WorkflowExpandTrigger,
3860
3984
  {
3861
3985
  summary: `${node.title} \xB7 done`,
@@ -3865,10 +3989,10 @@ function WorkflowAggregateContainerInner({
3865
3989
  testId: `${testId}-done-trigger`
3866
3990
  }
3867
3991
  ) : null,
3868
- markdownPreview ? /* @__PURE__ */ jsx9("div", { className: "lax-workflow-aggregate__done-preview", "data-testid": `${testId}-teaser`, children: markdownPreview }) : null,
3869
- !bodyMarkdown && expandHint ? /* @__PURE__ */ jsx9("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
3992
+ markdownPreview ? /* @__PURE__ */ jsx10("div", { className: "lax-workflow-aggregate__done-preview", "data-testid": `${testId}-teaser`, children: markdownPreview }) : null,
3993
+ !bodyMarkdown && expandHint ? /* @__PURE__ */ jsx10("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
3870
3994
  ] }) : null,
3871
- isDone && expanded ? loopTree ? /* @__PURE__ */ jsx9(
3995
+ isDone && expanded ? loopTree ? /* @__PURE__ */ jsx10(
3872
3996
  WorkflowStageDoneBody,
3873
3997
  {
3874
3998
  node,
@@ -3878,19 +4002,19 @@ function WorkflowAggregateContainerInner({
3878
4002
  virtualizeThreshold,
3879
4003
  groupParallelTools
3880
4004
  }
3881
- ) : bodyMarkdown ? /* @__PURE__ */ jsx9(
4005
+ ) : bodyMarkdown ? /* @__PURE__ */ jsx10(
3882
4006
  "div",
3883
4007
  {
3884
4008
  className: "lax-workflow-stage-container__body lax-workflow-stage-container__content-preview",
3885
4009
  "data-testid": `${testId}-body`,
3886
4010
  children: markdownPreview
3887
4011
  }
3888
- ) : usesScopedLoop ? /* @__PURE__ */ jsx9(
4012
+ ) : usesScopedLoop ? /* @__PURE__ */ jsx10(
3889
4013
  "div",
3890
4014
  {
3891
4015
  className: "lax-workflow-stage-container__body lax-workflow-loop-replay-pending",
3892
4016
  "data-testid": `${testId}-body`,
3893
- children: /* @__PURE__ */ jsx9("p", { className: "lax-workflow-loop-replay-pending__hint", children: replayHandler ? "Loading loop details\u2026" : "Loop details evicted from memory." })
4017
+ children: /* @__PURE__ */ jsx10("p", { className: "lax-workflow-loop-replay-pending__hint", children: replayHandler ? "Loading loop details\u2026" : "Loop details evicted from memory." })
3894
4018
  }
3895
4019
  ) : null : null
3896
4020
  ]
@@ -3914,7 +4038,7 @@ var WorkflowAggregateContainer = memo(
3914
4038
  );
3915
4039
 
3916
4040
  // src/view/workflow/WorkflowParallelGroup.tsx
3917
- import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo4, useState as useState3, memo as memo3 } from "react";
4041
+ import { useCallback as useCallback3, useEffect as useEffect8, useMemo as useMemo4, useState as useState3, memo as memo3 } from "react";
3918
4042
 
3919
4043
  // src/core/workflow/workflowParallelGroupMetrics.ts
3920
4044
  var ACTIVE_ITEM_STATUSES = /* @__PURE__ */ new Set([
@@ -4008,9 +4132,9 @@ function formatParallelGroupSummary(metrics) {
4008
4132
  }
4009
4133
 
4010
4134
  // src/view/workflow/WorkflowStageContainer.tsx
4011
- import { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo3, useState as useState2, memo as memo2 } from "react";
4012
- import { useStore as useStore3 } from "zustand";
4013
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
4135
+ import { useCallback as useCallback2, useEffect as useEffect7, useMemo as useMemo3, useState as useState2, memo as memo2 } from "react";
4136
+ import { useStore as useStore4 } from "zustand";
4137
+ import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
4014
4138
  function containerTestId(node) {
4015
4139
  if (node.scope === "item") return `lax-workflow-parallel-${node.scope_key}`;
4016
4140
  if (node.scope === "stage") return `lax-workflow-stage-${node.scope_key}`;
@@ -4033,13 +4157,13 @@ function WorkflowStageContainerInner({
4033
4157
  const { verbose } = useSessionViewOptions();
4034
4158
  const store = useWorkflowSessionStoreApi();
4035
4159
  const replayHandler = useWorkflowLoopReplayHandler();
4036
- const activeLoopSessionId = useStore3(store, (s) => s.state.activeLoopSessionId);
4037
- const activeContainerIds = useStore3(store, (s) => s.state.containerTree.activeContainerIds);
4038
- const cachedSummary = useStore3(
4160
+ const activeLoopSessionId = useStore4(store, (s) => s.state.activeLoopSessionId);
4161
+ const activeContainerIds = useStore4(store, (s) => s.state.containerTree.activeContainerIds);
4162
+ const cachedSummary = useStore4(
4039
4163
  store,
4040
4164
  (s) => s.state.stageDoneSummariesByContainerId[node.container_id]
4041
4165
  );
4042
- const pinLoopSession = useStore3(store, (s) => s.pinLoopSession);
4166
+ const pinLoopSession = useStore4(store, (s) => s.pinLoopSession);
4043
4167
  const isRunning = node.status === "running" || node.status === "retrying";
4044
4168
  const isDone = node.status === "completed" || node.status === "failed";
4045
4169
  const isSkipped = node.status === "skipped";
@@ -4055,7 +4179,7 @@ function WorkflowStageContainerInner({
4055
4179
  const doneSummary = doneSummaryFromTree ?? (isDone ? cachedSummary : null);
4056
4180
  const shouldAutoExpandRunning = effectivelyRunning && !isSkipped;
4057
4181
  const [expanded, setExpanded] = useState2(shouldAutoExpandRunning);
4058
- useEffect6(() => {
4182
+ useEffect7(() => {
4059
4183
  if (shouldAutoExpandRunning) {
4060
4184
  setExpanded(true);
4061
4185
  return;
@@ -4080,7 +4204,7 @@ function WorkflowStageContainerInner({
4080
4204
  return next;
4081
4205
  });
4082
4206
  }, [isDone, node.loopSessionId, loopTree, requestLoopHydration]);
4083
- useEffect6(() => {
4207
+ useEffect7(() => {
4084
4208
  const onKeyDown = (event) => {
4085
4209
  if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "o") return;
4086
4210
  setExpanded(true);
@@ -4115,7 +4239,7 @@ function WorkflowStageContainerInner({
4115
4239
  "--lax-workflow-indent-unit": workflowIndentUnit(depth)
4116
4240
  },
4117
4241
  children: [
4118
- /* @__PURE__ */ jsx10(
4242
+ /* @__PURE__ */ jsx11(
4119
4243
  WorkflowContainerLine,
4120
4244
  {
4121
4245
  title: displayTitle,
@@ -4139,8 +4263,8 @@ function WorkflowStageContainerInner({
4139
4263
  className: "lax-workflow-stage-container__done-teaser-line",
4140
4264
  "data-testid": `${testId}-teaser`,
4141
4265
  children: [
4142
- /* @__PURE__ */ jsx10("span", { className: "lax-workflow-stage-container__done-teaser-prefix", children: "\u23BF" }),
4143
- /* @__PURE__ */ jsx10(
4266
+ /* @__PURE__ */ jsx11("span", { className: "lax-workflow-stage-container__done-teaser-prefix", children: "\u23BF" }),
4267
+ /* @__PURE__ */ jsx11(
4144
4268
  WorkflowMarkdownPreview,
4145
4269
  {
4146
4270
  content: teaserText,
@@ -4150,14 +4274,14 @@ function WorkflowStageContainerInner({
4150
4274
  ]
4151
4275
  }
4152
4276
  ) : null,
4153
- expandHint ? /* @__PURE__ */ jsx10("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
4277
+ expandHint ? /* @__PURE__ */ jsx11("span", { className: "lax-workflow-expand-trigger__hint", children: expandHint }) : null
4154
4278
  ] }) : null,
4155
- expanded && isRunning && !usesScopedLoop && node.content_blocks.length > 0 ? /* @__PURE__ */ jsx10(
4279
+ expanded && isRunning && !usesScopedLoop && node.content_blocks.length > 0 ? /* @__PURE__ */ jsx11(
4156
4280
  "div",
4157
4281
  {
4158
4282
  className: "lax-workflow-stage-container__body lax-workflow-stage-container__content-preview",
4159
4283
  "data-testid": bodyTestId(node),
4160
- children: node.content_blocks.map((block, index) => /* @__PURE__ */ jsx10(
4284
+ children: node.content_blocks.map((block, index) => /* @__PURE__ */ jsx11(
4161
4285
  WorkflowMarkdownPreview,
4162
4286
  {
4163
4287
  content: block.preview,
@@ -4167,12 +4291,12 @@ function WorkflowStageContainerInner({
4167
4291
  ))
4168
4292
  }
4169
4293
  ) : null,
4170
- expanded && effectivelyRunning && runningLoopTree ? /* @__PURE__ */ jsx10(
4294
+ expanded && effectivelyRunning && runningLoopTree ? /* @__PURE__ */ jsx11(
4171
4295
  "div",
4172
4296
  {
4173
4297
  className: isItem ? "lax-workflow-parallel-item__body lax-workflow-stage-container__body" : "lax-workflow-stage-container__body",
4174
4298
  "data-testid": bodyTestId(node),
4175
- children: /* @__PURE__ */ jsx10(
4299
+ children: /* @__PURE__ */ jsx11(
4176
4300
  AgentLoopView,
4177
4301
  {
4178
4302
  tree: runningLoopTree,
@@ -4184,7 +4308,7 @@ function WorkflowStageContainerInner({
4184
4308
  )
4185
4309
  }
4186
4310
  ) : null,
4187
- effectivelyDone && expanded ? loopTree ? /* @__PURE__ */ jsx10(
4311
+ effectivelyDone && expanded ? loopTree ? /* @__PURE__ */ jsx11(
4188
4312
  WorkflowStageDoneBody,
4189
4313
  {
4190
4314
  node,
@@ -4201,7 +4325,7 @@ function WorkflowStageContainerInner({
4201
4325
  className: "lax-workflow-stage-container__body lax-workflow-loop-replay-pending",
4202
4326
  "data-testid": bodyTestId(node),
4203
4327
  children: [
4204
- teaserText ? /* @__PURE__ */ jsx10(
4328
+ teaserText ? /* @__PURE__ */ jsx11(
4205
4329
  WorkflowMarkdownPreview,
4206
4330
  {
4207
4331
  content: teaserText,
@@ -4209,7 +4333,7 @@ function WorkflowStageContainerInner({
4209
4333
  testId: `${testId}-replay-teaser`
4210
4334
  }
4211
4335
  ) : null,
4212
- /* @__PURE__ */ jsx10("p", { className: "lax-workflow-loop-replay-pending__hint", children: replayHandler ? "Loading loop details\u2026" : "Loop details evicted from memory." })
4336
+ /* @__PURE__ */ jsx11("p", { className: "lax-workflow-loop-replay-pending__hint", children: replayHandler ? "Loading loop details\u2026" : "Loop details evicted from memory." })
4213
4337
  ]
4214
4338
  }
4215
4339
  ) : null
@@ -4227,7 +4351,7 @@ function workflowStageContainerPropsEqual(prev, next) {
4227
4351
  var WorkflowStageContainer = memo2(WorkflowStageContainerInner, workflowStageContainerPropsEqual);
4228
4352
 
4229
4353
  // src/view/workflow/WorkflowParallelGroup.tsx
4230
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
4354
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
4231
4355
  function resolveLoopTree(node, loopTreesBySessionId) {
4232
4356
  if (!node.loopSessionId) return void 0;
4233
4357
  return loopTreesBySessionId[node.loopSessionId];
@@ -4249,7 +4373,7 @@ function WorkflowParallelGroupInner({
4249
4373
  );
4250
4374
  const allSettled = metrics.total > 0 && metrics.running === 0 && metrics.completed + metrics.failed >= metrics.total;
4251
4375
  const [expanded, setExpanded] = useState3(hasActiveWork);
4252
- useEffect7(() => {
4376
+ useEffect8(() => {
4253
4377
  if (hasActiveWork) {
4254
4378
  setExpanded(true);
4255
4379
  return;
@@ -4261,7 +4385,7 @@ function WorkflowParallelGroupInner({
4261
4385
  const toggleExpanded = useCallback3(() => {
4262
4386
  setExpanded((prev) => !prev);
4263
4387
  }, []);
4264
- useEffect7(() => {
4388
+ useEffect8(() => {
4265
4389
  const onKeyDown = (event) => {
4266
4390
  if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "o") return;
4267
4391
  setExpanded(true);
@@ -4271,7 +4395,7 @@ function WorkflowParallelGroupInner({
4271
4395
  }, []);
4272
4396
  const renderItem = (index) => {
4273
4397
  const item = items[index];
4274
- return /* @__PURE__ */ jsx11(
4398
+ return /* @__PURE__ */ jsx12(
4275
4399
  WorkflowStageContainer,
4276
4400
  {
4277
4401
  node: item,
@@ -4290,7 +4414,7 @@ function WorkflowParallelGroupInner({
4290
4414
  className: "lax-workflow-parallel-group",
4291
4415
  "data-testid": `lax-workflow-parallel-group-${groupKey}`,
4292
4416
  children: [
4293
- /* @__PURE__ */ jsx11(
4417
+ /* @__PURE__ */ jsx12(
4294
4418
  WorkflowExpandTrigger,
4295
4419
  {
4296
4420
  summary,
@@ -4301,7 +4425,7 @@ function WorkflowParallelGroupInner({
4301
4425
  className: "lax-workflow-parallel-group__summary"
4302
4426
  }
4303
4427
  ),
4304
- expanded ? /* @__PURE__ */ jsx11("div", { className: "lax-workflow-parallel-list", "data-testid": "lax-workflow-parallel-list", children: items.map((_, index) => renderItem(index)) }) : null
4428
+ expanded ? /* @__PURE__ */ jsx12("div", { className: "lax-workflow-parallel-list", "data-testid": "lax-workflow-parallel-list", children: items.map((_, index) => renderItem(index)) }) : null
4305
4429
  ]
4306
4430
  }
4307
4431
  );
@@ -4326,7 +4450,7 @@ function workflowParallelGroupPropsEqual(prev, next) {
4326
4450
  var WorkflowParallelGroup = memo3(WorkflowParallelGroupInner, workflowParallelGroupPropsEqual);
4327
4451
 
4328
4452
  // src/view/workflow/WorkflowRootContainer.tsx
4329
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
4453
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
4330
4454
  function WorkflowRootContainer({
4331
4455
  node,
4332
4456
  children,
@@ -4341,7 +4465,7 @@ function WorkflowRootContainer({
4341
4465
  const statusLabel = mapWorkflowRootStatusLabel(nodeStatus, sessionStatus);
4342
4466
  const meta = containersById ? buildEffectiveWorkflowRootMeta(node, containersById) : buildWorkflowRootMeta(node);
4343
4467
  return /* @__PURE__ */ jsxs7("div", { className: "lax-workflow-root", "data-testid": "lax-workflow-root", children: [
4344
- /* @__PURE__ */ jsx12(
4468
+ /* @__PURE__ */ jsx13(
4345
4469
  WorkflowContainerLine,
4346
4470
  {
4347
4471
  variant: "root",
@@ -4356,13 +4480,13 @@ function WorkflowRootContainer({
4356
4480
  verbose
4357
4481
  }
4358
4482
  ),
4359
- children ? /* @__PURE__ */ jsx12("div", { className: "lax-workflow-container-tree", children }) : null
4483
+ children ? /* @__PURE__ */ jsx13("div", { className: "lax-workflow-container-tree", children }) : null
4360
4484
  ] });
4361
4485
  }
4362
4486
 
4363
4487
  // src/view/workflow/WorkflowRouteContainer.tsx
4364
- import { useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo5, useState as useState4 } from "react";
4365
- import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
4488
+ import { useCallback as useCallback4, useEffect as useEffect9, useMemo as useMemo5, useState as useState4 } from "react";
4489
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
4366
4490
  function WorkflowRouteContainer({
4367
4491
  node,
4368
4492
  loopTree,
@@ -4386,7 +4510,7 @@ function WorkflowRouteContainer({
4386
4510
  const expandHint = doneSummary ? formatTeaserExpandHint(doneSummary.extraLines, verbose) : null;
4387
4511
  const doneLabel = doneSummary?.label ?? "done";
4388
4512
  const [expanded, setExpanded] = useState4(isRunning);
4389
- useEffect8(() => {
4513
+ useEffect9(() => {
4390
4514
  if (isSkipped) return;
4391
4515
  if (isRunning) setExpanded(true);
4392
4516
  if (isDone) setExpanded(false);
@@ -4408,7 +4532,7 @@ function WorkflowRouteContainer({
4408
4532
  "data-container-scope": "branch",
4409
4533
  style: depthStyle,
4410
4534
  children: [
4411
- /* @__PURE__ */ jsx13(
4535
+ /* @__PURE__ */ jsx14(
4412
4536
  WorkflowContainerLine,
4413
4537
  {
4414
4538
  title: node.title,
@@ -4423,7 +4547,7 @@ function WorkflowRouteContainer({
4423
4547
  descriptionTestId: `${testId}-desc`
4424
4548
  }
4425
4549
  ),
4426
- !isSkipped && expanded && isRunning && loopTree ? /* @__PURE__ */ jsx13("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx13(
4550
+ !isSkipped && expanded && isRunning && loopTree ? /* @__PURE__ */ jsx14("div", { className: "lax-workflow-stage-container__body", "data-testid": `${testId}-body`, children: /* @__PURE__ */ jsx14(
4427
4551
  AgentLoopView,
4428
4552
  {
4429
4553
  tree: loopTree,
@@ -4433,7 +4557,7 @@ function WorkflowRouteContainer({
4433
4557
  groupParallelTools
4434
4558
  }
4435
4559
  ) }) : null,
4436
- !isSkipped && isDone && !expanded ? /* @__PURE__ */ jsx13(
4560
+ !isSkipped && isDone && !expanded ? /* @__PURE__ */ jsx14(
4437
4561
  WorkflowExpandTrigger,
4438
4562
  {
4439
4563
  summary: `${node.title} \xB7 ${doneLabel}`,
@@ -4445,7 +4569,7 @@ function WorkflowRouteContainer({
4445
4569
  teaserTestId: teaserText ? `${testId}-teaser` : void 0
4446
4570
  }
4447
4571
  ) : null,
4448
- !isSkipped && isDone && expanded ? /* @__PURE__ */ jsx13(
4572
+ !isSkipped && isDone && expanded ? /* @__PURE__ */ jsx14(
4449
4573
  WorkflowStageDoneBody,
4450
4574
  {
4451
4575
  node,
@@ -4462,8 +4586,8 @@ function WorkflowRouteContainer({
4462
4586
  }
4463
4587
 
4464
4588
  // src/view/workflow/WorkflowSubworkflowContainer.tsx
4465
- import { useCallback as useCallback5, useEffect as useEffect9, useMemo as useMemo6, useState as useState5 } from "react";
4466
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
4589
+ import { useCallback as useCallback5, useEffect as useEffect10, useMemo as useMemo6, useState as useState5 } from "react";
4590
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
4467
4591
  function buildDoneSummary(node) {
4468
4592
  const display = node.display;
4469
4593
  if (display?.progress_total != null) {
@@ -4493,7 +4617,7 @@ function WorkflowSubworkflowContainer({
4493
4617
  return node.content_blocks[node.content_blocks.length - 1]?.preview ?? "";
4494
4618
  }, [node.content_blocks]);
4495
4619
  const [expanded, setExpanded] = useState5(effectivelyRunning);
4496
- useEffect9(() => {
4620
+ useEffect10(() => {
4497
4621
  if (effectivelyRunning) setExpanded(true);
4498
4622
  else if (effectivelyDone) setExpanded(false);
4499
4623
  }, [effectivelyRunning, effectivelyDone]);
@@ -4514,7 +4638,7 @@ function WorkflowSubworkflowContainer({
4514
4638
  "data-container-scope": "subworkflow",
4515
4639
  style: depthStyle,
4516
4640
  children: [
4517
- showShellLine ? /* @__PURE__ */ jsx14(
4641
+ showShellLine ? /* @__PURE__ */ jsx15(
4518
4642
  WorkflowContainerLine,
4519
4643
  {
4520
4644
  variant: "root",
@@ -4534,7 +4658,7 @@ function WorkflowSubworkflowContainer({
4534
4658
  verbose
4535
4659
  }
4536
4660
  ) : null,
4537
- expanded && children ? /* @__PURE__ */ jsx14(
4661
+ expanded && children ? /* @__PURE__ */ jsx15(
4538
4662
  "div",
4539
4663
  {
4540
4664
  className: "lax-workflow-subworkflow-boundary",
@@ -4543,7 +4667,7 @@ function WorkflowSubworkflowContainer({
4543
4667
  children
4544
4668
  }
4545
4669
  ) : null,
4546
- !expanded && children ? /* @__PURE__ */ jsx14(
4670
+ !expanded && children ? /* @__PURE__ */ jsx15(
4547
4671
  WorkflowExpandTrigger,
4548
4672
  {
4549
4673
  summary: `${node.title} \xB7 ${effectivelyDone ? buildDoneSummary(node) : "running"}`,
@@ -4562,7 +4686,7 @@ function WorkflowSubworkflowContainer({
4562
4686
  }
4563
4687
 
4564
4688
  // src/view/workflow/WorkflowChrome.tsx
4565
- import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
4689
+ import { Fragment as Fragment2, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
4566
4690
  function collectChildContainers(containersById, parentId) {
4567
4691
  return Object.values(containersById).filter((node) => node.parent_container_id === parentId).sort((a, b) => a.scope_key.localeCompare(b.scope_key));
4568
4692
  }
@@ -4581,7 +4705,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
4581
4705
  const subtreeActive = options?.subtreeHasActiveWork ?? subtreeHasActiveWork(containerTree.containersById, node.container_id);
4582
4706
  switch (node.scope) {
4583
4707
  case "aggregate":
4584
- return /* @__PURE__ */ jsx15(
4708
+ return /* @__PURE__ */ jsx16(
4585
4709
  WorkflowAggregateContainer,
4586
4710
  {
4587
4711
  node,
@@ -4593,7 +4717,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
4593
4717
  node.container_id
4594
4718
  );
4595
4719
  case "branch":
4596
- return /* @__PURE__ */ jsx15(
4720
+ return /* @__PURE__ */ jsx16(
4597
4721
  WorkflowRouteContainer,
4598
4722
  {
4599
4723
  node,
@@ -4604,7 +4728,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
4604
4728
  node.container_id
4605
4729
  );
4606
4730
  default:
4607
- return /* @__PURE__ */ jsx15(
4731
+ return /* @__PURE__ */ jsx16(
4608
4732
  WorkflowStageContainer,
4609
4733
  {
4610
4734
  node,
@@ -4620,7 +4744,7 @@ function renderLeafContainer(node, depth, containerTree, loopTreesBySessionId, v
4620
4744
  }
4621
4745
  function renderItemSiblings(items, groupKey, depth, containerTree, loopTreesBySessionId, viewProps) {
4622
4746
  if (items.length >= 2) {
4623
- return /* @__PURE__ */ jsx15(
4747
+ return /* @__PURE__ */ jsx16(
4624
4748
  WorkflowParallelGroup,
4625
4749
  {
4626
4750
  groupKey,
@@ -4668,7 +4792,7 @@ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProp
4668
4792
  );
4669
4793
  const hasNested = collectChildContainers(containerTree.containersById, node.container_id).length > 0;
4670
4794
  if (node.scope === "subworkflow") {
4671
- return /* @__PURE__ */ jsx15(
4795
+ return /* @__PURE__ */ jsx16(
4672
4796
  WorkflowSubworkflowContainer,
4673
4797
  {
4674
4798
  node,
@@ -4698,7 +4822,7 @@ function renderContainerNode(node, containerTree, loopTreesBySessionId, viewProp
4698
4822
  viewProps,
4699
4823
  { suppressHostScopedLoop }
4700
4824
  ),
4701
- hasNested ? /* @__PURE__ */ jsx15("div", { className: "lax-workflow-container-children", children: nested }) : null
4825
+ hasNested ? /* @__PURE__ */ jsx16("div", { className: "lax-workflow-container-children", children: nested }) : null
4702
4826
  ]
4703
4827
  },
4704
4828
  node.container_id
@@ -4724,9 +4848,9 @@ function renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewP
4724
4848
  viewProps
4725
4849
  );
4726
4850
  if (as === "div") {
4727
- return /* @__PURE__ */ jsx15("div", { className, role: "listitem", children: content }, node.container_id);
4851
+ return /* @__PURE__ */ jsx16("div", { className, role: "listitem", children: content }, node.container_id);
4728
4852
  }
4729
- return /* @__PURE__ */ jsx15("li", { className, children: content }, node.container_id);
4853
+ return /* @__PURE__ */ jsx16("li", { className, children: content }, node.container_id);
4730
4854
  }
4731
4855
  function WorkflowStageListPanel({
4732
4856
  stages,
@@ -4747,7 +4871,7 @@ function WorkflowStageListPanel({
4747
4871
  partition.totalDoneCount - partition.visibleDoneCount
4748
4872
  );
4749
4873
  return /* @__PURE__ */ jsxs10(Fragment2, { children: [
4750
- /* @__PURE__ */ jsx15("ol", { className: "lax-workflow-stage-list", "data-testid": "lax-workflow-stage-list", children: partition.visible.map(
4874
+ /* @__PURE__ */ jsx16("ol", { className: "lax-workflow-stage-list", "data-testid": "lax-workflow-stage-list", children: partition.visible.map(
4751
4875
  (node) => renderWorkflowStageRow(node, containerTree, loopTreesBySessionId, viewProps)
4752
4876
  ) }),
4753
4877
  partition.hiddenCount > 0 ? /* @__PURE__ */ jsxs10(
@@ -4766,7 +4890,7 @@ function WorkflowStageListPanel({
4766
4890
  ]
4767
4891
  }
4768
4892
  ) : null,
4769
- extraCompletedPages > 0 && partition.hiddenCount === 0 ? /* @__PURE__ */ jsx15(
4893
+ extraCompletedPages > 0 && partition.hiddenCount === 0 ? /* @__PURE__ */ jsx16(
4770
4894
  "button",
4771
4895
  {
4772
4896
  type: "button",
@@ -4802,7 +4926,7 @@ function renderWorkflowChildren(workflowNode, containerTree, loopTreesBySessionI
4802
4926
  (node) => node.scope !== "stage" && node.scope !== "item"
4803
4927
  );
4804
4928
  return /* @__PURE__ */ jsxs10(Fragment2, { children: [
4805
- stages.length > 0 ? /* @__PURE__ */ jsx15(
4929
+ stages.length > 0 ? /* @__PURE__ */ jsx16(
4806
4930
  WorkflowStageListPanel,
4807
4931
  {
4808
4932
  stages,
@@ -4827,7 +4951,7 @@ function WorkflowChrome({
4827
4951
  const viewProps = { virtualized, virtualizeThreshold, groupParallelTools };
4828
4952
  const rootNodes = containerTree.rootContainerIds.map((id) => containerTree.containersById[id]).filter((node) => node != null);
4829
4953
  if (rootNodes.length === 0) {
4830
- return /* @__PURE__ */ jsx15("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: /* @__PURE__ */ jsx15(
4954
+ return /* @__PURE__ */ jsx16("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: /* @__PURE__ */ jsx16(
4831
4955
  WorkflowContainerLine,
4832
4956
  {
4833
4957
  variant: "root",
@@ -4837,7 +4961,7 @@ function WorkflowChrome({
4837
4961
  }
4838
4962
  ) });
4839
4963
  }
4840
- return /* @__PURE__ */ jsx15("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: rootNodes.map((workflowNode) => /* @__PURE__ */ jsx15(
4964
+ return /* @__PURE__ */ jsx16("div", { className: "lax-workflow-chrome", "data-testid": "lax-workflow-chrome", children: rootNodes.map((workflowNode) => /* @__PURE__ */ jsx16(
4841
4965
  WorkflowRootContainer,
4842
4966
  {
4843
4967
  node: workflowNode,
@@ -4854,28 +4978,28 @@ function WorkflowChrome({
4854
4978
  }
4855
4979
 
4856
4980
  // src/view/workflow/WorkflowGlobalSpinner.tsx
4857
- import { useStore as useStore4 } from "zustand";
4858
- import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
4981
+ import { useStore as useStore5 } from "zustand";
4982
+ import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
4859
4983
  function WorkflowGlobalSpinner() {
4860
4984
  const store = useWorkflowSessionStoreApi();
4861
- const show = useStore4(store, (s) => shouldShowGlobalSpinner(s.state));
4985
+ const show = useStore5(store, (s) => shouldShowGlobalSpinner(s.state));
4862
4986
  if (!show) return null;
4863
- return /* @__PURE__ */ jsx16(
4987
+ return /* @__PURE__ */ jsx17(
4864
4988
  "div",
4865
4989
  {
4866
4990
  className: "lax-workflow-global-spinner lax-spinner-container",
4867
4991
  "data-testid": "lax-workflow-global-spinner",
4868
4992
  role: "status",
4869
4993
  children: /* @__PURE__ */ jsxs11("span", { className: "lax-spinner lax-workflow-global-spinner__inner", children: [
4870
- /* @__PURE__ */ jsx16("span", { className: "lax-spinner-frame", children: "\u280B" }),
4871
- /* @__PURE__ */ jsx16("span", { className: "lax-spinner-verb lax-spinner-verb--shimmer", children: "Workflow" })
4994
+ /* @__PURE__ */ jsx17("span", { className: "lax-spinner-frame", children: "\u280B" }),
4995
+ /* @__PURE__ */ jsx17("span", { className: "lax-spinner-verb lax-spinner-verb--shimmer", children: "Workflow" })
4872
4996
  ] })
4873
4997
  }
4874
4998
  );
4875
4999
  }
4876
5000
 
4877
5001
  // src/view/workflow/WorkflowSessionBanners.tsx
4878
- import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
5002
+ import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
4879
5003
  function WorkflowSessionBanners({ state }) {
4880
5004
  const banner = resolveWorkflowBanner(state);
4881
5005
  const { display } = state;
@@ -4884,7 +5008,7 @@ function WorkflowSessionBanners({ state }) {
4884
5008
  }
4885
5009
  const showTransportError = (display.projectionStatus === "error" || state.internalErrors.length > 0) && banner.kind !== "projection_degraded" && banner.kind !== "authority_error";
4886
5010
  return /* @__PURE__ */ jsxs12(Fragment3, { children: [
4887
- banner.kind === "authority_error" ? /* @__PURE__ */ jsx17(
5011
+ banner.kind === "authority_error" ? /* @__PURE__ */ jsx18(
4888
5012
  "div",
4889
5013
  {
4890
5014
  className: "lax-workflow-banner lax-workflow-banner--authority-error",
@@ -4893,7 +5017,7 @@ function WorkflowSessionBanners({ state }) {
4893
5017
  children: banner.message
4894
5018
  }
4895
5019
  ) : null,
4896
- banner.kind === "projection_degraded" ? /* @__PURE__ */ jsx17(
5020
+ banner.kind === "projection_degraded" ? /* @__PURE__ */ jsx18(
4897
5021
  "div",
4898
5022
  {
4899
5023
  className: "lax-workflow-banner lax-workflow-banner--projection-degraded",
@@ -4902,7 +5026,7 @@ function WorkflowSessionBanners({ state }) {
4902
5026
  children: banner.message
4903
5027
  }
4904
5028
  ) : null,
4905
- banner.kind === "snapshot_unavailable" ? /* @__PURE__ */ jsx17(
5029
+ banner.kind === "snapshot_unavailable" ? /* @__PURE__ */ jsx18(
4906
5030
  "div",
4907
5031
  {
4908
5032
  className: "lax-workflow-banner lax-workflow-banner--snapshot-unavailable",
@@ -4911,7 +5035,7 @@ function WorkflowSessionBanners({ state }) {
4911
5035
  children: banner.message
4912
5036
  }
4913
5037
  ) : null,
4914
- banner.kind === "partial_success" ? /* @__PURE__ */ jsx17(
5038
+ banner.kind === "partial_success" ? /* @__PURE__ */ jsx18(
4915
5039
  "div",
4916
5040
  {
4917
5041
  className: "lax-workflow-banner lax-workflow-banner--partial-success",
@@ -4927,16 +5051,16 @@ function WorkflowSessionBanners({ state }) {
4927
5051
  "data-testid": "lax-workflow-closeout-warning",
4928
5052
  role: "status",
4929
5053
  children: [
4930
- /* @__PURE__ */ jsx17("strong", { children: "Workflow forced closeout" }),
5054
+ /* @__PURE__ */ jsx18("strong", { children: "Workflow forced closeout" }),
4931
5055
  /* @__PURE__ */ jsxs12("span", { children: [
4932
5056
  " ",
4933
5057
  "\u2014 stream ended with unresolved child scopes; not a healthy completion."
4934
5058
  ] }),
4935
- display.openChildIdentities.length > 0 ? /* @__PURE__ */ jsx17("ul", { className: "lax-workflow-closeout-warning__scopes", children: display.openChildIdentities.map((identity) => /* @__PURE__ */ jsx17("li", { children: identity }, identity)) }) : null
5059
+ display.openChildIdentities.length > 0 ? /* @__PURE__ */ jsx18("ul", { className: "lax-workflow-closeout-warning__scopes", children: display.openChildIdentities.map((identity) => /* @__PURE__ */ jsx18("li", { children: identity }, identity)) }) : null
4936
5060
  ]
4937
5061
  }
4938
5062
  ) : null,
4939
- banner.kind === "awaiting_authority" ? /* @__PURE__ */ jsx17(
5063
+ banner.kind === "awaiting_authority" ? /* @__PURE__ */ jsx18(
4940
5064
  "div",
4941
5065
  {
4942
5066
  className: "lax-workflow-banner lax-workflow-banner--awaiting",
@@ -4945,7 +5069,7 @@ function WorkflowSessionBanners({ state }) {
4945
5069
  children: banner.message
4946
5070
  }
4947
5071
  ) : null,
4948
- banner.kind === "sync_failed" ? /* @__PURE__ */ jsx17(
5072
+ banner.kind === "sync_failed" ? /* @__PURE__ */ jsx18(
4949
5073
  "div",
4950
5074
  {
4951
5075
  className: "lax-workflow-banner lax-workflow-banner--sync-failed",
@@ -4954,7 +5078,7 @@ function WorkflowSessionBanners({ state }) {
4954
5078
  children: banner.message
4955
5079
  }
4956
5080
  ) : null,
4957
- showTransportError ? /* @__PURE__ */ jsx17(
5081
+ showTransportError ? /* @__PURE__ */ jsx18(
4958
5082
  "div",
4959
5083
  {
4960
5084
  className: "lax-workflow-error",
@@ -4968,7 +5092,7 @@ function WorkflowSessionBanners({ state }) {
4968
5092
 
4969
5093
  // src/view/workflow/WorkflowTaskListFooter.tsx
4970
5094
  import { useMemo as useMemo8, useState as useState7 } from "react";
4971
- import { useStore as useStore5 } from "zustand";
5095
+ import { useStore as useStore6 } from "zustand";
4972
5096
 
4973
5097
  // src/view/workflow/workflowTaskListMerge.ts
4974
5098
  var STATUS_ORDER = {
@@ -5039,16 +5163,16 @@ function loopSessionDisplayLabel(loopSessionId) {
5039
5163
  }
5040
5164
 
5041
5165
  // src/view/workflow/WorkflowTaskListFooter.tsx
5042
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
5166
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
5043
5167
  function MessageResponse({ children }) {
5044
5168
  return /* @__PURE__ */ jsxs13("div", { className: "lax-message-response", children: [
5045
- /* @__PURE__ */ jsx18("span", { className: "lax-message-response__marker", children: "\u23BF " }),
5046
- /* @__PURE__ */ jsx18("span", { className: "lax-message-response__content", children })
5169
+ /* @__PURE__ */ jsx19("span", { className: "lax-message-response__marker", children: "\u23BF " }),
5170
+ /* @__PURE__ */ jsx19("span", { className: "lax-message-response__content", children })
5047
5171
  ] });
5048
5172
  }
5049
5173
  function WorkflowTaskListFooter() {
5050
5174
  const store = useWorkflowSessionStoreApi();
5051
- const state = useStore5(store, (s) => s.state);
5175
+ const state = useStore6(store, (s) => s.state);
5052
5176
  const [expanded, setExpanded] = useState7(false);
5053
5177
  const footerState = useMemo8(() => deriveWorkflowTaskFooterState(state), [state]);
5054
5178
  const visible = shouldShowWorkflowTaskListFooter(state);
@@ -5068,18 +5192,18 @@ function WorkflowTaskListFooter() {
5068
5192
  onClick: () => setExpanded((v) => !v),
5069
5193
  children: [
5070
5194
  label,
5071
- /* @__PURE__ */ jsx18("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
5195
+ /* @__PURE__ */ jsx19("span", { className: "lax-task-list-footer__hint", children: expanded ? " \xB7 \u2191 to hide" : " \xB7 \u2193 to view" })
5072
5196
  ]
5073
5197
  }
5074
5198
  ),
5075
- expanded ? /* @__PURE__ */ jsx18(MessageResponse, { children: /* @__PURE__ */ jsx18("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs13(
5199
+ expanded ? /* @__PURE__ */ jsx19(MessageResponse, { children: /* @__PURE__ */ jsx19("div", { className: "lax-workflow-task-list-groups", children: groups.map((group) => /* @__PURE__ */ jsxs13(
5076
5200
  "div",
5077
5201
  {
5078
5202
  className: "lax-workflow-task-list-group",
5079
5203
  "data-testid": `lax-workflow-task-group-${group.loopSessionId}`,
5080
5204
  children: [
5081
- /* @__PURE__ */ jsx18("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
5082
- /* @__PURE__ */ jsx18(TaskList, { tasks: group.tasks })
5205
+ /* @__PURE__ */ jsx19("div", { className: "lax-workflow-task-list-group-title", children: loopSessionDisplayLabel(group.loopSessionId) }),
5206
+ /* @__PURE__ */ jsx19(TaskList, { tasks: group.tasks })
5083
5207
  ]
5084
5208
  },
5085
5209
  group.loopSessionId
@@ -5088,8 +5212,8 @@ function WorkflowTaskListFooter() {
5088
5212
  }
5089
5213
 
5090
5214
  // src/view/workflow/WorkflowSession.tsx
5091
- import { useStore as useStore6 } from "zustand";
5092
- import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
5215
+ import { useStore as useStore7 } from "zustand";
5216
+ import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
5093
5217
  function WorkflowSession({
5094
5218
  source,
5095
5219
  authoritySource,
@@ -5118,7 +5242,7 @@ function WorkflowSession({
5118
5242
  onDisplayStreamEnded,
5119
5243
  children
5120
5244
  }) {
5121
- const storeRef = useRef4(null);
5245
+ const storeRef = useRef5(null);
5122
5246
  if (storeRef.current === null) {
5123
5247
  const initialState = initialEvents && initialEvents.length > 0 ? reduceWorkflowEvents(initialEvents, { tierOverrides }) : void 0;
5124
5248
  storeRef.current = createWorkflowSessionStore(initialState, {
@@ -5130,15 +5254,15 @@ function WorkflowSession({
5130
5254
  }
5131
5255
  const store = storeRef.current;
5132
5256
  useWorkflowAuthorityBinding(store, authoritySource, { onAuthorityTerminal });
5133
- const streamEnded = useStore6(store, (s) => s.state.display.streamEnded);
5134
- const authorityStatus = useStore6(store, (s) => s.state.authority?.status);
5257
+ const streamEnded = useStore7(store, (s) => s.state.display.streamEnded);
5258
+ const authorityStatus = useStore7(store, (s) => s.state.authority?.status);
5135
5259
  useWorkflowAwaitingAuthorityTimers(store, streamEnded, authorityStatus);
5136
- useEffect10(() => {
5260
+ useEffect11(() => {
5137
5261
  if (snapshotBootstrap) {
5138
5262
  store.getState().hydrateFromSnapshot(snapshotBootstrap, { preserveAuthority: true });
5139
5263
  }
5140
5264
  }, [snapshotBootstrap, store]);
5141
- useEffect10(() => {
5265
+ useEffect11(() => {
5142
5266
  if (snapshotHydrateFailed) {
5143
5267
  store.getState().markSnapshotHydrateFailed();
5144
5268
  }
@@ -5180,15 +5304,15 @@ function WorkflowSession({
5180
5304
  workspaceRoot
5181
5305
  ]
5182
5306
  );
5183
- const sessionState = useStore6(store, (s) => s.state);
5307
+ const sessionState = useStore7(store, (s) => s.state);
5184
5308
  const containerTree = sessionState.containerTree;
5185
5309
  const loopTreesBySessionId = sessionState.loopTreesBySessionId;
5186
- const onErrorRef = useRef4(onError);
5187
- const onDisplayStreamEndedRef = useRef4(onDisplayStreamEnded);
5188
- const streamAbortRef = useRef4(null);
5310
+ const onErrorRef = useRef5(onError);
5311
+ const onDisplayStreamEndedRef = useRef5(onDisplayStreamEnded);
5312
+ const streamAbortRef = useRef5(null);
5189
5313
  onErrorRef.current = onError;
5190
5314
  onDisplayStreamEndedRef.current = onDisplayStreamEnded;
5191
- useEffect10(() => {
5315
+ useEffect11(() => {
5192
5316
  if (!source) return void 0;
5193
5317
  const controller = new AbortController();
5194
5318
  streamAbortRef.current = controller;
@@ -5210,20 +5334,20 @@ function WorkflowSession({
5210
5334
  streamAbortRef.current = null;
5211
5335
  };
5212
5336
  }, [source, store]);
5213
- useEffect10(() => {
5337
+ useEffect11(() => {
5214
5338
  if (isAuthorityTerminalStatus(authorityStatus)) {
5215
5339
  streamAbortRef.current?.abort();
5216
5340
  }
5217
5341
  }, [authorityStatus]);
5218
- return /* @__PURE__ */ jsx19(WorkflowSessionStoreContext.Provider, { value: store, children: /* @__PURE__ */ jsx19(WorkflowScaleContext.Provider, { value: scaleOptions, children: /* @__PURE__ */ jsx19(WorkflowLoopReplayProvider, { onRequestLoopReplay, children: /* @__PURE__ */ jsx19(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx19(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx19(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx19(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx19(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs14(
5342
+ return /* @__PURE__ */ jsx20(WorkflowSessionStoreContext.Provider, { value: store, children: /* @__PURE__ */ jsx20(WorkflowScaleContext.Provider, { value: scaleOptions, children: /* @__PURE__ */ jsx20(WorkflowLoopReplayProvider, { onRequestLoopReplay, children: /* @__PURE__ */ jsx20(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx20(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx20(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx20(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx20(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs14(
5219
5343
  "div",
5220
5344
  {
5221
5345
  className: "lax-agent-session lax-workflow-session",
5222
5346
  "data-testid": "lax-workflow-session",
5223
5347
  children: [
5224
- /* @__PURE__ */ jsx19(WorkflowSessionBanners, { state: sessionState }),
5225
- /* @__PURE__ */ jsx19(WorkflowGlobalSpinner, {}),
5226
- /* @__PURE__ */ jsx19(
5348
+ /* @__PURE__ */ jsx20(WorkflowSessionBanners, { state: sessionState }),
5349
+ /* @__PURE__ */ jsx20(WorkflowGlobalSpinner, {}),
5350
+ /* @__PURE__ */ jsx20(
5227
5351
  WorkflowChrome,
5228
5352
  {
5229
5353
  containerTree,
@@ -5233,7 +5357,7 @@ function WorkflowSession({
5233
5357
  groupParallelTools
5234
5358
  }
5235
5359
  ),
5236
- /* @__PURE__ */ jsx19(WorkflowTaskListFooter, {}),
5360
+ /* @__PURE__ */ jsx20(WorkflowTaskListFooter, {}),
5237
5361
  children
5238
5362
  ]
5239
5363
  }
@@ -5320,6 +5444,17 @@ function createControllableAuthoritySource(initial) {
5320
5444
  }
5321
5445
 
5322
5446
  // src/core/workflow/createPollingAuthoritySource.ts
5447
+ function isFatalAuthorityPollStatus(status) {
5448
+ return status === 404 || status === 410;
5449
+ }
5450
+ var AuthorityPollFatalError = class extends Error {
5451
+ status;
5452
+ constructor(status, message) {
5453
+ super(message ?? `authority poll fatal: ${status}`);
5454
+ this.name = "AuthorityPollFatalError";
5455
+ this.status = status;
5456
+ }
5457
+ };
5323
5458
  var DEFAULT_INTERVAL_MS = 2e3;
5324
5459
  function createPollingAuthoritySource(taskId, fetchFn, options) {
5325
5460
  let cached = options?.initialSnapshot ?? null;
@@ -5350,6 +5485,10 @@ function createPollingAuthoritySource(taskId, fetchFn, options) {
5350
5485
  if (isAuthorityTerminalStatus(snap.status)) {
5351
5486
  stopPoll();
5352
5487
  }
5488
+ } catch (err) {
5489
+ if (err instanceof AuthorityPollFatalError) {
5490
+ stopPoll();
5491
+ }
5353
5492
  } finally {
5354
5493
  pollInFlight = false;
5355
5494
  }
@@ -5422,7 +5561,7 @@ function buildTimelineEntries(rootIds, byId, options = {}) {
5422
5561
  }
5423
5562
 
5424
5563
  // src/view/nodes/SubAgentNode.tsx
5425
- import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
5564
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
5426
5565
  function SubAgentBlock({ nodeId }) {
5427
5566
  const node = useNodeTyped(nodeId, "subagent");
5428
5567
  const childIds = useChildren(nodeId);
@@ -5433,7 +5572,7 @@ function SubAgentBlock({ nodeId }) {
5433
5572
  "SubAgent: ",
5434
5573
  node.subagentId
5435
5574
  ] }),
5436
- /* @__PURE__ */ jsx20("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx20(TimelineItem, { nodeId: childId, registry }, childId)) })
5575
+ /* @__PURE__ */ jsx21("div", { className: "lax-subagent-block__children", children: childIds.map((childId) => /* @__PURE__ */ jsx21(TimelineItem, { nodeId: childId, registry }, childId)) })
5437
5576
  ] });
5438
5577
  }
5439
5578
 
@@ -5470,10 +5609,12 @@ export {
5470
5609
  Agent,
5471
5610
  AgentLoopView,
5472
5611
  AgentSession,
5612
+ AgentSessionBanners,
5473
5613
  AgentToolBody,
5474
5614
  ApplicationKindMismatchError,
5475
5615
  AskUserQuestion,
5476
5616
  AskUserQuestionToolBody,
5617
+ AuthorityPollFatalError,
5477
5618
  BOUNDED_DEFAULTS,
5478
5619
  Bash,
5479
5620
  BashToolBody,
@@ -5604,11 +5745,13 @@ export {
5604
5745
  getDisplayProjectionStatus,
5605
5746
  getMainStageIds,
5606
5747
  getNoopInteractionBus,
5748
+ hydrateAskFromSnapshot,
5607
5749
  hydrateWorkflowFromSnapshot,
5608
5750
  isAgentLoopReplayEventType,
5609
5751
  isAtOrBeforeProjectionCursor,
5610
5752
  isAuthorityTerminal,
5611
5753
  isAuthorityTerminalStatus,
5754
+ isFatalAuthorityPollStatus,
5612
5755
  isGitOperationCommand,
5613
5756
  isGroupableToolName,
5614
5757
  isMcpToolName,
@@ -5633,13 +5776,17 @@ export {
5633
5776
  resolveSseTransportPolicy,
5634
5777
  resolveToolBody,
5635
5778
  resolveWorkflowBanner,
5779
+ sessionTreeSnapshotToTree,
5636
5780
  shouldApplyGrouping,
5781
+ shouldShowAgentSpinner,
5637
5782
  shouldShowExploreDetail,
5638
5783
  shouldShowGlobalSpinner,
5639
5784
  shouldShowTaskListFooter,
5640
5785
  streamChunk,
5641
5786
  truncateCommand,
5642
5787
  useActiveSession,
5788
+ useAgentAuthority,
5789
+ useAgentAuthoritySubscribeFailed,
5643
5790
  useChildren,
5644
5791
  useInteractionBus,
5645
5792
  useInternalErrors,