@tangle-network/sandbox-ui 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AgentTree-D4d06rfH.js → AgentTree-abRugBMr.js} +1 -1
- package/dist/{ResearchReport-D1TFmldm.js → ResearchReport-BPOmsUFI.js} +2 -2
- package/dist/globals.css +45 -20
- package/dist/{index-D6fZ1Hgz.d.ts → index-PolYo7h9.d.ts} +11 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -4
- package/dist/record/ResearchReport.js +1 -1
- package/dist/{record-C-EUpK-n.js → record-hTSz-cf1.js} +2 -2
- package/dist/record.js +1 -1
- package/dist/run/live.js +12 -9
- package/dist/run/record.js +3 -3
- package/dist/run.d.ts +1 -1
- package/dist/run.js +4 -4
- package/dist/styles.css +45 -20
- package/dist/tailwind.css +1 -0
- package/dist/{trace-workspace-CTMV8zw_.js → trace-workspace-WJTjp2Id.js} +482 -122
- package/dist/{use-follow-tail-DZFn7QDO.js → use-follow-tail-Di1zlndg.js} +5 -5
- package/dist/workspace-viewer.js +10014 -9718
- package/package.json +11 -10
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { t as cn } from "./utils-BER_bdgE.js";
|
|
1
|
+
import { t as cn$1 } 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 { s as timelineItems, t as useFollowTail } from "./use-follow-tail-
|
|
5
|
+
import { s as timelineItems, t as useFollowTail } from "./use-follow-tail-Di1zlndg.js";
|
|
6
|
+
import { SegmentedControl } from "@tangle-network/ui/primitives";
|
|
6
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
-
import { X } from "lucide-react";
|
|
9
|
-
import {
|
|
8
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
9
|
+
import { ArrowLeft, ChevronRight, X } from "lucide-react";
|
|
10
|
+
import { formatCost, formatCount, formatSpanDuration } from "@tangle-network/ui/utils";
|
|
11
|
+
import { activityTiming } from "@tangle-network/charts/react/agent-graph/presentation";
|
|
10
12
|
import { AgentTimeline } from "@tangle-network/ui/chat";
|
|
11
|
-
import { runStatus } from "@tangle-network/ui/run";
|
|
13
|
+
import { RunStatusIcon, runStatus } from "@tangle-network/ui/run";
|
|
12
14
|
import { FlowWaterfall, toolGlyph } from "@tangle-network/charts/react";
|
|
13
15
|
import { LatestEvents } from "@tangle-network/charts/react/agent-graph";
|
|
14
16
|
import { useEventStream } from "@tangle-network/ui/hooks";
|
|
@@ -43,6 +45,40 @@ function TraceWaterfall({ renderIcon = traceSpanMark, ...props }) {
|
|
|
43
45
|
}
|
|
44
46
|
// src/run/agent-stream.ts
|
|
45
47
|
const textOf = (value) => value == null ? void 0 : typeof value === "string" ? value : JSON.stringify(value);
|
|
48
|
+
/** A native harness row (live-stream evidence and retained captures: `{ type, data }`) as a producer event, so
|
|
49
|
+
* non-root agents render: the wall's transcript pages and per-agent streams serve these rows verbatim, and before
|
|
50
|
+
* this adapter every one of them was dropped by the fold — a live child agent showed nothing at all. Text and
|
|
51
|
+
* reasoning parts (opencode `raw` events and the SDK's `message.part.updated`) become deltas; a `message.updated`
|
|
52
|
+
* carrying a user role becomes the user's words. Native tool shapes vary by harness and stay unrendered until a
|
|
53
|
+
* captured sample exists to map them honestly. */
|
|
54
|
+
function nativeEventOf(row) {
|
|
55
|
+
const data = row.data;
|
|
56
|
+
if (!data || typeof data !== "object") return void 0;
|
|
57
|
+
const wrapped = data.event;
|
|
58
|
+
const source = wrapped && (wrapped.type === "text" || wrapped.type === "reasoning" || wrapped.type === "message") ? wrapped : data;
|
|
59
|
+
const type = source.type;
|
|
60
|
+
const part = source.part;
|
|
61
|
+
if ((type === "text" || type === "message.part.updated") && part?.type === "text" && typeof part.text === "string") return {
|
|
62
|
+
kind: "text_delta",
|
|
63
|
+
text: part.text
|
|
64
|
+
};
|
|
65
|
+
if ((type === "reasoning" || type === "message.part.updated") && part?.type === "reasoning" && typeof part.text === "string") return {
|
|
66
|
+
kind: "reasoning_delta",
|
|
67
|
+
text: part.text
|
|
68
|
+
};
|
|
69
|
+
if (type === "text" && typeof source.text === "string") return {
|
|
70
|
+
kind: "text_delta",
|
|
71
|
+
text: source.text
|
|
72
|
+
};
|
|
73
|
+
const info = source.properties?.info ?? source.info;
|
|
74
|
+
if ((type === "message" || type === "message.updated") && info?.role === "user") {
|
|
75
|
+
const words = typeof info.content === "string" ? info.content : typeof info.text === "string" ? info.text : void 0;
|
|
76
|
+
if (words) return {
|
|
77
|
+
kind: "user",
|
|
78
|
+
text: words
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
46
82
|
const failed = (value) => typeof value === "object" && value !== null && ("error" in value || value.is_error === true || value.isError === true);
|
|
47
83
|
const recorded = (text) => {
|
|
48
84
|
try {
|
|
@@ -67,7 +103,7 @@ const TOOL_TYPES = /* @__PURE__ */ new Set([
|
|
|
67
103
|
function foldAgentRows(entries, rows) {
|
|
68
104
|
const next = [...entries];
|
|
69
105
|
for (const row of rows) {
|
|
70
|
-
const event = row.event;
|
|
106
|
+
const event = row.event ?? nativeEventOf(row);
|
|
71
107
|
if (!event) continue;
|
|
72
108
|
const at = Date.parse(row.at ?? ""), scope = JSON.stringify([row.stream ?? null, row.attempt ?? null]);
|
|
73
109
|
const base = {
|
|
@@ -392,6 +428,370 @@ function useAgentStream(url, openEventSource, history) {
|
|
|
392
428
|
} } : {}
|
|
393
429
|
};
|
|
394
430
|
}
|
|
431
|
+
// src/run/agent-inspector.tsx
|
|
432
|
+
/** Below the tablet breakpoint the inspector is a full-screen sheet over the run, never a squeezed side pane. */
|
|
433
|
+
const PHONE = "(max-width: 767px)";
|
|
434
|
+
function usePhone() {
|
|
435
|
+
return useSyncExternalStore((notify) => {
|
|
436
|
+
const query = matchMedia(PHONE);
|
|
437
|
+
query.addEventListener("change", notify);
|
|
438
|
+
return () => query.removeEventListener("change", notify);
|
|
439
|
+
}, () => matchMedia(PHONE).matches, () => false);
|
|
440
|
+
}
|
|
441
|
+
const WIDTH_KEY = "sandbox-ui:trace-inspector-width";
|
|
442
|
+
const MIN_PX = 360;
|
|
443
|
+
const MIN_LEFT_PX = 320;
|
|
444
|
+
const STEP = .02;
|
|
445
|
+
const readWidth = () => {
|
|
446
|
+
try {
|
|
447
|
+
const value = Number(localStorage.getItem(WIDTH_KEY));
|
|
448
|
+
return value > 0 && value < 1 ? value : .4;
|
|
449
|
+
} catch {
|
|
450
|
+
return .4;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
/** The docked inspector's share of the workspace: dragged or arrowed, remembered for this viewer, never under 360px. */
|
|
454
|
+
function useSplit(root) {
|
|
455
|
+
const [fraction, setFraction] = useState(.4), [total, setTotal] = useState(0);
|
|
456
|
+
useEffect(() => setFraction(readWidth()), []);
|
|
457
|
+
useEffect(() => {
|
|
458
|
+
const element = root.current;
|
|
459
|
+
if (!element) return;
|
|
460
|
+
const observer = new ResizeObserver(([entry]) => requestAnimationFrame(() => setTotal(entry.contentRect.width)));
|
|
461
|
+
observer.observe(element);
|
|
462
|
+
return () => observer.disconnect();
|
|
463
|
+
}, [root]);
|
|
464
|
+
const max = total ? Math.max(MIN_PX, total - MIN_LEFT_PX) : Infinity;
|
|
465
|
+
const width = Math.round(Math.min(max, Math.max(MIN_PX, fraction * (total || 1e3))));
|
|
466
|
+
const commit = (next) => {
|
|
467
|
+
const value = Math.min(.85, Math.max(.1, next));
|
|
468
|
+
setFraction(value);
|
|
469
|
+
try {
|
|
470
|
+
localStorage.setItem(WIDTH_KEY, String(value));
|
|
471
|
+
} catch {}
|
|
472
|
+
};
|
|
473
|
+
return {
|
|
474
|
+
width,
|
|
475
|
+
separator: {
|
|
476
|
+
role: "separator",
|
|
477
|
+
tabIndex: 0,
|
|
478
|
+
"aria-orientation": "vertical",
|
|
479
|
+
"aria-label": "Resize agent inspector",
|
|
480
|
+
"aria-valuemin": total ? Math.round(MIN_PX / total * 100) : 0,
|
|
481
|
+
"aria-valuemax": total ? Math.round(max / total * 100) : 100,
|
|
482
|
+
"aria-valuenow": total ? Math.round(width / total * 100) : Math.round(fraction * 100),
|
|
483
|
+
onKeyDown: (event) => {
|
|
484
|
+
const by = event.key === "ArrowLeft" ? STEP : event.key === "ArrowRight" ? -.02 : 0;
|
|
485
|
+
if (by) {
|
|
486
|
+
event.preventDefault();
|
|
487
|
+
commit(width / (total || 1e3) + by);
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
onPointerDown: (event) => {
|
|
491
|
+
const box = root.current?.getBoundingClientRect();
|
|
492
|
+
if (!box) return;
|
|
493
|
+
event.preventDefault();
|
|
494
|
+
const handle = event.currentTarget;
|
|
495
|
+
handle.setPointerCapture(event.pointerId);
|
|
496
|
+
const move = (next) => commit((box.right - next.clientX) / box.width);
|
|
497
|
+
const stop = () => {
|
|
498
|
+
handle.removeEventListener("pointermove", move);
|
|
499
|
+
handle.removeEventListener("pointerup", stop);
|
|
500
|
+
handle.removeEventListener("pointercancel", stop);
|
|
501
|
+
};
|
|
502
|
+
handle.addEventListener("pointermove", move);
|
|
503
|
+
handle.addEventListener("pointerup", stop);
|
|
504
|
+
handle.addEventListener("pointercancel", stop);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* The height the waterfall rows or graph may take so the run fills its pane: their own height plus the room left under
|
|
511
|
+
* the chart's last row (toolbar, axis and latest activity keep theirs). FlowWaterfall sizes its virtualized rows by `maxHeight`, so it is measured, not styled.
|
|
512
|
+
*/
|
|
513
|
+
function useFillHeight(pane) {
|
|
514
|
+
const [height, setHeight] = useState();
|
|
515
|
+
useEffect(() => {
|
|
516
|
+
const element = pane.current;
|
|
517
|
+
if (!element) return;
|
|
518
|
+
let frame = 0;
|
|
519
|
+
const measure = () => {
|
|
520
|
+
cancelAnimationFrame(frame);
|
|
521
|
+
frame = requestAnimationFrame(() => {
|
|
522
|
+
const root = element.querySelector(".fw-root");
|
|
523
|
+
const target = [...element.querySelectorAll("[data-trace-graph], .fw-scroller")].find((candidate) => candidate.offsetHeight > 0);
|
|
524
|
+
if (!root || !target) return;
|
|
525
|
+
const style = getComputedStyle(root), bottom = Math.max(...[...root.children].map((child) => child.getBoundingClientRect().bottom));
|
|
526
|
+
const slack = root.getBoundingClientRect().bottom - parseFloat(style.paddingBottom) - parseFloat(style.borderBottomWidth) - bottom;
|
|
527
|
+
const next = Math.floor(target.offsetHeight + slack);
|
|
528
|
+
if (next > 160) setHeight((prior) => prior !== void 0 && Math.abs(prior - next) < 2 ? prior : next);
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
const observer = new ResizeObserver(measure);
|
|
532
|
+
observer.observe(element);
|
|
533
|
+
const watch = new MutationObserver(() => {
|
|
534
|
+
const root = element.querySelector(".fw-root");
|
|
535
|
+
if (root) observer.observe(root);
|
|
536
|
+
measure();
|
|
537
|
+
});
|
|
538
|
+
watch.observe(element, {
|
|
539
|
+
childList: true,
|
|
540
|
+
subtree: true
|
|
541
|
+
});
|
|
542
|
+
const root = element.querySelector(".fw-root");
|
|
543
|
+
if (root) observer.observe(root);
|
|
544
|
+
return () => {
|
|
545
|
+
cancelAnimationFrame(frame);
|
|
546
|
+
observer.disconnect();
|
|
547
|
+
watch.disconnect();
|
|
548
|
+
};
|
|
549
|
+
}, [pane]);
|
|
550
|
+
return height;
|
|
551
|
+
}
|
|
552
|
+
/** Text held to a few lines with a control to read the rest; the control appears only when lines are hidden. */
|
|
553
|
+
function Clamped({ text, lines, className }) {
|
|
554
|
+
const [open, setOpen] = useState(false), [over, setOver] = useState(false), ref = useRef(null);
|
|
555
|
+
useLayoutEffect(() => {
|
|
556
|
+
const element = ref.current;
|
|
557
|
+
if (!element || open) return;
|
|
558
|
+
const measure = () => setOver(element.scrollHeight > element.clientHeight + 1);
|
|
559
|
+
measure();
|
|
560
|
+
const observer = new ResizeObserver(() => requestAnimationFrame(measure));
|
|
561
|
+
observer.observe(element);
|
|
562
|
+
return () => observer.disconnect();
|
|
563
|
+
}, [
|
|
564
|
+
text,
|
|
565
|
+
open,
|
|
566
|
+
lines
|
|
567
|
+
]);
|
|
568
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
569
|
+
className: "min-w-0",
|
|
570
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
571
|
+
ref,
|
|
572
|
+
className: cn$1("m-0 whitespace-pre-wrap break-words", !open && (lines === 2 ? "line-clamp-2" : "line-clamp-3"), className),
|
|
573
|
+
children: text
|
|
574
|
+
}), (over || open) && /* @__PURE__ */ jsx("button", {
|
|
575
|
+
type: "button",
|
|
576
|
+
"aria-expanded": open,
|
|
577
|
+
onClick: () => setOpen(!open),
|
|
578
|
+
className: "mt-0.5 rounded-sm text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-[hsl(var(--ring))]",
|
|
579
|
+
children: open ? "Show less" : "Show all"
|
|
580
|
+
})]
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
/** Fresh input, cached input and output as one stacked bar, so the split reads at a glance and its numbers stay in the line. */
|
|
584
|
+
function TokenBar({ fresh, cached, out }) {
|
|
585
|
+
const total = fresh + cached + out;
|
|
586
|
+
if (!total) return null;
|
|
587
|
+
const parts = [
|
|
588
|
+
[
|
|
589
|
+
"fresh in",
|
|
590
|
+
fresh,
|
|
591
|
+
"var(--chart-series-1)"
|
|
592
|
+
],
|
|
593
|
+
[
|
|
594
|
+
"cached in",
|
|
595
|
+
cached,
|
|
596
|
+
"var(--chart-muted)"
|
|
597
|
+
],
|
|
598
|
+
[
|
|
599
|
+
"out",
|
|
600
|
+
out,
|
|
601
|
+
"var(--chart-series-2)"
|
|
602
|
+
]
|
|
603
|
+
];
|
|
604
|
+
const label = parts.filter(([, value]) => value).map(([name, value]) => `${formatCount(value)} ${name}`).join(", ");
|
|
605
|
+
return /* @__PURE__ */ jsx("span", {
|
|
606
|
+
role: "img",
|
|
607
|
+
"aria-label": `Tokens: ${label}`,
|
|
608
|
+
title: label,
|
|
609
|
+
className: "flex h-1 w-full overflow-hidden rounded-full bg-muted",
|
|
610
|
+
children: parts.map(([name, value, color]) => value ? /* @__PURE__ */ jsx("span", { style: {
|
|
611
|
+
width: `${value / total * 100}%`,
|
|
612
|
+
background: color
|
|
613
|
+
} }, name) : null)
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
/** The header's one line of numbers, from the agent's own span: duration, cost and tokens; unknowns are left out. */
|
|
617
|
+
function metaOf(span) {
|
|
618
|
+
if (!span) return {
|
|
619
|
+
line: [],
|
|
620
|
+
tokens: void 0
|
|
621
|
+
};
|
|
622
|
+
const line = [];
|
|
623
|
+
if (span.endMs > span.startMs) line.push(formatSpanDuration(span.endMs - span.startMs));
|
|
624
|
+
if (span.costUsd) line.push((span.costBound === "lower" ? "≥" : "") + formatCost(span.costUsd));
|
|
625
|
+
const input = span.promptTokens ?? 0, out = span.completionTokens ?? 0, cached = Math.min(span.cachedTokens ?? 0, input);
|
|
626
|
+
if (input) line.push(`${formatCount(input)} in`);
|
|
627
|
+
if (out) line.push(`${formatCount(out)} out`);
|
|
628
|
+
return {
|
|
629
|
+
line,
|
|
630
|
+
tokens: input || out ? {
|
|
631
|
+
fresh: input - cached,
|
|
632
|
+
cached,
|
|
633
|
+
out
|
|
634
|
+
} : void 0
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
/** Facts the header already states are not repeated in Details. */
|
|
638
|
+
const IN_HEADER = /^(tokens|cost|duration|failure|model)$/i;
|
|
639
|
+
/** The selected agent: a compact header, its failure, its commission, then its conversation; recorded facts wait in Details. */
|
|
640
|
+
function AgentInspector({ name, model, status, span, live, failure, commission, facts, detail, children, scroller, unseen, onJump, onClose, mode, style }) {
|
|
641
|
+
const brand = model ? modelBrandFor(model) : null, meta = metaOf(span);
|
|
642
|
+
const details = facts.filter((fact) => !IN_HEADER.test(fact.label) && fact.value !== failure);
|
|
643
|
+
const back = useRef(null);
|
|
644
|
+
const sheet = mode === "sheet";
|
|
645
|
+
useEffect(() => {
|
|
646
|
+
if (!sheet) return;
|
|
647
|
+
const opener = document.activeElement, overflow = document.body.style.overflow;
|
|
648
|
+
document.body.style.overflow = "hidden";
|
|
649
|
+
back.current?.focus();
|
|
650
|
+
return () => {
|
|
651
|
+
document.body.style.overflow = overflow;
|
|
652
|
+
if (opener?.isConnected) opener.focus();
|
|
653
|
+
};
|
|
654
|
+
}, [sheet]);
|
|
655
|
+
return /* @__PURE__ */ jsxs("aside", {
|
|
656
|
+
role: sheet ? "dialog" : void 0,
|
|
657
|
+
"aria-modal": sheet || void 0,
|
|
658
|
+
"aria-label": `${name} conversation`,
|
|
659
|
+
style,
|
|
660
|
+
onKeyDown: (event) => {
|
|
661
|
+
if (event.key === "Escape") {
|
|
662
|
+
event.stopPropagation();
|
|
663
|
+
onClose();
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
className: cn$1("relative flex min-h-0 flex-col overflow-hidden bg-card text-foreground", mode === "docked" && "shrink-0 rounded-xl border border-border", mode === "overlay" && "fixed inset-y-0 right-0 z-40 w-full max-w-[420px] border-l border-border shadow-2xl", sheet && "fixed inset-0 z-[1100] bg-background"),
|
|
667
|
+
children: [
|
|
668
|
+
/* @__PURE__ */ jsxs("header", {
|
|
669
|
+
className: "flex shrink-0 items-start gap-2 border-b border-border px-3 py-2.5",
|
|
670
|
+
children: [
|
|
671
|
+
sheet && /* @__PURE__ */ jsxs("button", {
|
|
672
|
+
ref: back,
|
|
673
|
+
type: "button",
|
|
674
|
+
onClick: onClose,
|
|
675
|
+
"aria-label": "Back to the run",
|
|
676
|
+
className: "-ml-1 flex h-9 shrink-0 items-center gap-1 rounded-md px-1.5 text-sm text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-[hsl(var(--ring))]",
|
|
677
|
+
children: [/* @__PURE__ */ jsx(ArrowLeft, {
|
|
678
|
+
"aria-hidden": true,
|
|
679
|
+
className: "h-4 w-4"
|
|
680
|
+
}), "Back"]
|
|
681
|
+
}),
|
|
682
|
+
/* @__PURE__ */ jsxs("div", {
|
|
683
|
+
className: cn$1("min-w-0 flex-1", sheet && "pt-2"),
|
|
684
|
+
children: [
|
|
685
|
+
/* @__PURE__ */ jsxs("div", {
|
|
686
|
+
className: "flex min-w-0 items-start gap-2",
|
|
687
|
+
children: [
|
|
688
|
+
/* @__PURE__ */ jsx(RunStatusIcon, {
|
|
689
|
+
status,
|
|
690
|
+
className: "mt-0.5 size-4 shrink-0"
|
|
691
|
+
}),
|
|
692
|
+
/* @__PURE__ */ jsx("h3", {
|
|
693
|
+
className: "m-0 min-w-0 flex-1 break-words text-sm font-semibold leading-5",
|
|
694
|
+
children: name
|
|
695
|
+
}),
|
|
696
|
+
live && /* @__PURE__ */ jsxs("span", {
|
|
697
|
+
className: "flex shrink-0 items-center gap-1 text-xs font-medium text-[var(--surface-info-text)]",
|
|
698
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
699
|
+
"aria-hidden": true,
|
|
700
|
+
className: "h-1.5 w-1.5 rounded-full bg-current"
|
|
701
|
+
}), "Live"]
|
|
702
|
+
})
|
|
703
|
+
]
|
|
704
|
+
}),
|
|
705
|
+
(model || meta.line.length > 0) && /* @__PURE__ */ jsxs("p", {
|
|
706
|
+
className: "m-0 mt-1 min-w-0 text-xs tabular-nums text-muted-foreground",
|
|
707
|
+
children: [brand && /* @__PURE__ */ jsx("span", {
|
|
708
|
+
className: "mr-1.5 inline-flex align-[-3px]",
|
|
709
|
+
children: /* @__PURE__ */ jsx(ModelBrandStack, {
|
|
710
|
+
identity: brand,
|
|
711
|
+
size: "sm"
|
|
712
|
+
})
|
|
713
|
+
}), [model, ...meta.line].filter(Boolean).join(" · ")]
|
|
714
|
+
}),
|
|
715
|
+
meta.tokens && /* @__PURE__ */ jsx("div", {
|
|
716
|
+
className: "mt-1.5 max-w-[240px]",
|
|
717
|
+
children: /* @__PURE__ */ jsx(TokenBar, { ...meta.tokens })
|
|
718
|
+
})
|
|
719
|
+
]
|
|
720
|
+
}),
|
|
721
|
+
!sheet && /* @__PURE__ */ jsx("button", {
|
|
722
|
+
type: "button",
|
|
723
|
+
"aria-label": "Close conversation",
|
|
724
|
+
title: "Close (Esc)",
|
|
725
|
+
onClick: onClose,
|
|
726
|
+
className: "shrink-0 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-[hsl(var(--ring))]",
|
|
727
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
728
|
+
"aria-hidden": true,
|
|
729
|
+
className: "h-4 w-4"
|
|
730
|
+
})
|
|
731
|
+
})
|
|
732
|
+
]
|
|
733
|
+
}),
|
|
734
|
+
failure && /* @__PURE__ */ jsx("section", {
|
|
735
|
+
"aria-label": "Failure",
|
|
736
|
+
className: "shrink-0 border-b border-border px-3 py-2 text-sm text-[var(--surface-danger-text)]",
|
|
737
|
+
children: /* @__PURE__ */ jsx(Clamped, {
|
|
738
|
+
text: failure,
|
|
739
|
+
lines: 3
|
|
740
|
+
})
|
|
741
|
+
}),
|
|
742
|
+
commission && (commission.text || commission.by) && /* @__PURE__ */ jsxs("section", {
|
|
743
|
+
"aria-label": "Commission",
|
|
744
|
+
className: "shrink-0 border-b border-border px-3 py-2 text-sm",
|
|
745
|
+
children: [commission.by && /* @__PURE__ */ jsxs("p", {
|
|
746
|
+
className: "m-0 text-xs text-muted-foreground",
|
|
747
|
+
children: ["from ", commission.by]
|
|
748
|
+
}), commission.text && /* @__PURE__ */ jsx(Clamped, {
|
|
749
|
+
text: commission.text,
|
|
750
|
+
lines: 2
|
|
751
|
+
})]
|
|
752
|
+
}),
|
|
753
|
+
/* @__PURE__ */ jsx("div", {
|
|
754
|
+
ref: scroller,
|
|
755
|
+
"data-primary-content": "",
|
|
756
|
+
className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
|
|
757
|
+
children
|
|
758
|
+
}),
|
|
759
|
+
unseen && /* @__PURE__ */ jsx("button", {
|
|
760
|
+
type: "button",
|
|
761
|
+
onClick: onJump,
|
|
762
|
+
className: "absolute bottom-14 left-1/2 -translate-x-1/2 rounded-full border border-border bg-background px-3 py-1 text-xs shadow-sm",
|
|
763
|
+
children: "New output"
|
|
764
|
+
}),
|
|
765
|
+
(details.length > 0 || detail) && /* @__PURE__ */ jsxs("details", {
|
|
766
|
+
className: "group max-h-[45%] shrink-0 overflow-y-auto border-t border-border text-sm",
|
|
767
|
+
children: [/* @__PURE__ */ jsxs("summary", {
|
|
768
|
+
className: "flex cursor-pointer list-none items-center gap-1.5 px-3 py-2 text-xs font-medium text-muted-foreground hover:text-foreground [&::-webkit-details-marker]:hidden",
|
|
769
|
+
children: [/* @__PURE__ */ jsx(ChevronRight, {
|
|
770
|
+
"aria-hidden": true,
|
|
771
|
+
className: "h-3.5 w-3.5 transition-transform group-open:rotate-90"
|
|
772
|
+
}), "Details"]
|
|
773
|
+
}), /* @__PURE__ */ jsxs("section", {
|
|
774
|
+
"aria-label": "Recorded agent details",
|
|
775
|
+
tabIndex: 0,
|
|
776
|
+
className: "px-3 pb-3",
|
|
777
|
+
children: [details.length > 0 && /* @__PURE__ */ jsx("dl", {
|
|
778
|
+
className: "m-0 grid grid-cols-[minmax(0,auto)_minmax(0,1fr)] gap-x-3 gap-y-1",
|
|
779
|
+
children: details.map((fact, index) => /* @__PURE__ */ jsxs("div", {
|
|
780
|
+
className: "contents",
|
|
781
|
+
children: [/* @__PURE__ */ jsx("dt", {
|
|
782
|
+
className: "text-xs text-muted-foreground",
|
|
783
|
+
children: fact.label
|
|
784
|
+
}), /* @__PURE__ */ jsx("dd", {
|
|
785
|
+
className: "m-0 whitespace-pre-wrap break-words text-xs",
|
|
786
|
+
children: fact.value
|
|
787
|
+
})]
|
|
788
|
+
}, index))
|
|
789
|
+
}), detail]
|
|
790
|
+
})]
|
|
791
|
+
})
|
|
792
|
+
]
|
|
793
|
+
});
|
|
794
|
+
}
|
|
395
795
|
// src/run/trace-workspace.tsx
|
|
396
796
|
/** The agent that owns a span: the span itself when it is an agent, else its nearest agent ancestor. */
|
|
397
797
|
function agentOf(span, byId) {
|
|
@@ -481,11 +881,11 @@ function agentGraphAt(spans, byId, at, epoch, cards, extraEdges = []) {
|
|
|
481
881
|
};
|
|
482
882
|
}
|
|
483
883
|
/**
|
|
484
|
-
* The run workspace: the agent timeline and span waterfall over one clock,
|
|
485
|
-
*
|
|
884
|
+
* The run workspace: the agent timeline and span waterfall over one clock, filling its parent's height, with the selected
|
|
885
|
+
* agent's conversation at the center of its inspector, and the page's code workspace and scoreboard following the time cursor. Scrubbing is time travel:
|
|
486
886
|
* every panel shows what was true at the cursor, and the conversation shows what had been said by then.
|
|
487
887
|
*/
|
|
488
|
-
function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChange, files, scoreboard, selectedAgentId, conversationOpen, onSelectAgent, renderAgentDrawer, docked, commission, agentCards, agentGraph, className, ...waterfall }) {
|
|
888
|
+
function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChange, files, scoreboard, selectedAgentId, conversationOpen, onSelectAgent, renderAgentDrawer, transcriptNote, docked, commission, agentCards, agentGraph, className, ...waterfall }) {
|
|
489
889
|
const byId = useMemo(() => new Map(trace.spans.flatMap((span) => span.id ? [[span.id, span]] : [])), [trace]);
|
|
490
890
|
const [ownSelection, setOwnSelection] = useState();
|
|
491
891
|
const selected = selectedAgentId ?? ownSelection;
|
|
@@ -534,11 +934,35 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
|
|
|
534
934
|
identity: showConversation ? selected : void 0,
|
|
535
935
|
content: items
|
|
536
936
|
});
|
|
937
|
+
const root = useRef(null), pane = useRef(null);
|
|
938
|
+
const phone = usePhone(), split = useSplit(root), fill = useFillHeight(pane);
|
|
939
|
+
const mode = phone ? "sheet" : docked ? "docked" : "overlay";
|
|
940
|
+
const status = live ? "running" : selectedNode?.status ?? runStatus(agent?.status ?? "unknown");
|
|
941
|
+
const failure = facts.find((fact) => fact.label === "Failure")?.value ?? (status === "failed" ? selectedNode?.result ?? void 0 : void 0);
|
|
942
|
+
const said = (text) => !!text && shown.some((entry) => entry.kind === "message" && entry.role === "user" && entry.content.trim() === text.trim());
|
|
943
|
+
const silent = agentId && transcriptNote?.(agentId) || "No conversation was recorded for this agent.";
|
|
944
|
+
const conversation = !streamUrl ? /* @__PURE__ */ jsx("p", {
|
|
945
|
+
className: "p-4 text-sm text-muted-foreground",
|
|
946
|
+
children: silent
|
|
947
|
+
}) : cursor !== null && origin === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
948
|
+
className: "p-4 text-sm text-muted-foreground",
|
|
949
|
+
children: "This conversation has no timestamps to replay at the cursor."
|
|
950
|
+
}) : /* @__PURE__ */ jsx(AgentTimeline, {
|
|
951
|
+
items,
|
|
952
|
+
isThinking: live && items.length === 0,
|
|
953
|
+
emptyState: /* @__PURE__ */ jsx("p", {
|
|
954
|
+
className: "p-4 text-sm text-muted-foreground",
|
|
955
|
+
"data-stream": stream.error ? "reconnecting" : ended ? "ended" : "empty",
|
|
956
|
+
children: stream.error ? ended ? stream.error : `Reconnecting… ${stream.error}` : ended ? silent : "No messages yet."
|
|
957
|
+
})
|
|
958
|
+
});
|
|
537
959
|
const left = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
538
960
|
/* @__PURE__ */ jsx("div", {
|
|
539
|
-
|
|
961
|
+
ref: pane,
|
|
962
|
+
className: cn$1("flex min-h-[320px] min-w-0 flex-1 flex-col", showConversation && mode === "overlay" && "lg:mr-[420px]"),
|
|
540
963
|
children: /* @__PURE__ */ jsx(TraceWaterfall, {
|
|
541
964
|
...waterfall,
|
|
965
|
+
maxHeight: fill ?? waterfall.maxHeight,
|
|
542
966
|
trace,
|
|
543
967
|
markers: events,
|
|
544
968
|
selectedId: agentId,
|
|
@@ -559,23 +983,22 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
|
|
|
559
983
|
onSelect: select
|
|
560
984
|
}),
|
|
561
985
|
graph: /* @__PURE__ */ jsxs("section", {
|
|
562
|
-
|
|
986
|
+
"data-trace-graph": "",
|
|
987
|
+
className: "relative flex min-h-[320px] flex-col",
|
|
988
|
+
style: { height: fill ?? "max(60vh, 480px)" },
|
|
563
989
|
"aria-label": "Agent graph",
|
|
564
|
-
children: [/* @__PURE__ */
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
children: "Hub"
|
|
577
|
-
})]
|
|
578
|
-
})]
|
|
990
|
+
children: [/* @__PURE__ */ jsx(SegmentedControl, {
|
|
991
|
+
"aria-label": "Graph layout",
|
|
992
|
+
value: graphLayout,
|
|
993
|
+
onValueChange: setGraphLayout,
|
|
994
|
+
className: "absolute right-2 top-1 z-10",
|
|
995
|
+
options: [{
|
|
996
|
+
value: "tree",
|
|
997
|
+
label: "Tree"
|
|
998
|
+
}, {
|
|
999
|
+
value: "hub",
|
|
1000
|
+
label: "Hub"
|
|
1001
|
+
}]
|
|
579
1002
|
}), /* @__PURE__ */ jsx(AgentGraph$1, {
|
|
580
1003
|
...graphOptions,
|
|
581
1004
|
layout: graphLayout,
|
|
@@ -592,116 +1015,53 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
|
|
|
592
1015
|
"needs-you",
|
|
593
1016
|
"failed"
|
|
594
1017
|
].includes(node.status)),
|
|
595
|
-
className: cn("min-h-0 flex-1", graphOptions.className)
|
|
1018
|
+
className: cn$1("min-h-0 flex-1", graphOptions.className)
|
|
596
1019
|
})]
|
|
597
1020
|
})
|
|
598
1021
|
})
|
|
599
1022
|
}),
|
|
600
1023
|
files && /* @__PURE__ */ jsx("section", {
|
|
601
1024
|
"aria-label": "Code at the cursor",
|
|
602
|
-
className: "flex h-[70vh] min-h-0 flex-col overflow-hidden rounded-xl border border-border bg-card",
|
|
1025
|
+
className: "flex h-[70vh] min-h-0 shrink-0 flex-col overflow-hidden rounded-xl border border-border bg-card",
|
|
603
1026
|
children: files
|
|
604
1027
|
}),
|
|
605
1028
|
scoreboard && /* @__PURE__ */ jsx("section", {
|
|
606
1029
|
"aria-label": "Scoreboard at the cursor",
|
|
607
|
-
className: "overflow-hidden rounded-xl border border-border bg-card",
|
|
1030
|
+
className: "shrink-0 overflow-hidden rounded-xl border border-border bg-card",
|
|
608
1031
|
children: scoreboard
|
|
609
1032
|
})
|
|
610
1033
|
] });
|
|
611
1034
|
return /* @__PURE__ */ jsxs("div", {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1035
|
+
ref: root,
|
|
1036
|
+
className: cn$1("sandbox-trace-workspace flex h-full min-h-0 flex-row", className),
|
|
1037
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1038
|
+
className: cn$1("flex min-h-0 min-w-0 flex-1 flex-col gap-3", (files || scoreboard) && "overflow-y-auto"),
|
|
615
1039
|
children: left
|
|
616
|
-
})
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
/* @__PURE__ */ jsx("button", {
|
|
642
|
-
type: "button",
|
|
643
|
-
"aria-label": "Close conversation",
|
|
644
|
-
title: "Close (Esc)",
|
|
645
|
-
onClick: () => select(void 0),
|
|
646
|
-
className: "shrink-0 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
647
|
-
children: /* @__PURE__ */ jsx(X, {
|
|
648
|
-
"aria-hidden": true,
|
|
649
|
-
className: "h-4 w-4"
|
|
650
|
-
})
|
|
651
|
-
})
|
|
652
|
-
]
|
|
653
|
-
}),
|
|
654
|
-
(facts.length > 0 || detail) && /* @__PURE__ */ jsxs("section", {
|
|
655
|
-
"aria-label": "Recorded agent details",
|
|
656
|
-
tabIndex: 0,
|
|
657
|
-
className: "max-h-[35vh] shrink-0 overflow-y-auto border-b border-border px-3 py-2 text-sm",
|
|
658
|
-
children: [facts.length > 0 && /* @__PURE__ */ jsx("dl", {
|
|
659
|
-
className: "space-y-2",
|
|
660
|
-
children: facts.map((fact, index) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
|
|
661
|
-
className: "text-xs text-muted-foreground",
|
|
662
|
-
children: fact.label
|
|
663
|
-
}), /* @__PURE__ */ jsx("dd", {
|
|
664
|
-
className: "m-0 whitespace-pre-wrap break-words",
|
|
665
|
-
children: fact.value
|
|
666
|
-
})] }, index))
|
|
667
|
-
}), detail]
|
|
668
|
-
}),
|
|
669
|
-
commission && (commission.text || commission.by) && /* @__PURE__ */ jsxs("section", {
|
|
670
|
-
"aria-label": "Commission",
|
|
671
|
-
className: "shrink-0 border-b border-border bg-muted/40 px-3 py-2 text-sm",
|
|
672
|
-
children: [commission.by && /* @__PURE__ */ jsxs("p", {
|
|
673
|
-
className: "m-0 mb-1 text-xs text-muted-foreground",
|
|
674
|
-
children: ["commissioned by ", commission.by]
|
|
675
|
-
}), commission.text && /* @__PURE__ */ jsx("p", {
|
|
676
|
-
className: "m-0 line-clamp-6 whitespace-pre-wrap break-words",
|
|
677
|
-
children: commission.text
|
|
678
|
-
})]
|
|
679
|
-
}),
|
|
680
|
-
/* @__PURE__ */ jsx("div", {
|
|
681
|
-
ref: scroller,
|
|
682
|
-
"data-primary-content": "",
|
|
683
|
-
className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
|
|
684
|
-
children: streamUrl && (cursor === null || origin !== void 0) ? /* @__PURE__ */ jsx(AgentTimeline, {
|
|
685
|
-
items,
|
|
686
|
-
isThinking: live && items.length === 0,
|
|
687
|
-
emptyState: /* @__PURE__ */ jsx("p", {
|
|
688
|
-
className: "p-4 text-sm text-muted-foreground",
|
|
689
|
-
"data-stream": stream.error ? "reconnecting" : ended ? "ended" : "empty",
|
|
690
|
-
children: stream.error ? ended ? stream.error : `Reconnecting… ${stream.error}` : ended ? "The conversation ended." : "No messages yet."
|
|
691
|
-
})
|
|
692
|
-
}) : /* @__PURE__ */ jsx("p", {
|
|
693
|
-
className: "p-4 text-sm text-muted-foreground",
|
|
694
|
-
children: "Conversation: not time-indexed"
|
|
695
|
-
})
|
|
696
|
-
}),
|
|
697
|
-
unseen && /* @__PURE__ */ jsx("button", {
|
|
698
|
-
type: "button",
|
|
699
|
-
onClick: () => jump(),
|
|
700
|
-
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",
|
|
701
|
-
children: "New output"
|
|
702
|
-
})
|
|
703
|
-
]
|
|
704
|
-
})]
|
|
1040
|
+
}), showConversation && agentId && /* @__PURE__ */ jsxs(Fragment, { children: [mode === "docked" && /* @__PURE__ */ jsx("div", {
|
|
1041
|
+
...split.separator,
|
|
1042
|
+
className: "group relative w-3 shrink-0 cursor-col-resize touch-none outline-none",
|
|
1043
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
1044
|
+
"aria-hidden": true,
|
|
1045
|
+
className: "absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border transition-colors group-hover:bg-[hsl(var(--ring))] group-focus-visible:w-0.5 group-focus-visible:bg-[hsl(var(--ring))]"
|
|
1046
|
+
})
|
|
1047
|
+
}), /* @__PURE__ */ jsx(AgentInspector, {
|
|
1048
|
+
name: agentName ?? agentId,
|
|
1049
|
+
model: agentModel && !/^not recorded$/i.test(agentModel) ? agentModel : void 0,
|
|
1050
|
+
status,
|
|
1051
|
+
span: agent,
|
|
1052
|
+
live,
|
|
1053
|
+
failure,
|
|
1054
|
+
commission: said(commission?.text) ? { by: commission?.by } : commission,
|
|
1055
|
+
facts,
|
|
1056
|
+
detail,
|
|
1057
|
+
scroller,
|
|
1058
|
+
unseen,
|
|
1059
|
+
onJump: () => jump(),
|
|
1060
|
+
onClose: () => select(void 0),
|
|
1061
|
+
mode,
|
|
1062
|
+
style: mode === "docked" ? { width: split.width } : void 0,
|
|
1063
|
+
children: conversation
|
|
1064
|
+
})] })]
|
|
705
1065
|
});
|
|
706
1066
|
}
|
|
707
1067
|
export { useEventStream as a, useAgentStream as i, LatestEvents as n, TraceWaterfall as o, foldAgentRows as r, traceSpanMark as s, TraceWorkspace as t };
|