@tangle-network/sandbox-ui 7.0.0 → 7.0.2

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,18 +1,17 @@
1
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
- import { t as AgentGraph$1 } from "./AgentGraph-BGWx9hhS.js";
5
- import { s as timelineItems, t as useFollowTail } from "./use-follow-tail-Di1zlndg.js";
6
- import { SegmentedControl } from "@tangle-network/ui/primitives";
4
+ import { t as AgentGraph } from "./AgentGraph-BGWx9hhS.js";
5
+ import { s as timelineItems, t as useFollowTail } from "./use-follow-tail-Df965WQi.js";
6
+ import { Button, SegmentedControl } from "@tangle-network/ui/primitives";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
9
9
  import { ArrowLeft, ChevronRight, X } from "lucide-react";
10
10
  import { formatCost, formatCount, formatSpanDuration } from "@tangle-network/ui/utils";
11
11
  import { activityTiming } from "@tangle-network/charts/react/agent-graph/presentation";
12
- import { AgentTimeline } from "@tangle-network/ui/chat";
12
+ import { AgentTimeline, AgentTimelineColumn } from "@tangle-network/ui/chat";
13
13
  import { RunStatusIcon, runStatus } from "@tangle-network/ui/run";
14
14
  import { FlowWaterfall, toolGlyph } from "@tangle-network/charts/react";
15
- import { LatestEvents } from "@tangle-network/charts/react/agent-graph";
16
15
  import { useEventStream } from "@tangle-network/ui/hooks";
17
16
  // src/run/trace-waterfall.tsx
18
17
  /** Agent and model spans show their lab/host mark; tool spans show their provider's mark. Unknown keeps the kind glyph. */
@@ -24,7 +23,7 @@ function traceSpanMark(span) {
24
23
  size: "sm"
25
24
  }) : void 0;
26
25
  }
27
- const provider = span.icon ?? (span.kind === "tool" ? span.detail : void 0);
26
+ const provider = span.icon;
28
27
  return provider ? /* @__PURE__ */ jsx(ProviderIcon, {
29
28
  id: provider,
30
29
  displayName: provider,
@@ -506,49 +505,6 @@ function useSplit(root) {
506
505
  }
507
506
  };
508
507
  }
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
508
  /** Text held to a few lines with a control to read the rest; the control appears only when lines are hidden. */
553
509
  function Clamped({ text, lines, className }) {
554
510
  const [open, setOpen] = useState(false), [over, setOver] = useState(false), ref = useRef(null);
@@ -637,22 +593,24 @@ function metaOf(span) {
637
593
  /** Facts the header already states are not repeated in Details. */
638
594
  const IN_HEADER = /^(tokens|cost|duration|failure|model)$/i;
639
595
  /** 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 }) {
596
+ function AgentInspector({ name, model, status, span, live, failure, silent, commission, facts, detail, children, scroller, unseen, onJump, onClose, mode, style }) {
641
597
  const brand = model ? modelBrandFor(model) : null, meta = metaOf(span);
642
598
  const details = facts.filter((fact) => !IN_HEADER.test(fact.label) && fact.value !== failure);
643
- const back = useRef(null);
599
+ const sheetRef = useRef(null);
644
600
  const sheet = mode === "sheet";
645
601
  useEffect(() => {
646
602
  if (!sheet) return;
647
603
  const opener = document.activeElement, overflow = document.body.style.overflow;
648
604
  document.body.style.overflow = "hidden";
649
- back.current?.focus();
605
+ sheetRef.current?.focus();
650
606
  return () => {
651
607
  document.body.style.overflow = overflow;
652
608
  if (opener?.isConnected) opener.focus();
653
609
  };
654
610
  }, [sheet]);
655
611
  return /* @__PURE__ */ jsxs("aside", {
612
+ ref: sheetRef,
613
+ tabIndex: sheet ? -1 : void 0,
656
614
  role: sheet ? "dialog" : void 0,
657
615
  "aria-modal": sheet || void 0,
658
616
  "aria-label": `${name} conversation`,
@@ -663,24 +621,24 @@ function AgentInspector({ name, model, status, span, live, failure, commission,
663
621
  onClose();
664
622
  }
665
623
  },
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"),
624
+ className: cn$1("relative flex min-h-0 flex-col overflow-hidden bg-card text-foreground outline-none", 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
625
  children: [
668
626
  /* @__PURE__ */ jsxs("header", {
669
627
  className: "flex shrink-0 items-start gap-2 border-b border-border px-3 py-2.5",
670
628
  children: [
671
- sheet && /* @__PURE__ */ jsxs("button", {
672
- ref: back,
673
- type: "button",
629
+ sheet && /* @__PURE__ */ jsxs(Button, {
630
+ variant: "ghost",
631
+ size: "sm",
674
632
  onClick: onClose,
675
633
  "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))]",
634
+ className: "-ml-1.5 shrink-0 gap-1 px-2",
677
635
  children: [/* @__PURE__ */ jsx(ArrowLeft, {
678
636
  "aria-hidden": true,
679
637
  className: "h-4 w-4"
680
638
  }), "Back"]
681
639
  }),
682
640
  /* @__PURE__ */ jsxs("div", {
683
- className: cn$1("min-w-0 flex-1", sheet && "pt-2"),
641
+ className: cn$1("min-w-0 flex-1", sheet && "pt-1.5"),
684
642
  children: [
685
643
  /* @__PURE__ */ jsxs("div", {
686
644
  className: "flex min-w-0 items-start gap-2",
@@ -733,7 +691,7 @@ function AgentInspector({ name, model, status, span, live, failure, commission,
733
691
  }),
734
692
  failure && /* @__PURE__ */ jsx("section", {
735
693
  "aria-label": "Failure",
736
- className: "shrink-0 border-b border-border px-3 py-2 text-sm text-[var(--surface-danger-text)]",
694
+ className: "shrink-0 border-b border-border px-3 py-2.5 text-sm font-medium leading-5 text-[var(--surface-danger-text)]",
737
695
  children: /* @__PURE__ */ jsx(Clamped, {
738
696
  text: failure,
739
697
  lines: 3
@@ -763,9 +721,10 @@ function AgentInspector({ name, model, status, span, live, failure, commission,
763
721
  children: "New output"
764
722
  }),
765
723
  (details.length > 0 || detail) && /* @__PURE__ */ jsxs("details", {
724
+ open: silent || void 0,
766
725
  className: "group max-h-[45%] shrink-0 overflow-y-auto border-t border-border text-sm",
767
726
  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",
727
+ 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 before:hidden before:content-none [&::-webkit-details-marker]:hidden",
769
728
  children: [/* @__PURE__ */ jsx(ChevronRight, {
770
729
  "aria-hidden": true,
771
730
  className: "h-3.5 w-3.5 transition-transform group-open:rotate-90"
@@ -934,35 +893,31 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
934
893
  identity: showConversation ? selected : void 0,
935
894
  content: items
936
895
  });
937
- const root = useRef(null), pane = useRef(null);
938
- const phone = usePhone(), split = useSplit(root), fill = useFillHeight(pane);
896
+ const root = useRef(null);
897
+ const phone = usePhone(), split = useSplit(root);
939
898
  const mode = phone ? "sheet" : docked ? "docked" : "overlay";
940
899
  const status = live ? "running" : selectedNode?.status ?? runStatus(agent?.status ?? "unknown");
941
900
  const failure = facts.find((fact) => fact.label === "Failure")?.value ?? (status === "failed" ? selectedNode?.result ?? void 0 : void 0);
942
901
  const said = (text) => !!text && shown.some((entry) => entry.kind === "message" && entry.role === "user" && entry.content.trim() === text.trim());
943
902
  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."
903
+ const note = !streamUrl ? silent : cursor !== null && origin === void 0 ? "This conversation has no timestamps to replay at the cursor." : items.length > 0 || live ? void 0 : stream.error ? ended ? stream.error : `Reconnecting… ${stream.error}` : ended ? silent : "No messages yet.";
904
+ const conversation = note === void 0 ? /* @__PURE__ */ jsx(AgentTimelineColumn, {
905
+ className: "max-w-[46rem] p-0",
906
+ children: /* @__PURE__ */ jsx(AgentTimeline, {
907
+ items,
908
+ isThinking: live && items.length === 0
957
909
  })
910
+ }) : /* @__PURE__ */ jsx("p", {
911
+ className: "m-0 py-1 text-sm text-muted-foreground",
912
+ "data-stream": stream.error ? "reconnecting" : ended ? "ended" : "empty",
913
+ children: note
958
914
  });
959
915
  const left = /* @__PURE__ */ jsxs(Fragment, { children: [
960
916
  /* @__PURE__ */ jsx("div", {
961
- ref: pane,
962
917
  className: cn$1("flex min-h-[320px] min-w-0 flex-1 flex-col", showConversation && mode === "overlay" && "lg:mr-[420px]"),
963
918
  children: /* @__PURE__ */ jsx(TraceWaterfall, {
964
919
  ...waterfall,
965
- maxHeight: fill ?? waterfall.maxHeight,
920
+ maxHeight: waterfall.maxHeight ?? "fill",
966
921
  trace,
967
922
  markers: events,
968
923
  selectedId: agentId,
@@ -975,17 +930,9 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
975
930
  setCursor(time);
976
931
  onTimeChange?.(time);
977
932
  },
978
- latestEvents: /* @__PURE__ */ jsx(LatestEvents, {
979
- agents: graph.nodes,
980
- now: graphNow,
981
- live: cursor === null,
982
- selectedId: agentId,
983
- onSelect: select
984
- }),
985
933
  graph: /* @__PURE__ */ jsxs("section", {
986
- "data-trace-graph": "",
987
- className: "relative flex min-h-[320px] flex-col",
988
- style: { height: fill ?? "max(60vh, 480px)" },
934
+ className: "relative flex min-h-[320px] flex-1 flex-col",
935
+ style: typeof waterfall.maxHeight === "number" ? { height: waterfall.maxHeight } : void 0,
989
936
  "aria-label": "Agent graph",
990
937
  children: [/* @__PURE__ */ jsx(SegmentedControl, {
991
938
  "aria-label": "Graph layout",
@@ -999,7 +946,7 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
999
946
  value: "hub",
1000
947
  label: "Hub"
1001
948
  }]
1002
- }), /* @__PURE__ */ jsx(AgentGraph$1, {
949
+ }), /* @__PURE__ */ jsx(AgentGraph, {
1003
950
  ...graphOptions,
1004
951
  layout: graphLayout,
1005
952
  renderEventIcon: (event) => toolGlyph(event.label),
@@ -1051,6 +998,7 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
1051
998
  span: agent,
1052
999
  live,
1053
1000
  failure,
1001
+ silent: note !== void 0 && note === silent,
1054
1002
  commission: said(commission?.text) ? { by: commission?.by } : commission,
1055
1003
  facts,
1056
1004
  detail,
@@ -1064,4 +1012,4 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
1064
1012
  })] })]
1065
1013
  });
1066
1014
  }
1067
- export { useEventStream as a, useAgentStream as i, LatestEvents as n, TraceWaterfall as o, foldAgentRows as r, traceSpanMark as s, TraceWorkspace as t };
1015
+ export { TraceWaterfall as a, useEventStream as i, foldAgentRows as n, traceSpanMark as o, useAgentStream as r, TraceWorkspace as t };
@@ -3,6 +3,7 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from "@tangle-network/u
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { Brain, Settings, Timer } from "lucide-react";
5
5
  import { getToolCategory, getToolDisplayMetadata } from "@tangle-network/ui/utils";
6
+ import { toolLabel } from "@tangle-network/charts/react/agent-graph";
6
7
  import { formatCompact, formatCompact as fmt, formatCost as formatCost$1, formatSpanDuration as formatSpanDuration$1, formatSpanDuration as interval, formatUtcTime as utcTime, reportedCount, sumReportedCounts } from "@tangle-network/charts";
7
8
  import { AgentTimelineStep, agentTimelineStepKind } from "@tangle-network/ui/chat/agent-timeline";
8
9
  import { SpeakerHeader } from "@tangle-network/ui/chat/speaker";
@@ -853,7 +854,7 @@ function toolItem(id, call, output, state, durationMs, live, cost) {
853
854
  }
854
855
  };
855
856
  const known = KIT_TOOLS.has(name.toLowerCase()), meta = getToolDisplayMetadata(part);
856
- const detail = argsLabel(call.input) ?? (meta.description || (known ? void 0 : preview(call)));
857
+ const detail = argsLabel(call.input) ?? (known && meta.description || void 0);
857
858
  return {
858
859
  id,
859
860
  kind: "tool",
@@ -861,7 +862,7 @@ function toolItem(id, call, output, state, durationMs, live, cost) {
861
862
  call: {
862
863
  id,
863
864
  type: TYPES[name.toLowerCase()] ?? "unknown",
864
- label: known ? meta.title : name.replaceAll("_", " "),
865
+ label: known ? meta.title : toolLabel(name),
865
866
  status: status === "error" ? "error" : status === "running" ? "running" : "success",
866
867
  ...detail ? { detail } : {},
867
868
  ...duration !== void 0 ? { duration } : {}
@@ -907,7 +908,7 @@ function timelineItems(entries, streaming, opened) {
907
908
  key: events[0].id,
908
909
  events
909
910
  }));
910
- return rows.map((row, index) => {
911
+ return foldTools(rows.map((row, index) => {
911
912
  const entry = row.kind === "event" ? row.event : row.events[0];
912
913
  if (row.kind === "idle") return idleItem(entry.id, row.events.map((event) => ({
913
914
  id: event.id,
@@ -923,7 +924,10 @@ function timelineItems(entries, streaming, opened) {
923
924
  role: entry.role,
924
925
  content: short ? `${body.slice(0, 280).trimEnd()}…` : body,
925
926
  isStreaming: streaming && index === rows.length - 1,
926
- ...entry.role === "user" && Number.isFinite(entry.at) ? { timestamp: new Date(entry.at) } : {}
927
+ ...entry.role === "user" ? {
928
+ ...opened ? {} : { collapsedLines: 6 },
929
+ ...Number.isFinite(entry.at) ? { timestamp: new Date(entry.at) } : {}
930
+ } : {}
927
931
  };
928
932
  }
929
933
  if (entry.kind === "tool") return toolItem(entry.id, {
@@ -947,7 +951,7 @@ function timelineItems(entries, streaming, opened) {
947
951
  })
948
952
  };
949
953
  return {
950
- id: entry.id,
954
+ id: `reasoning:${entry.id}`,
951
955
  kind: "custom",
952
956
  content: /* @__PURE__ */ jsx(InlineThinkingItem, {
953
957
  part: {
@@ -958,7 +962,38 @@ function timelineItems(entries, streaming, opened) {
958
962
  openWhileActive: false
959
963
  })
960
964
  };
961
- });
965
+ }), streaming);
966
+ }
967
+ /** Back-to-back tool calls (and the reasoning between them) fold into one row that names them, "Ran 6 tools · Read ×3,
968
+ * knowledge record"; their arguments and results open with it, so the agent's own words stay the conversation. A live
969
+ * agent's newest call stays its own row so the reader sees what is running now. */
970
+ function foldTools(items, streaming) {
971
+ const out = [];
972
+ let run = [];
973
+ const flush = (last) => {
974
+ const open = last && streaming ? run.splice(-1) : [];
975
+ const tools = run.filter((item) => item.kind === "tool");
976
+ if (tools.length > 1) {
977
+ const counts = /* @__PURE__ */ new Map();
978
+ for (const tool of tools) counts.set(tool.call.label, (counts.get(tool.call.label) ?? 0) + 1);
979
+ const names = [...counts].map(([name, n]) => n > 1 ? `${name} ×${n}` : name), more = names.length - 3;
980
+ out.push({
981
+ id: `fold:${run[0].id}`,
982
+ kind: "fold",
983
+ items: run,
984
+ summary: `Ran ${tools.length} tools · ${names.slice(0, 3).join(", ")}${more > 0 ? ` +${more} more` : ""}`
985
+ });
986
+ } else out.push(...run);
987
+ out.push(...open);
988
+ run = [];
989
+ };
990
+ for (const item of items) if (item.kind === "tool" || item.kind === "custom" && item.id.startsWith("reasoning:")) run.push(item);
991
+ else {
992
+ flush(false);
993
+ out.push(item);
994
+ }
995
+ flush(true);
996
+ return out;
962
997
  }
963
998
  const toolNote = (part) => {
964
999
  const note = part.state.metadata?.note, time = part.state.time;