@tangle-network/sandbox-ui 2.0.7 → 2.0.9

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,616 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.js";
2
- import { t as cn$1 } from "./utils-BER_bdgE.js";
3
- import { i as modelBrandFor, t as ModelBrandStack } from "./model-brand-bTcMjQtB.js";
4
- import { t as ProviderIcon } from "./provider-logo-CPKRPHcS.js";
5
- import { f as syncAmbient, l as activityTiming, t as AgentGraph, u as ambientStyle } from "./AgentGraph-CFlI9qta.js";
6
- import { t as LatestEvents } from "./latest-events-v_vXRyd7.js";
7
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
- import { useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
9
- import { X } from "lucide-react";
3
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
10
4
  import { getToolDisplayMetadata } from "@tangle-network/ui/utils";
11
- import { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, SpeakerHeader, agentTimelineStepKind } from "@tangle-network/ui/chat";
12
- import { ApprovalCard, ApprovalDiffSummary, ExpandedToolDetail, InlineThinkingItem, InlineThinkingItem as InlineThinkingItem$1, InlineToolItem, LiveDuration, RunGroup, RunPhaseList, RunRowShell, RunRowShell as RunRowShell$1, RunRowStatusDot, RunStatusIcon as RunStatusIcon$1, TaskList, ToolCallFeed, parseToolEvent, runStatus, runStatus as runStatus$1, showsTaskPriority, statusMeta as statusMeta$1, taskProgress, taskStatus } from "@tangle-network/ui/run";
13
- import { FlowWaterfall, toolGlyph } from "@tangle-network/charts/react";
14
- // src/run/trace-waterfall.tsx
15
- /** Agent and model spans show their lab/host mark; tool spans show their provider's mark. Unknown keeps the kind glyph. */
16
- function traceSpanMark(span) {
17
- if (span.kind === "pipeline" || span.kind === "model") {
18
- const identity = span.detail ? modelBrandFor(span.detail) : null;
19
- return identity ? /* @__PURE__ */ jsx(ModelBrandStack, {
20
- identity,
21
- size: "sm"
22
- }) : void 0;
23
- }
24
- const provider = span.icon ?? (span.kind === "tool" ? span.detail : void 0);
25
- return provider ? /* @__PURE__ */ jsx(ProviderIcon, {
26
- id: provider,
27
- displayName: provider,
28
- size: 14,
29
- className: "rounded-[3px]"
30
- }) : void 0;
31
- }
32
- /**
33
- * The run inspector: the agent timeline (`defaultView="agents"`) and the span waterfall over one clock, from
34
- * `@tangle-network/charts`, with Sandbox UI's brand marks. Feed it `otlpFlowTrace(spans)` from the
35
- * `traces convert` contract. Import `@tangle-network/charts/react/flow-waterfall.css` once.
36
- */
37
- function TraceWaterfall({ renderIcon = traceSpanMark, ...props }) {
38
- return /* @__PURE__ */ jsx(FlowWaterfall, {
39
- renderIcon,
40
- ...props
41
- });
42
- }
43
- // src/run/agent-stream.ts
44
- const TOOL_TYPES = /* @__PURE__ */ new Set([
45
- "bash",
46
- "read",
47
- "write",
48
- "edit",
49
- "glob",
50
- "grep",
51
- "list",
52
- "download",
53
- "inspect",
54
- "audit"
55
- ]);
56
- const toolType = (name) => {
57
- const key = (name ?? "").toLowerCase();
58
- return TOOL_TYPES.has(key) ? key : "unknown";
59
- };
60
- const brief = (value, max = 240) => {
61
- if (value === void 0 || value === null) return void 0;
62
- const text = typeof value === "string" ? value : JSON.stringify(value);
63
- return text.length > max ? `${text.slice(0, max - 1)}…` : text;
64
- };
65
- const failed = (result) => typeof result === "object" && result !== null && ("error" in result || result.is_error === true);
66
- const recorded = (text) => {
67
- try {
68
- const value = JSON.parse(text);
69
- return typeof value === "object" && value !== null ? `\`\`\`json\n${text}\n\`\`\`` : text;
70
- } catch {
71
- return text;
72
- }
73
- };
74
- /** Fold stream rows into transcript entries: text of one turn merges, a tool call becomes a row its result completes. */
75
- function foldAgentRows(entries, rows) {
76
- const next = [...entries];
77
- for (const row of rows) {
78
- const at = Date.parse(row.at);
79
- const { event } = row;
80
- const last = next[next.length - 1];
81
- if (event.kind === "text" && event.text) {
82
- if (last?.kind === "message" && last.role === "assistant" && !last.sealed) next[next.length - 1] = {
83
- ...last,
84
- content: last.content + event.text
85
- };
86
- else next.push({
87
- id: `m-${row.seq}`,
88
- at,
89
- kind: "message",
90
- role: "assistant",
91
- content: event.text
92
- });
93
- } else if (event.kind === "user" && event.text) next.push({
94
- id: `u-${row.seq}`,
95
- at,
96
- kind: "message",
97
- role: "user",
98
- content: event.text
99
- });
100
- else if (event.kind === "notice" && event.text) {
101
- if (event.provenance === "delegated-input") next.push({
102
- id: `u-${row.seq}`,
103
- at,
104
- kind: "message",
105
- role: "user",
106
- content: recorded(event.text)
107
- });
108
- else if (event.provenance === "recorded-result") next.push({
109
- id: `m-${row.seq}`,
110
- at,
111
- kind: "message",
112
- role: "assistant",
113
- content: recorded(event.text),
114
- sealed: true
115
- });
116
- else next.push({
117
- id: `n-${row.seq}`,
118
- at,
119
- kind: "notice",
120
- text: event.text
121
- });
122
- } else if (event.kind === "reasoning" && event.text) {
123
- if (last?.kind === "reasoning") next[next.length - 1] = {
124
- ...last,
125
- text: last.text + event.text
126
- };
127
- else next.push({
128
- id: `r-${row.seq}`,
129
- at,
130
- kind: "reasoning",
131
- text: event.text
132
- });
133
- } else if (event.kind === "tool-call") {
134
- const id = event.toolCallId ?? `t-${row.seq}`;
135
- next.push({
136
- id,
137
- at,
138
- kind: "tool",
139
- call: {
140
- id,
141
- type: toolType(event.toolName),
142
- label: event.toolName ?? "tool",
143
- status: "running",
144
- detail: brief(event.args, 120)
145
- }
146
- });
147
- } else if (event.kind === "tool-result") {
148
- const index = next.findIndex((entry) => entry.kind === "tool" && entry.call.id === event.toolCallId);
149
- const entry = next[index];
150
- if (entry?.kind === "tool") next[index] = {
151
- ...entry,
152
- call: {
153
- ...entry.call,
154
- status: failed(event.result) ? "error" : "success",
155
- output: brief(event.result),
156
- duration: Math.max(0, at - entry.at)
157
- }
158
- };
159
- }
160
- }
161
- return next;
162
- }
163
- function reduce(state, action) {
164
- switch (action.type) {
165
- case "reset": return {
166
- generation: action.generation,
167
- entries: [],
168
- lastSeq: 0,
169
- alive: true
170
- };
171
- case "rows": {
172
- const base = action.generation !== void 0 && action.generation !== state.generation ? {
173
- entries: [],
174
- lastSeq: 0
175
- } : state;
176
- const fresh = action.rows.filter((row) => row.seq > base.lastSeq);
177
- return {
178
- generation: action.generation ?? state.generation,
179
- entries: foldAgentRows(base.entries, fresh),
180
- lastSeq: fresh.reduce((max, row) => Math.max(max, row.seq), base.lastSeq),
181
- alive: action.alive ?? state.alive,
182
- error: void 0
183
- };
184
- }
185
- case "end": return {
186
- ...state,
187
- alive: false,
188
- error: void 0
189
- };
190
- case "error": return {
191
- ...state,
192
- error: action.message
193
- };
194
- case "unreachable": return {
195
- ...state,
196
- error: "The agent stream is unreachable; the viewer may be closed."
197
- };
198
- }
199
- }
200
- /**
201
- * Follow one agent's message stream over Server-Sent Events: `rows`, `reset` and `end` events, reconnecting with
202
- * `?after=<lastSeq>` when the server closes the connection, so a long turn streams without gaps or repeats.
203
- */
204
- function useAgentStream(url) {
205
- const [state, dispatch] = useReducer(reduce, {
206
- entries: [],
207
- lastSeq: 0,
208
- alive: false
209
- });
210
- useEffect(() => {
211
- if (!url || typeof EventSource === "undefined") return;
212
- dispatch({ type: "reset" });
213
- let lastSeq = 0;
214
- let closed = false;
215
- let source;
216
- let retry;
217
- const open = () => {
218
- const target = new URL(url, globalThis.location?.href);
219
- if (lastSeq > 0) target.searchParams.set("after", String(lastSeq));
220
- source = new EventSource(target.toString());
221
- let opened = false;
222
- 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
- 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]);
261
- return state;
262
- }
263
- // src/run/trace-workspace.tsx
264
- /** The agent that owns a span: the span itself when it is an agent, else its nearest agent ancestor. */
265
- function agentOf(span, byId) {
266
- for (let cursor = span; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) if (cursor.kind === "pipeline") return cursor;
267
- }
268
- /** The trace's agents as a spawn tree at the cursor: an agent not started by then is left out, one that ends after it is
269
- * still running, so the Graph and the Timeline always show the same moment. */
270
- function agentGraphAt(spans, byId, at, epoch, cards, extraEdges = []) {
271
- const nodes = [], edges = [], seen = /* @__PURE__ */ new Set();
272
- const timed = new Set(spans.flatMap((span) => span.kind === "pipeline" && span.id ? [span.id] : []));
273
- const recorded = /* @__PURE__ */ new Map();
274
- for (const [index, span] of spans.entries()) {
275
- if (span.kind === "pipeline" || at !== null && span.startMs > at) continue;
276
- const owner = agentOf(span, byId);
277
- if (!owner?.id) continue;
278
- const events = recorded.get(owner.id) ?? [];
279
- const endpoints = [{
280
- suffix: "start",
281
- label: "Started ",
282
- time: span.startMs
283
- }];
284
- if (span.status !== "running" && span.endMs !== span.startMs && (at === null || span.endMs <= at)) endpoints.push({
285
- suffix: "end",
286
- label: "Recorded ",
287
- time: span.endMs
288
- });
289
- for (const endpoint of endpoints) events.push({
290
- id: (span.id ?? String(index)) + ":" + endpoint.suffix,
291
- label: endpoint.label + span.name,
292
- at: epoch == null || !Number.isFinite(epoch) ? void 0 : epoch + endpoint.time
293
- });
294
- recorded.set(owner.id, events);
295
- }
296
- for (const span of spans) {
297
- if (span.kind !== "pipeline" || !span.id || seen.has(span.id) || at !== null && span.startMs > at) continue;
298
- seen.add(span.id);
299
- const card = cards?.[span.id];
300
- const state = at !== null && (span.endMs > at || span.status === "running") ? "running" : runStatus(span.status ?? "unknown");
301
- const activity = (span.activityAt ?? []).reduce((last, time) => Number.isFinite(time) && (at === null || time <= at) ? Math.max(last, time) : last, -Infinity);
302
- const events = (card?.events ?? recorded.get(span.id) ?? []).filter((event) => at === null || epoch != null && event.at != null && event.at <= epoch + at).sort((a, b) => (a.at ?? 0) - (b.at ?? 0));
303
- const timing = activityTiming({
304
- events,
305
- lastActivityAt: epoch != null && Number.isFinite(epoch) && Number.isFinite(activity) ? epoch + activity : at === null ? card?.lastActivityAt : void 0,
306
- medianEventGapMs: span.medianEventGapMs ?? (at === null ? card?.medianEventGapMs : void 0)
307
- });
308
- nodes.push({
309
- ...card,
310
- id: span.id,
311
- label: card?.label ?? span.name,
312
- model: card?.model ?? span.detail,
313
- status: at === null && card?.status ? card.status : state === "done" ? "succeeded" : state,
314
- costUsd: at === null ? card?.costUsd ?? span.costUsd : span.endMs <= at ? span.costUsd : void 0,
315
- startedAt: epoch == null || !Number.isFinite(epoch) ? at === null ? card?.startedAt : void 0 : epoch + span.startMs,
316
- ...timing,
317
- events: events.slice(-4),
318
- turn: at === null ? card?.turn : void 0,
319
- result: at === null ? card?.result ?? span.output : span.endMs <= at ? span.output : void 0,
320
- commits: at === null ? card?.commits : void 0,
321
- tokens: at === null ? card?.tokens : void 0
322
- });
323
- const parent = span.parentId ? byId.get(span.parentId) : void 0, spawner = parent && agentOf(parent, byId);
324
- if (spawner?.id) edges.push({
325
- from: spawner.id,
326
- to: span.id,
327
- kind: "spawn"
328
- });
329
- }
330
- for (const [id, card] of Object.entries(cards ?? {})) if (!timed.has(id)) nodes.push(at === null ? {
331
- ...card,
332
- id,
333
- label: card.label ?? id,
334
- status: card.status ?? "unknown"
335
- } : {
336
- id,
337
- label: card.label ?? id,
338
- model: card.model,
339
- harness: card.harness,
340
- role: card.role,
341
- status: "unknown",
342
- turn: "Timing unavailable at this cursor"
343
- });
344
- const ids = new Set(nodes.map((node) => node.id));
345
- for (const edge of extraEdges) if (ids.has(edge.from) && ids.has(edge.to) && (edge.kind !== "spawn" || !edges.some((prior) => prior.kind === "spawn" && prior.from === edge.from && prior.to === edge.to))) edges.push(edge);
346
- return {
347
- nodes,
348
- edges
349
- };
350
- }
351
- function timelineItems(entries, streaming) {
352
- return entries.map((entry, index) => {
353
- if (entry.kind === "message") return {
354
- id: entry.id,
355
- kind: "message",
356
- role: entry.role,
357
- content: entry.content,
358
- isStreaming: streaming && index === entries.length - 1
359
- };
360
- if (entry.kind === "tool") return {
361
- id: entry.id,
362
- kind: "tool",
363
- call: entry.call
364
- };
365
- if (entry.kind === "notice") return {
366
- id: entry.id,
367
- kind: "custom",
368
- content: /* @__PURE__ */ jsx("p", {
369
- className: "text-xs text-muted-foreground",
370
- children: entry.text
371
- })
372
- };
373
- return {
374
- id: entry.id,
375
- kind: "custom",
376
- content: /* @__PURE__ */ jsx(InlineThinkingItem, {
377
- part: {
378
- type: "reasoning",
379
- text: entry.text
380
- },
381
- defaultOpen: false,
382
- openWhileActive: false
383
- })
384
- };
385
- });
386
- }
387
- function NotTimeIndexed({ label }) {
388
- return /* @__PURE__ */ jsxs("p", {
389
- className: "p-4 text-sm text-muted-foreground",
390
- children: [label, ": not time-indexed"]
391
- });
392
- }
393
- /**
394
- * The run workspace: the agent timeline and span waterfall over one clock, the selected agent's conversation
395
- * streaming in a right-hand drawer, and the page's code workspace and scoreboard following the time cursor. Scrubbing is time travel:
396
- * every panel shows what was true at the cursor, and the conversation shows what had been said by then.
397
- */
398
- function TraceWorkspace({ trace, streamUrl, events, onTimeChange, files, scoreboard, selectedAgentId, onSelectAgent, agentCards, agentGraph, className, ...waterfall }) {
399
- const byId = useMemo(() => new Map(trace.spans.flatMap((span) => span.id ? [[span.id, span]] : [])), [trace]);
400
- const [ownSelection, setOwnSelection] = useState();
401
- const selected = selectedAgentId ?? ownSelection;
402
- const select = (agentId) => {
403
- setOwnSelection(agentId);
404
- onSelectAgent?.(agentId);
405
- };
406
- const agent = selected ? byId.get(selected) : void 0, card = selected ? agentCards?.[selected] : void 0;
407
- const agentId = agent?.id ?? (card ? selected : void 0), agentName = agent?.name ?? card?.label ?? agentId, agentModel = agent?.detail ?? card?.model;
408
- const { defaultLayout = "tree", ...graphOptions } = agentGraph ?? {};
409
- const [graphLayout, setGraphLayout] = useState(defaultLayout);
410
- const [cursor, setCursor] = useState(null);
411
- const stream = useAgentStream(selected && streamUrl ? streamUrl(selected) : void 0);
412
- const origin = useMemo(() => {
413
- if (waterfall.startedAt !== void 0) return waterfall.startedAt;
414
- const first = stream.entries[0]?.at;
415
- return agent && first !== void 0 ? first - agent.startMs : void 0;
416
- }, [
417
- waterfall.startedAt,
418
- agent,
419
- stream.entries
420
- ]);
421
- const graph = useMemo(() => agentGraphAt(trace.spans, byId, cursor, waterfall.startedAt, agentCards, agentGraph?.edges), [
422
- trace,
423
- byId,
424
- cursor,
425
- waterfall.startedAt,
426
- agentCards,
427
- agentGraph?.edges
428
- ]);
429
- const selectedNode = graph.nodes.find((node) => node.id === agentId);
430
- const facts = cursor === null || agent && agent.endMs <= cursor ? [...agent?.facts ?? []] : [];
431
- for (const [label, value] of [["Recorded result", selectedNode?.result], ["Activity", selectedNode?.turn]]) if (value && !facts.some((fact) => fact.value === value)) facts.push({
432
- label,
433
- value
434
- });
435
- const graphNow = cursor !== null && waterfall.startedAt !== void 0 ? waterfall.startedAt + cursor : void 0;
436
- const shown = cursor === null || origin === void 0 ? stream.entries : stream.entries.filter((entry) => entry.at - origin <= cursor);
437
- const live = stream.alive && !stream.error && cursor === null;
438
- const items = useMemo(() => timelineItems(shown, live), [shown, live]);
439
- const scroller = useRef(null);
440
- const pinned = useRef(true);
441
- const [unseen, setUnseen] = useState(false);
442
- useLayoutEffect(() => {
443
- const element = scroller.current;
444
- if (!element) return;
445
- if (pinned.current) element.scrollTop = element.scrollHeight;
446
- else setUnseen(true);
447
- }, [items.length, items[items.length - 1]]);
448
- useEffect(() => {
449
- pinned.current = true;
450
- setUnseen(false);
451
- }, [selected]);
452
- return /* @__PURE__ */ jsxs("div", {
453
- className: cn$1("sandbox-trace-workspace flex min-h-0 flex-col gap-3", className),
454
- children: [
455
- /* @__PURE__ */ jsx("div", {
456
- className: agentId ? "min-w-0 lg:mr-[420px]" : "min-w-0",
457
- children: /* @__PURE__ */ jsx(TraceWaterfall, {
458
- ...waterfall,
459
- trace,
460
- markers: events,
461
- selectedId: agentId,
462
- onDeselect: () => select(void 0),
463
- onSelect: (span) => {
464
- const owner = agentOf(span, byId);
465
- if (owner?.id) select(owner.id);
466
- },
467
- onTimeChange: (time) => {
468
- setCursor(time);
469
- onTimeChange?.(time);
470
- },
471
- latestEvents: /* @__PURE__ */ jsx(LatestEvents, {
472
- agents: graph.nodes,
473
- now: graphNow,
474
- live: cursor === null,
475
- selectedId: agentId,
476
- onSelect: select
477
- }),
478
- graph: /* @__PURE__ */ jsxs("section", {
479
- className: "flex h-[60vh] min-h-[480px] flex-col",
480
- "aria-label": "Agent graph",
481
- children: [/* @__PURE__ */ jsxs("label", {
482
- className: "flex h-8 shrink-0 items-center justify-end gap-2 px-3 text-xs text-muted-foreground",
483
- children: ["Layout", /* @__PURE__ */ jsxs("select", {
484
- "aria-label": "Graph layout",
485
- value: graphLayout,
486
- onChange: (event) => setGraphLayout(event.currentTarget.value),
487
- className: "rounded-[var(--radius-control)] border border-border bg-card px-2 py-1 text-foreground",
488
- children: [/* @__PURE__ */ jsx("option", {
489
- value: "tree",
490
- children: "Tree"
491
- }), /* @__PURE__ */ jsx("option", {
492
- value: "hub",
493
- children: "Hub"
494
- })]
495
- })]
496
- }), /* @__PURE__ */ jsx(AgentGraph, {
497
- ...graphOptions,
498
- layout: graphLayout,
499
- renderEventIcon: (event) => toolGlyph(event.label),
500
- nodes: graph.nodes,
501
- edges: graph.edges,
502
- selectedId: agentId ?? null,
503
- onSelect: select,
504
- showLatestEvents: false,
505
- now: graphNow,
506
- live: cursor === null && graph.nodes.some((node) => [
507
- "running",
508
- "waiting",
509
- "needs-you",
510
- "failed"
511
- ].includes(node.status)),
512
- className: cn$1("min-h-0 flex-1", graphOptions.className)
513
- })]
514
- })
515
- })
516
- }),
517
- files && /* @__PURE__ */ jsx("section", {
518
- "aria-label": "Code at the cursor",
519
- className: "flex min-h-[70vh] flex-col overflow-hidden rounded-xl border border-border bg-card",
520
- children: files
521
- }),
522
- scoreboard && /* @__PURE__ */ jsx("section", {
523
- "aria-label": "Scoreboard at the cursor",
524
- className: "overflow-hidden rounded-xl border border-border bg-card",
525
- children: scoreboard
526
- }),
527
- agentId && /* @__PURE__ */ jsxs("aside", {
528
- "aria-label": `${agentName} conversation`,
529
- onKeyDown: (event) => {
530
- if (event.key === "Escape") select(void 0);
531
- },
532
- className: "fixed inset-y-0 right-0 z-40 flex w-full max-w-[420px] flex-col border-l border-border bg-card shadow-2xl",
533
- children: [
534
- /* @__PURE__ */ jsxs("header", {
535
- className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2",
536
- children: [
537
- /* @__PURE__ */ jsx("span", {
538
- className: cn$1("h-1.5 w-1.5 shrink-0 rounded-full", live ? "bg-[var(--surface-info-text)]" : "bg-muted-foreground"),
539
- "aria-hidden": true,
540
- "data-motion": live ? "essential" : void 0,
541
- style: live ? ambientStyle : void 0,
542
- onAnimationStart: syncAmbient
543
- }),
544
- /* @__PURE__ */ jsx("h3", {
545
- className: "min-w-0 flex-1 truncate text-sm font-semibold",
546
- children: agentName
547
- }),
548
- agentModel && /* @__PURE__ */ jsx("span", {
549
- className: "truncate text-xs text-muted-foreground",
550
- children: agentModel
551
- }),
552
- /* @__PURE__ */ jsx("button", {
553
- type: "button",
554
- "aria-label": "Close conversation",
555
- title: "Close (Esc)",
556
- onClick: () => select(void 0),
557
- className: "shrink-0 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
558
- children: /* @__PURE__ */ jsx(X, {
559
- "aria-hidden": true,
560
- className: "h-4 w-4"
561
- })
562
- })
563
- ]
564
- }),
565
- facts.length > 0 && /* @__PURE__ */ jsx("section", {
566
- "aria-label": "Recorded agent details",
567
- tabIndex: 0,
568
- className: "max-h-[35vh] shrink-0 overflow-y-auto border-b border-border px-3 py-2 text-sm",
569
- children: /* @__PURE__ */ jsx("dl", {
570
- className: "space-y-2",
571
- children: facts.map((fact, index) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
572
- className: "text-xs text-muted-foreground",
573
- children: fact.label
574
- }), /* @__PURE__ */ jsx("dd", {
575
- className: "m-0 whitespace-pre-wrap break-words",
576
- children: fact.value
577
- })] }, index))
578
- })
579
- }),
580
- /* @__PURE__ */ jsx("div", {
581
- ref: scroller,
582
- onScroll: (event) => {
583
- const element = event.currentTarget;
584
- pinned.current = element.scrollHeight - element.scrollTop - element.clientHeight < 48;
585
- if (pinned.current) setUnseen(false);
586
- },
587
- "data-primary-content": "",
588
- className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
589
- children: streamUrl && (cursor === null || origin !== void 0) ? /* @__PURE__ */ jsx(AgentTimeline, {
590
- items,
591
- isThinking: live && items.length === 0,
592
- emptyState: /* @__PURE__ */ jsx("p", {
593
- className: "p-4 text-sm text-muted-foreground",
594
- children: stream.error ?? "No messages yet."
595
- })
596
- }) : /* @__PURE__ */ jsx(NotTimeIndexed, { label: "Conversation" })
597
- }),
598
- unseen && /* @__PURE__ */ jsx("button", {
599
- type: "button",
600
- onClick: () => {
601
- const element = scroller.current;
602
- if (element) element.scrollTop = element.scrollHeight;
603
- pinned.current = true;
604
- setUnseen(false);
605
- },
606
- 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",
607
- children: "New output"
608
- })
609
- ]
610
- })
611
- ]
612
- });
613
- }
5
+ import { AgentTimelineColumn, AgentTimelineStep, SpeakerHeader, agentTimelineStepKind } from "@tangle-network/ui/chat";
6
+ import { InlineThinkingItem, RunRowShell } from "@tangle-network/ui/run";
614
7
  // src/run/record/VirtualList.tsx
615
8
  /**
616
9
  * Renders only the rows near the viewport of `scroller`. Row heights are measured as they render; unmeasured
@@ -788,6 +181,7 @@ function VirtualList({ items, keyOf, render, scroller, estimate = 96, overscan =
788
181
  return /* @__PURE__ */ jsx("div", {
789
182
  ref: bind(key),
790
183
  className: "virtual-row",
184
+ style: { display: "flow-root" },
791
185
  children: render(item, start + i)
792
186
  }, key);
793
187
  }),
@@ -914,12 +308,14 @@ function preview(call) {
914
308
  }
915
309
  /** Identity is established by the importer, never inferred from labels or timestamps. */
916
310
  function indexRecord(record) {
917
- const nodes = new Map(record.nodes.map((node) => [node.id, node]));
311
+ const inputNodes = record.nodes;
312
+ const inputEvents = record.events;
313
+ const nodes = new Map(inputNodes.map((node) => [node.id, node]));
918
314
  const canonical = (id) => nodes.get(id)?.agentId ?? id;
919
- const actors = record.nodes.filter((node) => node.kind !== "session" || !node.agentId);
920
- const events = record.events.map(withoutHostPaths).sort((a, b) => ms(a.at) - ms(b.at));
315
+ const actors = inputNodes.filter((node) => node.kind !== "session" || !node.agentId);
316
+ const events = inputEvents.map((event) => withoutHostPaths(event)).sort((a, b) => ms(a.at) - ms(b.at));
921
317
  const byEvent = new Map(events.map((event) => [event.id, event]));
922
- const recordedOrder = new Map(record.events.map((event, i) => [event.id, i]));
318
+ const recordedOrder = new Map(inputEvents.map((event, i) => [event.id, i]));
923
319
  const byActor = /* @__PURE__ */ new Map();
924
320
  const results = /* @__PURE__ */ new Map();
925
321
  const calls = /* @__PURE__ */ new Map();
@@ -2033,4 +1429,4 @@ function withPrompts(record) {
2033
1429
  events
2034
1430
  };
2035
1431
  }
2036
- export { model_exports as C, TraceWaterfall as D, useAgentStream as E, traceSpanMark as O, indexRecord as S, foldAgentRows as T, statusMeta$1 as _, InlineToolItem as a, withPrompts as b, RunPhaseList as c, RunStatusIcon$1 as d, TaskList as f, showsTaskPriority as g, runStatus$1 as h, InlineThinkingItem$1 as i, RunRowShell$1 as l, parseToolEvent as m, ApprovalDiffSummary as n, LiveDuration as o, ToolCallFeed as p, ExpandedToolDetail as r, RunGroup as s, ApprovalCard as t, RunRowStatusDot as u, taskProgress as v, TraceWorkspace as w, Conversation as x, taskStatus as y };
1432
+ export { VirtualList as a, model_exports as i, Conversation as n, indexRecord as r, withPrompts as t };
@@ -0,0 +1,2 @@
1
+ import { a as EventFlag, c as indexRecord, d as ConversationNode, f as ConversationRecord, i as ConversationProps, l as model_d_exports, n as withPrompts, o as RecordIndex, r as Conversation, s as ToolCall, t as VirtualList, u as ConversationEvent } from "../index-DRLVHl-o.js";
2
+ export { Conversation, type ConversationEvent, type ConversationNode, type ConversationProps, type ConversationRecord, type EventFlag, type RecordIndex, type ToolCall, VirtualList, indexRecord, model_d_exports as recordModel, withPrompts };
@@ -0,0 +1 @@
1
+ export { n as Conversation, a as VirtualList, r as indexRecord, i as recordModel, t as withPrompts } from "../record-CzsY5-Mv.js";