@tangle-network/sandbox-ui 2.0.20 → 2.0.21

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.
@@ -1,4 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.js";
2
+ import { t as VirtualList } from "./VirtualList-DLh2JE0F.js";
3
+ import { t as useFollowTail } from "./use-follow-tail-Ckx5ZSV2.js";
2
4
  import { HoverCard, HoverCardContent, HoverCardTrigger } from "@tangle-network/ui/primitives";
3
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
6
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
@@ -6,212 +8,6 @@ import { Brain, Settings, Timer } from "lucide-react";
6
8
  import { formatDuration, getToolCategory, getToolDisplayMetadata } from "@tangle-network/ui/utils";
7
9
  import { AgentTimelineColumn, AgentTimelineStep, SpeakerHeader, agentTimelineStepKind } from "@tangle-network/ui/chat";
8
10
  import { ExpandedToolDetail, InlineThinkingItem, RunRowShell, RunStatusIcon, runStatus } from "@tangle-network/ui/run";
9
- // src/run/record/VirtualList.tsx
10
- /**
11
- * Renders only the rows near the viewport of `scroller`. Row heights are measured as they render; unmeasured
12
- * rows use `estimate`. Spacers keep the scroll height, so the browser's scroll anchoring holds the reading place.
13
- */
14
- function VirtualList({ items, keyOf, render, scroller, estimate = 96, overscan = 900, target }) {
15
- const heights = useRef(/* @__PURE__ */ new Map());
16
- const [version, setVersion] = useState(0);
17
- const [viewport, setViewport] = useState({
18
- top: 0,
19
- height: 900
20
- });
21
- const observer = useRef(null);
22
- const nodes = useRef(/* @__PURE__ */ new Map());
23
- const frame = useRef(0);
24
- const observe = (element) => {
25
- if (!observer.current && typeof ResizeObserver !== "undefined") observer.current = new ResizeObserver((entries) => {
26
- let changed = false;
27
- for (const entry of entries) {
28
- const key = nodes.current.get(entry.target);
29
- if (!key) continue;
30
- const height = Math.ceil(entry.borderBoxSize?.[0]?.blockSize ?? entry.target.offsetHeight);
31
- if (height > 0 && heights.current.get(key) !== height) {
32
- heights.current.set(key, height);
33
- changed = true;
34
- }
35
- }
36
- if (changed && !frame.current) frame.current = requestAnimationFrame(() => {
37
- frame.current = 0;
38
- setVersion((value) => value + 1);
39
- });
40
- });
41
- observer.current?.observe(element);
42
- };
43
- useEffect(() => () => {
44
- cancelAnimationFrame(frame.current);
45
- observer.current?.disconnect();
46
- observer.current = null;
47
- nodes.current.clear();
48
- }, []);
49
- useEffect(() => {
50
- const element = scroller.current;
51
- if (!element) return;
52
- let frame = 0;
53
- const update = () => {
54
- frame = 0;
55
- setViewport((current) => current.top === element.scrollTop && current.height === element.clientHeight ? current : {
56
- top: element.scrollTop,
57
- height: element.clientHeight
58
- });
59
- };
60
- const schedule = () => {
61
- if (!frame) frame = requestAnimationFrame(update);
62
- };
63
- update();
64
- element.addEventListener("scroll", schedule, { passive: true });
65
- const resize = new ResizeObserver(schedule);
66
- resize.observe(element);
67
- return () => {
68
- cancelAnimationFrame(frame);
69
- element.removeEventListener("scroll", schedule);
70
- resize.disconnect();
71
- };
72
- }, [scroller]);
73
- const offsets = useMemo(() => {
74
- const result = new Float64Array(items.length + 1);
75
- const guess = typeof estimate === "function" ? estimate : () => estimate;
76
- for (let i = 0; i < items.length; i++) result[i + 1] = result[i] + (heights.current.get(keyOf(items[i])) ?? guess(items[i]));
77
- return result;
78
- }, [
79
- items,
80
- keyOf,
81
- estimate,
82
- version
83
- ]);
84
- const find = (y) => {
85
- let low = 0;
86
- let high = items.length;
87
- while (low < high) {
88
- const mid = low + high >> 1;
89
- if (offsets[mid + 1] <= y) low = mid + 1;
90
- else high = mid;
91
- }
92
- return low;
93
- };
94
- const start = Math.max(0, find(viewport.top - overscan));
95
- const end = Math.min(items.length, find(viewport.top + viewport.height + overscan) + 1);
96
- const scrollTo = useCallback((key, align) => {
97
- const element = scroller.current;
98
- const index = items.findIndex((item) => keyOf(item) === key);
99
- if (!element || index < 0) return false;
100
- const top = offsets[index];
101
- const bottom = offsets[index + 1];
102
- if (align || top < element.scrollTop || bottom > element.scrollTop + element.clientHeight) element.scrollTop = Math.max(0, top - 8);
103
- return true;
104
- }, [
105
- items,
106
- keyOf,
107
- offsets,
108
- scroller
109
- ]);
110
- const pending = useRef(null);
111
- const rows = useRef(/* @__PURE__ */ new Map());
112
- const settle = () => {
113
- const goal = pending.current;
114
- if (!goal) return;
115
- if (performance.now() > goal.until) {
116
- pending.current = null;
117
- return;
118
- }
119
- if (!scrollTo(goal.key, true)) return;
120
- const row = rows.current.get(goal.key);
121
- if (row?.isConnected) {
122
- const box = row.getBoundingClientRect();
123
- if (box.top < 72 || box.top > window.innerHeight - 96) window.scrollBy({ top: box.top - 120 });
124
- }
125
- };
126
- useLayoutEffect(() => {
127
- pending.current = target ? {
128
- key: target,
129
- until: performance.now() + 2e3
130
- } : null;
131
- settle();
132
- }, [target]);
133
- useLayoutEffect(settle, [
134
- version,
135
- viewport,
136
- items
137
- ]);
138
- useEffect(() => {
139
- const element = scroller.current;
140
- if (!element) return;
141
- const release = (event) => {
142
- const key = event instanceof KeyboardEvent && event.target === element && !event.defaultPrevented ? event.key : "";
143
- pending.current = items.length && (key === "Home" || key === "End") ? {
144
- key: keyOf(items[key === "Home" ? 0 : items.length - 1]),
145
- until: performance.now() + 2e3
146
- } : null;
147
- if (pending.current) {
148
- event.preventDefault();
149
- settle();
150
- }
151
- };
152
- element.addEventListener("wheel", release, { passive: true });
153
- element.addEventListener("touchstart", release, { passive: true });
154
- element.addEventListener("pointerdown", release);
155
- element.addEventListener("keydown", release);
156
- return () => {
157
- element.removeEventListener("wheel", release);
158
- element.removeEventListener("touchstart", release);
159
- element.removeEventListener("pointerdown", release);
160
- element.removeEventListener("keydown", release);
161
- };
162
- }, [
163
- scroller,
164
- items,
165
- keyOf,
166
- offsets
167
- ]);
168
- const bind = (key) => (element) => {
169
- if (!element) {
170
- rows.current.delete(key);
171
- return;
172
- }
173
- rows.current.set(key, element);
174
- for (const [node, value] of nodes.current) if (value === key && node !== element) {
175
- observer.current?.unobserve(node);
176
- nodes.current.delete(node);
177
- }
178
- if (!nodes.current.has(element)) {
179
- nodes.current.set(element, key);
180
- observe(element);
181
- }
182
- };
183
- useEffect(() => {
184
- for (const node of [...nodes.current.keys()]) if (!node.isConnected) {
185
- observer.current?.unobserve(node);
186
- nodes.current.delete(node);
187
- }
188
- });
189
- return /* @__PURE__ */ jsxs(Fragment, { children: [
190
- /* @__PURE__ */ jsx("div", {
191
- style: {
192
- height: offsets[start],
193
- transitionProperty: "none"
194
- },
195
- "aria-hidden": "true"
196
- }),
197
- items.slice(start, end).map((item, i) => {
198
- const key = keyOf(item);
199
- return /* @__PURE__ */ jsx("div", {
200
- ref: bind(key),
201
- className: "virtual-row",
202
- style: { display: "flow-root" },
203
- children: render(item, start + i)
204
- }, key);
205
- }),
206
- /* @__PURE__ */ jsx("div", {
207
- style: {
208
- height: offsets[items.length] - offsets[end],
209
- transitionProperty: "none"
210
- },
211
- "aria-hidden": "true"
212
- })
213
- ] });
214
- }
215
11
  // src/run/record/model.ts
216
12
  var model_exports = /* @__PURE__ */ __exportAll({
217
13
  agentState: () => agentState,
@@ -1330,27 +1126,12 @@ function Conversation({ index, actor, cutoff, query, category, selected, openSou
1330
1126
  return map;
1331
1127
  }, [rows]);
1332
1128
  const retained = (index.byActor.get(actor) ?? []).some((item) => !isLifecycle(item) && (item.detail.role || textOf(item) || callsOf(item).length));
1333
- const targetRef = useRef(target);
1334
- targetRef.current = target;
1335
- const following = useRef(false);
1336
- const followRef = useRef(follow);
1337
- followRef.current = follow;
1338
- useLayoutEffect(() => {
1339
- following.current = follow && !targetRef.current;
1340
- if (scroll.current && !targetRef.current) scroll.current.scrollTop = follow ? scroll.current.scrollHeight : 0;
1341
- }, [actor]);
1342
- useLayoutEffect(() => {
1343
- if (!follow) following.current = false;
1344
- }, [follow]);
1345
- useEffect(() => {
1346
- const element = scroll.current;
1347
- if (!element) return;
1348
- const track = () => {
1349
- following.current = followRef.current && element.scrollHeight - element.scrollTop - element.clientHeight < 24;
1350
- };
1351
- element.addEventListener("scroll", track, { passive: true });
1352
- return () => element.removeEventListener("scroll", track);
1353
- }, []);
1129
+ const { following } = useFollowTail(scroll, {
1130
+ identity: actor,
1131
+ content: rows,
1132
+ follow,
1133
+ target
1134
+ });
1354
1135
  const arrival = useRef({ actor });
1355
1136
  useLayoutEffect(() => {
1356
1137
  const tail = rows.at(-1), event = tail?.kind === "event" ? tail.event : tail?.events.at(-1), entry = tail?.kind === "event" ? tail.event.id : tail?.events[0]?.id;
@@ -1361,11 +1142,8 @@ function Conversation({ index, actor, cutoff, query, category, selected, openSou
1361
1142
  };
1362
1143
  const element = scroll.current;
1363
1144
  if (!element || !following.current) return;
1364
- element.scrollTop = element.scrollHeight;
1365
- let frames = 0;
1366
- let frame = requestAnimationFrame(function pin() {
1145
+ const frame = requestAnimationFrame(() => {
1367
1146
  if (!following.current) return;
1368
- element.scrollTop = element.scrollHeight;
1369
1147
  const row = entry && element.querySelector(`[data-entry="${CSS.escape(entry)}"]`);
1370
1148
  if (animate && row) {
1371
1149
  animate = false;
@@ -1375,7 +1153,6 @@ function Conversation({ index, actor, cutoff, query, category, selected, openSou
1375
1153
  easing: style.getPropertyValue("--ease-expo").trim() || "linear"
1376
1154
  });
1377
1155
  }
1378
- if (++frames < 20) frame = requestAnimationFrame(pin);
1379
1156
  });
1380
1157
  return () => cancelAnimationFrame(frame);
1381
1158
  }, [rows]);
@@ -1594,4 +1371,4 @@ function AgentTree({ index, actor, cutoff, select }) {
1594
1371
  })
1595
1372
  });
1596
1373
  }
1597
- export { model_exports as a, indexRecord as i, withPrompts as n, VirtualList as o, Conversation as r, AgentTree as t };
1374
+ export { model_exports as a, indexRecord as i, withPrompts as n, Conversation as r, AgentTree as t };
@@ -1,4 +1,4 @@
1
- import { t as AgentStreamOpener } from "../agent-stream-CBZ2fXJ2.js";
1
+ import { t as AgentStreamOpener } from "../event-stream-DNEbvx4s.js";
2
2
  /** Keep one transport identity per authenticated account; changing it discards the previous view. */
3
3
  export interface LiveRunTransport {
4
4
  fetch?: typeof globalThis.fetch;
package/dist/run/live.js CHANGED
@@ -1,7 +1,7 @@
1
- import { t as WorkspaceFilesPane } from "../workspace-files-pane-DB9Nvwh4.js";
1
+ import { t as WorkspaceFilesPane } from "../workspace-files-pane-C0PeY6rY.js";
2
2
  import { t as DiffView } from "../diff-view-B9mNeTLF.js";
3
3
  import { t as CodeView } from "../code-view-BlhCS1SD.js";
4
- import { t as TraceWorkspace } from "../trace-workspace-C8dHRBDV.js";
4
+ import { a as useEventStream, t as TraceWorkspace } from "../trace-workspace-DkakKRLV.js";
5
5
  import { useDocument } from "../hooks/use-document.js";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useEffect, useMemo, useRef, useState } from "react";
@@ -180,32 +180,30 @@ function LiveRunView({ api, transport }) {
180
180
  const [rendered, setRendered] = useState(0);
181
181
  const [agent, setAgent] = useState();
182
182
  const [conversationOpen, setConversationOpen] = useState(true);
183
- useEffect(() => {
184
- const source = transport?.openEventSource ? transport.openEventSource(`${api}/events`, setConnected) : new EventSource(`${api}/events`);
185
- source.addEventListener("snapshot", (message) => {
186
- try {
187
- const frame = JSON.parse(message.data);
188
- if (!Array.isArray(frame.snapshots)) throw new Error("Invalid snapshot stream");
189
- setSnapshots((prior) => {
190
- const byRoot = new Map((frame.reset ? [] : prior).map((snapshot) => [snapshot.root, snapshot]));
191
- for (const snapshot of frame.snapshots) byRoot.set(snapshot.root, snapshot);
192
- return [...byRoot.values()];
193
- });
194
- setRendered(frame.sequence);
195
- setError(null);
196
- setConnected(true);
197
- } catch (cause) {
198
- setError(String(cause));
199
- }
200
- });
201
- source.addEventListener("unavailable", () => setError("The run snapshot is unavailable"));
202
- source.addEventListener("end", () => {
203
- source.close();
204
- setEnded(true);
205
- });
206
- if (!transport?.openEventSource) source.onerror = () => setConnected(false);
207
- return () => source.close();
208
- }, [api, transport?.openEventSource]);
183
+ useEventStream(`${api}/events`, {
184
+ opener: transport?.openEventSource,
185
+ onLiveChange: setConnected,
186
+ events: {
187
+ snapshot: (message) => {
188
+ try {
189
+ const frame = JSON.parse(message.data);
190
+ if (!Array.isArray(frame.snapshots)) throw new Error("Invalid snapshot stream");
191
+ setSnapshots((prior) => {
192
+ const byRoot = new Map((frame.reset ? [] : prior).map((snapshot) => [snapshot.root, snapshot]));
193
+ for (const snapshot of frame.snapshots) byRoot.set(snapshot.root, snapshot);
194
+ return [...byRoot.values()];
195
+ });
196
+ setRendered(frame.sequence);
197
+ setError(null);
198
+ setConnected(true);
199
+ } catch (cause) {
200
+ setError(String(cause));
201
+ }
202
+ },
203
+ unavailable: () => setError("The run snapshot is unavailable"),
204
+ end: () => setEnded(true)
205
+ }
206
+ });
209
207
  const runs = snapshots.flatMap((snapshot, root) => snapshot.supervisors.map((run) => ({
210
208
  snapshot,
211
209
  root,
@@ -1,2 +1,2 @@
1
- import { a as ConversationProps, c as ToolCall, d as ConversationEvent, f as ConversationNode, i as Conversation, l as indexRecord, n as VirtualList, o as EventFlag, p as ConversationRecord, r as withPrompts, s as RecordIndex, t as AgentTree, u as model_d_exports } from "../index-BYkigpIM.js";
1
+ import { a as ConversationProps, c as ToolCall, d as ConversationEvent, f as ConversationNode, i as Conversation, l as indexRecord, n as VirtualList, o as EventFlag, p as ConversationRecord, r as withPrompts, s as RecordIndex, t as AgentTree, u as model_d_exports } from "../index-1qlc9-S2.js";
2
2
  export { AgentTree, Conversation, type ConversationEvent, type ConversationNode, type ConversationProps, type ConversationRecord, type EventFlag, type RecordIndex, type ToolCall, VirtualList, indexRecord, model_d_exports as recordModel, withPrompts };
@@ -1 +1,3 @@
1
- export { t as AgentTree, r as Conversation, o as VirtualList, i as indexRecord, a as recordModel, n as withPrompts } from "../record-Cwyo2oHb.js";
1
+ import { t as VirtualList } from "../VirtualList-DLh2JE0F.js";
2
+ import { a as model_exports, i as indexRecord, n as withPrompts, r as Conversation, t as AgentTree } from "../record-CnxPRyxC.js";
3
+ export { AgentTree, Conversation, VirtualList, indexRecord, model_exports as recordModel, withPrompts };
package/dist/run.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as ToolCallData, B as taskStatus, C as RunStatusIcon, D as TaskListItem, E as TaskList, F as parseToolEvent, G as TraceWorkspaceProps, H as LatestEventsProps, I as runStatus, J as traceSpanMark, K as TraceWaterfall, L as showsTaskPriority, M as ToolCallFeedProps, N as ToolCallStatus, O as TaskListProps, P as ToolCallType, R as statusMeta, S as RunStatus, T as StatusMeta, U as TraceEvent, V as LatestEvents, W as TraceWorkspace, _ as RunPhaseList, a as ApprovalPlanStep, b as RunRowStatus, c as FeedSegment, d as InlineToolItem, f as InlineToolItemProps, g as RunPhase, h as RunGroupProps, i as ApprovalDiffSummary, j as ToolCallFeed, k as TaskStatus, l as InlineThinkingItem, m as RunGroup, n as ApprovalCardProps, o as ExpandedToolDetail, p as LiveDuration, q as TraceWaterfallProps, r as ApprovalDiffFile, s as ExpandedToolDetailProps, t as ApprovalCard, u as InlineThinkingItemProps, v as RunRowShell, w as RunStatusIconProps, x as RunRowStatusDot, y as RunRowShellProps, z as taskProgress } from "./index-ByNsP4VC.js";
2
- import { a as AgentTranscriptEntry, i as AgentStreamState, n as AgentStreamRow, o as foldAgentRows, r as AgentStreamSource, s as useAgentStream, t as AgentStreamOpener } from "./agent-stream-CBZ2fXJ2.js";
3
- import { a as ConversationProps, c as ToolCall, d as ConversationEvent, f as ConversationNode, i as Conversation, l as indexRecord, n as VirtualList, o as EventFlag, p as ConversationRecord, r as withPrompts, s as RecordIndex, t as AgentTree, u as model_d_exports } from "./index-BYkigpIM.js";
4
- export { type AgentStreamOpener, type AgentStreamRow, type AgentStreamSource, type AgentStreamState, type AgentTranscriptEntry, AgentTree, ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, Conversation, type ConversationEvent, type ConversationNode, type ConversationProps, type ConversationRecord, type EventFlag, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, LatestEvents, type LatestEventsProps, LiveDuration, type RecordIndex, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, type RunStatus, RunStatusIcon, type RunStatusIconProps, type StatusMeta, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, type ToolCall, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, type TraceEvent, TraceWaterfall, type TraceWaterfallProps, TraceWorkspace, type TraceWorkspaceProps, VirtualList, foldAgentRows, indexRecord, parseToolEvent, model_d_exports as recordModel, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus, traceSpanMark, useAgentStream, withPrompts };
1
+ import { $ as traceSpanMark, A as ToolCallData, B as taskStatus, C as RunStatusIcon, D as TaskListItem, E as TaskList, F as parseToolEvent, G as TraceWorkspaceProps, H as LatestEventsProps, I as runStatus, J as AgentTranscriptEntry, K as AgentStreamRow, L as showsTaskPriority, M as ToolCallFeedProps, N as ToolCallStatus, O as TaskListProps, P as ToolCallType, Q as TraceWaterfallProps, R as statusMeta, S as RunStatus, T as StatusMeta, U as TraceEvent, V as LatestEvents, W as TraceWorkspace, X as useAgentStream, Y as foldAgentRows, Z as TraceWaterfall, _ as RunPhaseList, a as ApprovalPlanStep, b as RunRowStatus, c as FeedSegment, d as InlineToolItem, f as InlineToolItemProps, g as RunPhase, h as RunGroupProps, i as ApprovalDiffSummary, j as ToolCallFeed, k as TaskStatus, l as InlineThinkingItem, m as RunGroup, n as ApprovalCardProps, o as ExpandedToolDetail, p as LiveDuration, q as AgentStreamState, r as ApprovalDiffFile, s as ExpandedToolDetailProps, t as ApprovalCard, u as InlineThinkingItemProps, v as RunRowShell, w as RunStatusIconProps, x as RunRowStatusDot, y as RunRowShellProps, z as taskProgress } from "./index-DxMX-DlX.js";
2
+ import { i as useEventStream, n as AgentStreamSource, r as EventStreamHandlers, t as AgentStreamOpener } from "./event-stream-DNEbvx4s.js";
3
+ import { a as ConversationProps, c as ToolCall, d as ConversationEvent, f as ConversationNode, i as Conversation, l as indexRecord, n as VirtualList, o as EventFlag, p as ConversationRecord, r as withPrompts, s as RecordIndex, t as AgentTree, u as model_d_exports } from "./index-1qlc9-S2.js";
4
+ export { type AgentStreamOpener, type AgentStreamRow, type AgentStreamSource, type AgentStreamState, type AgentTranscriptEntry, AgentTree, ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, Conversation, type ConversationEvent, type ConversationNode, type ConversationProps, type ConversationRecord, type EventFlag, type EventStreamHandlers, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, LatestEvents, type LatestEventsProps, LiveDuration, type RecordIndex, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, type RunStatus, RunStatusIcon, type RunStatusIconProps, type StatusMeta, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, type ToolCall, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, type TraceEvent, TraceWaterfall, type TraceWaterfallProps, TraceWorkspace, type TraceWorkspaceProps, VirtualList, foldAgentRows, indexRecord, parseToolEvent, model_d_exports as recordModel, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus, traceSpanMark, useAgentStream, useEventStream, withPrompts };
package/dist/run.js CHANGED
@@ -1,4 +1,5 @@
1
- import { a as TraceWaterfall, i as useAgentStream, n as LatestEvents, o as traceSpanMark, r as foldAgentRows, t as TraceWorkspace } from "./trace-workspace-C8dHRBDV.js";
2
- import { a as model_exports, i as indexRecord, n as withPrompts, o as VirtualList, r as Conversation, t as AgentTree } from "./record-Cwyo2oHb.js";
1
+ import { t as VirtualList } from "./VirtualList-DLh2JE0F.js";
2
+ import { a as useEventStream, i as useAgentStream, n as LatestEvents, o as TraceWaterfall, r as foldAgentRows, s as traceSpanMark, t as TraceWorkspace } from "./trace-workspace-DkakKRLV.js";
3
+ import { a as model_exports, i as indexRecord, n as withPrompts, r as Conversation, t as AgentTree } from "./record-CnxPRyxC.js";
3
4
  import { ApprovalCard, ApprovalDiffSummary, ExpandedToolDetail, InlineThinkingItem, InlineToolItem, LiveDuration, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, TaskList, ToolCallFeed, parseToolEvent, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus } from "@tangle-network/ui/run";
4
- export { AgentTree, ApprovalCard, ApprovalDiffSummary, Conversation, ExpandedToolDetail, InlineThinkingItem, InlineToolItem, LatestEvents, LiveDuration, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, TaskList, ToolCallFeed, TraceWaterfall, TraceWorkspace, VirtualList, foldAgentRows, indexRecord, parseToolEvent, model_exports as recordModel, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus, traceSpanMark, useAgentStream, withPrompts };
5
+ export { AgentTree, ApprovalCard, ApprovalDiffSummary, Conversation, ExpandedToolDetail, InlineThinkingItem, InlineToolItem, LatestEvents, LiveDuration, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, TaskList, ToolCallFeed, TraceWaterfall, TraceWorkspace, VirtualList, foldAgentRows, indexRecord, parseToolEvent, model_exports as recordModel, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus, traceSpanMark, useAgentStream, useEventStream, withPrompts };
@@ -2,14 +2,16 @@ import { t as cn } from "./utils-BER_bdgE.js";
2
2
  import { i as modelBrandFor, t as ModelBrandStack } from "./model-brand-BQlarCIo.js";
3
3
  import { t as ProviderIcon } from "./provider-logo-CPKRPHcS.js";
4
4
  import { t as AgentGraph$1 } from "./AgentGraph-BGWx9hhS.js";
5
+ import { t as useFollowTail } from "./use-follow-tail-Ckx5ZSV2.js";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
- import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
7
+ import { useMemo, useReducer, useRef, useState } from "react";
7
8
  import { X } from "lucide-react";
8
9
  import { activityTiming, ambientStyle, syncAmbient } from "@tangle-network/charts/react/agent-graph/presentation";
9
10
  import { AgentTimeline } from "@tangle-network/ui/chat";
10
11
  import { InlineThinkingItem, runStatus } from "@tangle-network/ui/run";
11
12
  import { FlowWaterfall, toolGlyph } from "@tangle-network/charts/react";
12
13
  import { LatestEvents } from "@tangle-network/charts/react/agent-graph";
14
+ import { useEventStream } from "@tangle-network/ui/hooks";
13
15
  // src/run/trace-waterfall.tsx
14
16
  /** Agent and model spans show their lab/host mark; tool spans show their provider's mark. Unknown keeps the kind glyph. */
15
17
  function traceSpanMark(span) {
@@ -205,59 +207,61 @@ function useAgentStream(url, openEventSource) {
205
207
  lastSeq: 0,
206
208
  alive: false
207
209
  });
208
- useEffect(() => {
209
- if (!url || !openEventSource && typeof EventSource === "undefined") return;
210
- dispatch({ type: "reset" });
211
- let lastSeq = 0;
212
- let closed = false;
213
- let source;
214
- let retry;
215
- const open = () => {
216
- const target = new URL(url, globalThis.location?.href);
217
- if (lastSeq > 0) target.searchParams.set("after", String(lastSeq));
218
- source = openEventSource ? openEventSource(target.toString(), (live) => {
219
- if (!closed && !live) dispatch({ type: "unreachable" });
220
- }) : new EventSource(target.toString());
221
- let opened = false;
222
- if (!openEventSource) source.onopen = () => {
223
- opened = true;
224
- };
225
- source.addEventListener("rows", (message) => {
226
- const data = JSON.parse(message.data);
227
- lastSeq = Math.max(lastSeq, data.lastSeq ?? 0, ...data.events.map((row) => row.seq));
228
- dispatch({
229
- type: "rows",
230
- generation: data.generation,
231
- rows: data.events,
232
- alive: data.alive
233
- });
234
- });
235
- source.addEventListener("reset", (message) => {
236
- lastSeq = 0;
237
- dispatch({
238
- type: "reset",
239
- generation: JSON.parse(message.data).generation
240
- });
241
- });
242
- source.addEventListener("end", () => {
243
- closed = true;
244
- source?.close();
245
- dispatch({ type: "end" });
246
- });
247
- if (!openEventSource) source.onerror = () => {
248
- source?.close();
249
- if (closed) return;
250
- if (!opened) dispatch({ type: "unreachable" });
251
- retry = setTimeout(open, 1e3);
252
- };
253
- };
254
- open();
255
- return () => {
256
- closed = true;
257
- clearTimeout(retry);
258
- source?.close();
259
- };
260
- }, [url, openEventSource]);
210
+ const cursor = useRef({ lastSeq: 0 });
211
+ useEventStream(url, {
212
+ opener: openEventSource,
213
+ after: () => cursor.current.lastSeq,
214
+ onStart: () => {
215
+ cursor.current = { lastSeq: 0 };
216
+ dispatch({ type: "reset" });
217
+ },
218
+ onLiveChange: (live) => {
219
+ if (!live) dispatch({ type: "unreachable" });
220
+ },
221
+ events: {
222
+ rows: (message) => {
223
+ try {
224
+ const data = JSON.parse(message.data);
225
+ if (!Array.isArray(data.events)) throw new Error("Invalid agent rows stream");
226
+ if (data.generation !== void 0 && data.generation !== cursor.current.generation) cursor.current = {
227
+ lastSeq: 0,
228
+ generation: data.generation
229
+ };
230
+ cursor.current.lastSeq = Math.max(cursor.current.lastSeq, data.lastSeq ?? 0, ...data.events.map((row) => row.seq));
231
+ dispatch({
232
+ type: "rows",
233
+ generation: data.generation,
234
+ rows: data.events,
235
+ alive: data.alive
236
+ });
237
+ } catch (cause) {
238
+ dispatch({
239
+ type: "error",
240
+ message: String(cause)
241
+ });
242
+ }
243
+ },
244
+ reset: (message) => {
245
+ try {
246
+ const { generation } = JSON.parse(message.data);
247
+ cursor.current = {
248
+ lastSeq: 0,
249
+ generation
250
+ };
251
+ dispatch({
252
+ type: "reset",
253
+ generation
254
+ });
255
+ } catch (cause) {
256
+ dispatch({
257
+ type: "error",
258
+ message: String(cause)
259
+ });
260
+ }
261
+ },
262
+ end: () => dispatch({ type: "end" })
263
+ }
264
+ });
261
265
  return state;
262
266
  }
263
267
  // src/run/trace-workspace.tsx
@@ -438,18 +442,10 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
438
442
  const live = stream.alive && !stream.error && cursor === null;
439
443
  const items = useMemo(() => timelineItems(shown, live), [shown, live]);
440
444
  const scroller = useRef(null);
441
- const pinned = useRef(true);
442
- const [unseen, setUnseen] = useState(false);
443
- useLayoutEffect(() => {
444
- const element = scroller.current;
445
- if (!element) return;
446
- if (pinned.current) element.scrollTop = element.scrollHeight;
447
- else setUnseen(true);
448
- }, [items.length, items[items.length - 1]]);
449
- useEffect(() => {
450
- pinned.current = true;
451
- setUnseen(false);
452
- }, [selected]);
445
+ const { unseen, jump } = useFollowTail(scroller, {
446
+ identity: showConversation ? selected : void 0,
447
+ content: items
448
+ });
453
449
  return /* @__PURE__ */ jsxs("div", {
454
450
  className: cn("sandbox-trace-workspace flex min-h-0 flex-col gap-3", className),
455
451
  children: [
@@ -580,11 +576,6 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
580
576
  }),
581
577
  /* @__PURE__ */ jsx("div", {
582
578
  ref: scroller,
583
- onScroll: (event) => {
584
- const element = event.currentTarget;
585
- pinned.current = element.scrollHeight - element.scrollTop - element.clientHeight < 48;
586
- if (pinned.current) setUnseen(false);
587
- },
588
579
  "data-primary-content": "",
589
580
  className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
590
581
  children: streamUrl && (cursor === null || origin !== void 0) ? /* @__PURE__ */ jsx(AgentTimeline, {
@@ -598,12 +589,7 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
598
589
  }),
599
590
  unseen && /* @__PURE__ */ jsx("button", {
600
591
  type: "button",
601
- onClick: () => {
602
- const element = scroller.current;
603
- if (element) element.scrollTop = element.scrollHeight;
604
- pinned.current = true;
605
- setUnseen(false);
606
- },
592
+ onClick: () => jump(),
607
593
  className: "absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full border border-border bg-background px-3 py-1 text-xs shadow-sm",
608
594
  children: "New output"
609
595
  })
@@ -612,4 +598,4 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
612
598
  ]
613
599
  });
614
600
  }
615
- export { TraceWaterfall as a, useAgentStream as i, LatestEvents as n, traceSpanMark as o, foldAgentRows as r, TraceWorkspace as t };
601
+ export { useEventStream as a, useAgentStream as i, LatestEvents as n, TraceWaterfall as o, foldAgentRows as r, traceSpanMark as s, TraceWorkspace as t };