@tangle-network/sandbox-ui 6.0.1 → 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.
@@ -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-DZFn7QDO.js";
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 { activityTiming, ambientStyle, syncAmbient } from "@tangle-network/charts/react/agent-graph/presentation";
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";
@@ -426,6 +428,370 @@ function useAgentStream(url, openEventSource, history) {
426
428
  } } : {}
427
429
  };
428
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
+ }
429
795
  // src/run/trace-workspace.tsx
430
796
  /** The agent that owns a span: the span itself when it is an agent, else its nearest agent ancestor. */
431
797
  function agentOf(span, byId) {
@@ -515,11 +881,11 @@ function agentGraphAt(spans, byId, at, epoch, cards, extraEdges = []) {
515
881
  };
516
882
  }
517
883
  /**
518
- * The run workspace: the agent timeline and span waterfall over one clock, the selected agent's conversation
519
- * streaming in a right-hand drawer, and the page's code workspace and scoreboard following the time cursor. Scrubbing is time travel:
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:
520
886
  * every panel shows what was true at the cursor, and the conversation shows what had been said by then.
521
887
  */
522
- 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 }) {
523
889
  const byId = useMemo(() => new Map(trace.spans.flatMap((span) => span.id ? [[span.id, span]] : [])), [trace]);
524
890
  const [ownSelection, setOwnSelection] = useState();
525
891
  const selected = selectedAgentId ?? ownSelection;
@@ -568,11 +934,35 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
568
934
  identity: showConversation ? selected : void 0,
569
935
  content: items
570
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
+ });
571
959
  const left = /* @__PURE__ */ jsxs(Fragment, { children: [
572
960
  /* @__PURE__ */ jsx("div", {
573
- className: showConversation && !docked ? "min-w-0 lg:mr-[420px]" : "min-w-0",
961
+ ref: pane,
962
+ className: cn$1("flex min-h-[320px] min-w-0 flex-1 flex-col", showConversation && mode === "overlay" && "lg:mr-[420px]"),
574
963
  children: /* @__PURE__ */ jsx(TraceWaterfall, {
575
964
  ...waterfall,
965
+ maxHeight: fill ?? waterfall.maxHeight,
576
966
  trace,
577
967
  markers: events,
578
968
  selectedId: agentId,
@@ -593,23 +983,22 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
593
983
  onSelect: select
594
984
  }),
595
985
  graph: /* @__PURE__ */ jsxs("section", {
596
- className: "flex h-[60vh] min-h-[480px] flex-col",
986
+ "data-trace-graph": "",
987
+ className: "relative flex min-h-[320px] flex-col",
988
+ style: { height: fill ?? "max(60vh, 480px)" },
597
989
  "aria-label": "Agent graph",
598
- children: [/* @__PURE__ */ jsxs("label", {
599
- className: "flex h-8 shrink-0 items-center justify-end gap-2 px-3 text-xs text-muted-foreground",
600
- children: ["Layout", /* @__PURE__ */ jsxs("select", {
601
- "aria-label": "Graph layout",
602
- value: graphLayout,
603
- onChange: (event) => setGraphLayout(event.currentTarget.value),
604
- className: "rounded-[var(--radius-control)] border border-border bg-card px-2 py-1 text-foreground",
605
- children: [/* @__PURE__ */ jsx("option", {
606
- value: "tree",
607
- children: "Tree"
608
- }), /* @__PURE__ */ jsx("option", {
609
- value: "hub",
610
- children: "Hub"
611
- })]
612
- })]
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
+ }]
613
1002
  }), /* @__PURE__ */ jsx(AgentGraph$1, {
614
1003
  ...graphOptions,
615
1004
  layout: graphLayout,
@@ -626,116 +1015,53 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
626
1015
  "needs-you",
627
1016
  "failed"
628
1017
  ].includes(node.status)),
629
- className: cn("min-h-0 flex-1", graphOptions.className)
1018
+ className: cn$1("min-h-0 flex-1", graphOptions.className)
630
1019
  })]
631
1020
  })
632
1021
  })
633
1022
  }),
634
1023
  files && /* @__PURE__ */ jsx("section", {
635
1024
  "aria-label": "Code at the cursor",
636
- 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",
637
1026
  children: files
638
1027
  }),
639
1028
  scoreboard && /* @__PURE__ */ jsx("section", {
640
1029
  "aria-label": "Scoreboard at the cursor",
641
- className: "overflow-hidden rounded-xl border border-border bg-card",
1030
+ className: "shrink-0 overflow-hidden rounded-xl border border-border bg-card",
642
1031
  children: scoreboard
643
1032
  })
644
1033
  ] });
645
1034
  return /* @__PURE__ */ jsxs("div", {
646
- className: cn("sandbox-trace-workspace flex min-h-0 gap-3", docked && showConversation ? "flex-row" : "flex-col", className),
647
- children: [docked && showConversation ? /* @__PURE__ */ jsx("div", {
648
- className: "flex min-w-0 flex-1 flex-col gap-3",
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"),
649
1039
  children: left
650
- }) : left, showConversation && /* @__PURE__ */ jsxs("aside", {
651
- "aria-label": `${agentName} conversation`,
652
- onKeyDown: (event) => {
653
- if (event.key === "Escape") select(void 0);
654
- },
655
- className: docked ? "flex min-h-0 w-[clamp(320px,32vw,560px)] shrink-0 resize-x flex-col overflow-hidden rounded-xl border border-border bg-card" : "fixed inset-y-0 right-0 z-40 flex w-full max-w-[420px] flex-col border-l border-border bg-card shadow-2xl",
656
- children: [
657
- /* @__PURE__ */ jsxs("header", {
658
- className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2",
659
- children: [
660
- /* @__PURE__ */ jsx("span", {
661
- className: cn("h-1.5 w-1.5 shrink-0 rounded-full", live ? "bg-[var(--surface-info-text)]" : "bg-muted-foreground"),
662
- "aria-hidden": true,
663
- "data-motion": live ? "essential" : void 0,
664
- style: live ? ambientStyle : void 0,
665
- onAnimationStart: syncAmbient
666
- }),
667
- /* @__PURE__ */ jsx("h3", {
668
- className: "min-w-0 flex-1 truncate text-sm font-semibold",
669
- children: agentName
670
- }),
671
- agentModel && /* @__PURE__ */ jsx("span", {
672
- className: "truncate text-xs text-muted-foreground",
673
- children: agentModel
674
- }),
675
- /* @__PURE__ */ jsx("button", {
676
- type: "button",
677
- "aria-label": "Close conversation",
678
- title: "Close (Esc)",
679
- onClick: () => select(void 0),
680
- className: "shrink-0 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
681
- children: /* @__PURE__ */ jsx(X, {
682
- "aria-hidden": true,
683
- className: "h-4 w-4"
684
- })
685
- })
686
- ]
687
- }),
688
- (facts.length > 0 || detail) && /* @__PURE__ */ jsxs("section", {
689
- "aria-label": "Recorded agent details",
690
- tabIndex: 0,
691
- className: "max-h-[35vh] shrink-0 overflow-y-auto border-b border-border px-3 py-2 text-sm",
692
- children: [facts.length > 0 && /* @__PURE__ */ jsx("dl", {
693
- className: "space-y-2",
694
- children: facts.map((fact, index) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
695
- className: "text-xs text-muted-foreground",
696
- children: fact.label
697
- }), /* @__PURE__ */ jsx("dd", {
698
- className: "m-0 whitespace-pre-wrap break-words",
699
- children: fact.value
700
- })] }, index))
701
- }), detail]
702
- }),
703
- commission && (commission.text || commission.by) && /* @__PURE__ */ jsxs("section", {
704
- "aria-label": "Commission",
705
- className: "shrink-0 border-b border-border bg-muted/40 px-3 py-2 text-sm",
706
- children: [commission.by && /* @__PURE__ */ jsxs("p", {
707
- className: "m-0 mb-1 text-xs text-muted-foreground",
708
- children: ["commissioned by ", commission.by]
709
- }), commission.text && /* @__PURE__ */ jsx("p", {
710
- className: "m-0 line-clamp-6 whitespace-pre-wrap break-words",
711
- children: commission.text
712
- })]
713
- }),
714
- /* @__PURE__ */ jsx("div", {
715
- ref: scroller,
716
- "data-primary-content": "",
717
- className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
718
- children: streamUrl && (cursor === null || origin !== void 0) ? /* @__PURE__ */ jsx(AgentTimeline, {
719
- items,
720
- isThinking: live && items.length === 0,
721
- emptyState: /* @__PURE__ */ jsx("p", {
722
- className: "p-4 text-sm text-muted-foreground",
723
- "data-stream": stream.error ? "reconnecting" : ended ? "ended" : "empty",
724
- children: stream.error ? ended ? stream.error : `Reconnecting… ${stream.error}` : ended ? "The conversation ended." : "No messages yet."
725
- })
726
- }) : /* @__PURE__ */ jsx("p", {
727
- className: "p-4 text-sm text-muted-foreground",
728
- children: "Conversation: not time-indexed"
729
- })
730
- }),
731
- unseen && /* @__PURE__ */ jsx("button", {
732
- type: "button",
733
- onClick: () => jump(),
734
- 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",
735
- children: "New output"
736
- })
737
- ]
738
- })]
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
+ })] })]
739
1065
  });
740
1066
  }
741
1067
  export { useEventStream as a, useAgentStream as i, LatestEvents as n, TraceWaterfall as o, foldAgentRows as r, traceSpanMark as s, TraceWorkspace as t };