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