@tangle-network/sandbox-ui 2.0.7 → 2.0.8

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