@tangle-network/sandbox-ui 2.0.4 → 2.0.5

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.
@@ -23,6 +23,11 @@ interface AgentGraphNode {
23
23
  turn?: string | null;
24
24
  costUsd?: number | null;
25
25
  tokens?: number | null;
26
+ /** Recorded metrics with units; retained in pointer and keyboard detail cards. */
27
+ facts?: readonly {
28
+ label: string;
29
+ value: string;
30
+ }[];
26
31
  /** Live cards: what it is doing now, from its real state; the card carries it as `data-activity` for hosts to style. */
27
32
  activity?: AgentGraphActivity | null;
28
33
  /** Live cards: its latest tool calls or events, oldest first; the card shows the last three. */
@@ -1258,7 +1258,7 @@ function retryImport(factory, retries = 2, delayMs = 350) {
1258
1258
  });
1259
1259
  }
1260
1260
  function lazyWorkflowGraph() {
1261
- return lazy(() => retryImport(() => import("./WorkflowGraph-CQyy5SBb.js").then((m) => ({ default: m.WorkflowGraph }))));
1261
+ return lazy(() => retryImport(() => import("./WorkflowGraph-DkdcJ8wb.js").then((m) => ({ default: m.WorkflowGraph }))));
1262
1262
  }
1263
1263
  var GraphErrorBoundary = class extends Component {
1264
1264
  state = { hasError: false };
@@ -1318,7 +1318,7 @@ function WorkflowGraph(props) {
1318
1318
  * taking the pane down.
1319
1319
  */
1320
1320
  function lazyCanvas() {
1321
- return lazy(() => retryImport(() => import("./AgentGraphCanvas-BQwSxnaH.js").then((m) => ({ default: m.AgentGraphCanvas }))));
1321
+ return lazy(() => retryImport(() => import("./AgentGraphCanvas-DYAMCkhv.js").then((m) => ({ default: m.AgentGraphCanvas }))));
1322
1322
  }
1323
1323
  /** The agents as a list, for when the canvas cannot load. */
1324
1324
  function AgentListFallback({ nodes, selectedId, onSelect, className, onRetry }) {
@@ -1,11 +1,12 @@
1
1
  import { t as MOTION_CONTROL } from "./motion-Bn7ESHrj.js";
2
- import { d as quietActivity, f as syncAmbient, g as useRunNow, h as useMotionAllowed, i as fmtCost, m as useAttention, o as fmtEventTime, p as tokenMilliseconds, s as fmtTokenTotal, u as ambientStyle } from "./AgentGraph-DA5lyE06.js";
3
- import { t as LatestEvents } from "./latest-events-C_G-ekNg.js";
4
- import { P as shortModel, m as edgeColor, o as RESTING_BORDER, r as NodeMark, s as RESTING_SHADOW, t as KIND_ACCENT } from "./node-ui-DJo7yr4o.js";
2
+ import { d as quietActivity, f as syncAmbient, g as useRunNow, h as useMotionAllowed, i as fmtCost, m as useAttention, o as fmtEventTime, p as tokenMilliseconds, s as fmtTokenTotal, u as ambientStyle } from "./AgentGraph-YuBa5O8m.js";
3
+ import { t as LatestEvents } from "./latest-events-CWvZr8Ut.js";
4
+ import { P as shortModel, m as edgeColor, o as RESTING_BORDER, r as NodeMark, s as RESTING_SHADOW, t as KIND_ACCENT } from "./node-ui-Dst2R2M5.js";
5
5
  import { i as loadElk, r as useColorMode, t as SELECTION_OUTLINE } from "./canvas-theme-aqWjGbfu.js";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
8
- import { Wrench } from "lucide-react";
7
+ import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
8
+ import { createPortal } from "react-dom";
9
+ import { ChevronDown, Wrench } from "lucide-react";
9
10
  import { RunStatusIcon, statusMeta } from "@tangle-network/ui/run";
10
11
  import { Background, BaseEdge, Controls, EdgeLabelRenderer, Handle, MarkerType, MiniMap, Position, ReactFlow, useInternalNode } from "@xyflow/react";
11
12
  import "@xyflow/react/dist/style.css";
@@ -22,7 +23,6 @@ const LIVE_AGENT_CARD = {
22
23
  };
23
24
  const GRID_GAP = 10;
24
25
  const FRAME_PAD = 12;
25
- const MAX_GRID_COLUMNS = 6;
26
26
  const LAYER_GAP = 56;
27
27
  const SIBLING_GAP = 24;
28
28
  function buildAgentTopology(nodes, edges) {
@@ -71,7 +71,11 @@ function buildAgentTopology(nodes, edges) {
71
71
  groupOf
72
72
  };
73
73
  }
74
- function gridSize(members, columns, card) {
74
+ function gridSize(members, columns, card, collapsed = false) {
75
+ if (collapsed) return {
76
+ width: card.width + 24,
77
+ height: 80
78
+ };
75
79
  const cols = Math.max(1, Math.min(columns, members));
76
80
  const rows = Math.ceil(members / cols);
77
81
  return {
@@ -80,7 +84,7 @@ function gridSize(members, columns, card) {
80
84
  };
81
85
  }
82
86
  /** The boxes the layouter places: each ungrouped agent, and each grid as one block. */
83
- function layoutUnits(topology, columns, card) {
87
+ function layoutUnits(topology, columns, card, collapsed) {
84
88
  const units = [];
85
89
  const ownerGroup = new Map(topology.groups.map((group) => [group.parentId, group.id]));
86
90
  for (const id of topology.agentIds) if (!topology.groupOf.has(id) && !ownerGroup.has(id)) units.push({
@@ -90,7 +94,7 @@ function layoutUnits(topology, columns, card) {
90
94
  });
91
95
  for (const g of topology.groups) units.push({
92
96
  id: g.id,
93
- ...gridSize(g.memberIds.length, columns, card)
97
+ ...gridSize(g.memberIds.length, columns, card, collapsed.has(g.id))
94
98
  });
95
99
  const unitOf = (id) => topology.groupOf.get(id) ?? ownerGroup.get(id) ?? id;
96
100
  const seen = /* @__PURE__ */ new Set();
@@ -191,8 +195,8 @@ function placeByDepth(units, links, direction) {
191
195
  return at;
192
196
  }
193
197
  /** Lay the run out with every grid `columns` wide. */
194
- async function layoutAgents(topology, direction, columns, card = AGENT_CARD) {
195
- const { units, links } = layoutUnits(topology, columns, card);
198
+ async function layoutAgents(topology, direction, columns, card = AGENT_CARD, collapsed = /* @__PURE__ */ new Set()) {
199
+ const { units, links } = layoutUnits(topology, columns, card, collapsed);
196
200
  const placed = await placeWithElk(units, links, direction) ?? placeByDepth(units, links, direction);
197
201
  const positions = /* @__PURE__ */ new Map();
198
202
  const frames = [];
@@ -213,16 +217,19 @@ async function layoutAgents(topology, direction, columns, card = AGENT_CARD) {
213
217
  y: 0
214
218
  };
215
219
  positions.delete(g.id);
216
- const size = gridSize(g.memberIds.length, columns, card);
220
+ const closed = collapsed.has(g.id);
221
+ const size = gridSize(g.memberIds.length, columns, card, closed);
217
222
  frames.push({
218
223
  group: g,
219
224
  ...origin,
220
- ...size
225
+ ...size,
226
+ collapsed: closed
221
227
  });
222
228
  positions.set(g.parentId, {
223
229
  x: origin.x + FRAME_PAD,
224
230
  y: origin.y + 8
225
231
  });
232
+ if (closed) continue;
226
233
  const cols = Math.max(1, Math.min(columns, g.memberIds.length));
227
234
  g.memberIds.forEach((id, i) => {
228
235
  positions.set(id, {
@@ -239,34 +246,6 @@ async function layoutAgents(topology, direction, columns, card = AGENT_CARD) {
239
246
  height
240
247
  };
241
248
  }
242
- /** The grid widths worth trying for this run: one per column count a grid can use. */
243
- function candidateColumns(topology) {
244
- const largest = Math.max(0, ...topology.groups.map((g) => g.memberIds.length));
245
- const top = Math.min(MAX_GRID_COLUMNS, largest);
246
- return top <= 1 ? [1] : Array.from({ length: top }, (_, i) => i + 1);
247
- }
248
- /**
249
- * The layout that shows the run largest in this canvas: the highest zoom at
250
- * which it fits whole, capped at the cards' designed size. Layouts that all
251
- * reach that cap tie, and the tie goes to the one whose shape is closest to the
252
- * canvas's, so a wide canvas gets a wide grid and a phone a tall one.
253
- */
254
- function pickAgentLayout(layouts, canvas, padding) {
255
- let best = null;
256
- let bestZoom = -1;
257
- let bestShape = Number.POSITIVE_INFINITY;
258
- const aspect = canvas.width / Math.max(1, canvas.height);
259
- for (const l of layouts) {
260
- const zoom = Math.min(1, (canvas.width - 2 * padding) / Math.max(1, l.width), (canvas.height - 2 * padding) / Math.max(1, l.height));
261
- const shape = Math.abs(Math.log(l.width / Math.max(1, l.height) / aspect));
262
- if (zoom > bestZoom + .02 || Math.abs(zoom - bestZoom) <= .02 && shape < bestShape) {
263
- best = l;
264
- bestZoom = zoom;
265
- bestShape = shape;
266
- }
267
- }
268
- return best;
269
- }
270
249
  // src/workflows/agent-graph-parts.tsx
271
250
  /**
272
251
  * What `AgentGraph` draws: an agent's card, the frame around a grid of leaf
@@ -423,9 +402,20 @@ function AgentFrameBox({ data }) {
423
402
  background: "color-mix(in srgb, hsl(var(--muted-foreground)) 7%, transparent)",
424
403
  borderColor: "hsl(var(--foreground) / 0.08)"
425
404
  },
426
- children: [/* @__PURE__ */ jsx(Handles, { direction }), /* @__PURE__ */ jsxs("span", {
427
- className: "sr-only",
428
- children: [frame.group.memberIds.length, " workers"]
405
+ children: [/* @__PURE__ */ jsx(Handles, { direction }), /* @__PURE__ */ jsxs("button", {
406
+ type: "button",
407
+ className: "nodrag nopan absolute right-3 top-2 flex h-16 w-8 flex-col items-center justify-center gap-1 rounded text-sm text-muted-foreground hover:bg-muted focus-visible:outline",
408
+ "aria-label": `${frame.collapsed ? "Expand" : "Collapse"} ${data.label} team`,
409
+ "aria-expanded": !frame.collapsed,
410
+ title: `${data.label} · ${frame.group.memberIds.length} agents`,
411
+ onClick: (event) => {
412
+ event.stopPropagation();
413
+ data.onToggle();
414
+ },
415
+ children: [/* @__PURE__ */ jsx(ChevronDown, {
416
+ size: 16,
417
+ className: frame.collapsed ? "-rotate-90" : void 0
418
+ }), /* @__PURE__ */ jsx("span", { children: frame.group.memberIds.length })]
429
419
  })]
430
420
  });
431
421
  }
@@ -689,7 +679,7 @@ const NODE_TYPES = {
689
679
  const EDGE_TYPES = { agentMessage: AgentMessageEdgeView };
690
680
  /** The fit's margin, in screen pixels. */
691
681
  const FIT_PADDING = 16;
692
- const MINIMAP_MIN_NODES = 40;
682
+ const PHONE_PAGE = 24;
693
683
  /** The canvas's size, once it has one. */
694
684
  function useCanvasSize() {
695
685
  const [size, setSize] = useState(null);
@@ -718,7 +708,7 @@ function revealSelection(inst, wrapper, id) {
718
708
  const width = node?.measured.width ?? node?.width, height = node?.measured.height ?? node?.height;
719
709
  if (!node || !box || !width || !height) return;
720
710
  const current = inst.getViewport(), position = node.internals.positionAbsolute;
721
- const zoom = Math.max(.02, Math.min(current.zoom, (box.width - 32) / width, (box.height - 32) / height));
711
+ const zoom = Math.max(.02, Math.min(1, (box.width - 32) / width, (box.height - 32) / height));
722
712
  const x = Math.max(FIT_PADDING - position.x * zoom, Math.min(box.width - FIT_PADDING - (position.x + width) * zoom, current.x));
723
713
  const y = Math.max(FIT_PADDING - position.y * zoom, Math.min(box.height - FIT_PADDING - (position.y + height) * zoom, current.y));
724
714
  if (x !== current.x || y !== current.y || zoom !== current.zoom) inst.setViewport({
@@ -731,7 +721,23 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
731
721
  const card = live ? LIVE_AGENT_CARD : AGENT_CARD;
732
722
  const clock = useRunNow(now);
733
723
  const animateMotion = useMotionAllowed();
724
+ const detailsId = useId();
725
+ const hoverTimer = useRef(void 0);
726
+ const hoverTarget = useRef(null);
727
+ const returningFocus = useRef(false);
728
+ const keepDetails = () => clearTimeout(hoverTimer.current);
729
+ const hideDetails = () => {
730
+ keepDetails();
731
+ setHovered(null);
732
+ };
733
+ const leaveDetails = () => {
734
+ keepDetails();
735
+ hoverTimer.current = setTimeout(() => setHovered(null), tokenMilliseconds("--duration-fast", wrapperRef.current));
736
+ };
737
+ useEffect(() => () => clearTimeout(hoverTimer.current), []);
734
738
  const [hovered, setHovered] = useState(null);
739
+ const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
740
+ const [phonePage, setPhonePage] = useState(0);
735
741
  const wrapperRef = useRef(null);
736
742
  const [canvas, measureCanvas] = useCanvasSize();
737
743
  const colorMode = useColorMode(wrapperRef, nodes.length > 0);
@@ -740,37 +746,44 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
740
746
  const topologyRef = useRef(nextTopology);
741
747
  if (topologyRef.current.key !== nextTopology.key) topologyRef.current = nextTopology;
742
748
  const topology = topologyRef.current;
743
- const [layouts, setLayouts] = useState(null);
744
- const layoutKey = `${direction}:${live ? "live" : "cards"}:${topology.key}`;
749
+ const columnKey = `${direction}:${live}:${topology.key}`;
750
+ const columnRef = useRef(null);
751
+ if (canvas && !narrow && columnRef.current?.key !== columnKey) columnRef.current = {
752
+ key: columnKey,
753
+ columns: Math.max(1, Math.min(6, Math.floor((canvas.width - 32) / (card.width + 10))))
754
+ };
755
+ const columns = columnRef.current?.columns ?? 1;
756
+ const layoutKey = JSON.stringify([
757
+ direction,
758
+ live,
759
+ topology.key,
760
+ columns,
761
+ [...collapsed]
762
+ ]);
763
+ const [placed, setPlaced] = useState(null);
745
764
  useEffect(() => {
746
765
  if (narrow) return;
747
- let live = true;
748
- Promise.all(candidateColumns(topology).map((c) => layoutAgents(topology, direction, c, card))).then((all) => {
749
- if (live) setLayouts({
766
+ let active = true;
767
+ layoutAgents(topology, direction, columns, card, collapsed).then((layout) => {
768
+ if (active) setPlaced({
750
769
  key: layoutKey,
751
- all
770
+ layout
752
771
  });
753
772
  });
754
773
  return () => {
755
- live = false;
774
+ active = false;
756
775
  };
757
- }, [
758
- topology,
759
- direction,
760
- layoutKey,
761
- card,
762
- narrow
763
- ]);
764
- const priorLayout = useRef(null);
765
- const layout = useMemo(() => {
766
- if (!layouts || layouts.key !== layoutKey || !canvas) return null;
767
- return priorLayout.current = selectedId && priorLayout.current && layouts.all.includes(priorLayout.current) ? priorLayout.current : pickAgentLayout(layouts.all, canvas, FIT_PADDING);
768
- }, [
769
- layouts,
770
- layoutKey,
771
- canvas,
772
- selectedId
773
- ]);
776
+ }, [layoutKey, narrow]);
777
+ const layout = placed?.key === layoutKey ? placed.layout : null;
778
+ useEffect(() => {
779
+ const group = selectedId && topology.groupOf.get(selectedId);
780
+ if (group) setCollapsed((current) => {
781
+ if (!current.has(group)) return current;
782
+ const next = new Set(current);
783
+ next.delete(group);
784
+ return next;
785
+ });
786
+ }, [selectedId, topology.key]);
774
787
  const byId = useMemo(() => new Map(nodes.map((n) => [n.id, n])), [nodes]);
775
788
  const rfRef = useRef(null);
776
789
  const flowNodes = useMemo(() => {
@@ -791,7 +804,16 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
791
804
  draggable: false,
792
805
  data: {
793
806
  frame,
794
- direction
807
+ direction,
808
+ label: byId.get(frame.group.parentId)?.label ?? "Agent",
809
+ onToggle: () => {
810
+ if (!frame.collapsed && selectedId && frame.group.memberIds.includes(selectedId)) onSelect?.(frame.group.parentId);
811
+ setCollapsed((current) => {
812
+ const next = new Set(current);
813
+ next.has(frame.group.id) ? next.delete(frame.group.id) : next.add(frame.group.id);
814
+ return next;
815
+ });
816
+ }
795
817
  }
796
818
  }));
797
819
  const cards = [];
@@ -799,9 +821,8 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
799
821
  const agent = byId.get(id);
800
822
  const position = layout.positions.get(id);
801
823
  if (!agent || !position) continue;
802
- const selected = selectedId === id;
803
- const owned = layout.frames.find((frame) => frame.group.parentId === id);
804
- const width = owned ? owned.width - 24 : card.width, height = owned ? 64 : card.height;
824
+ const selected = selectedId === id, owned = layout.frames.find((frame) => frame.group.parentId === id);
825
+ const width = owned ? owned.width - 64 : card.width, height = owned ? 64 : card.height;
805
826
  const measured = rfRef.current?.getInternalNode(id)?.measured;
806
827
  cards.push({
807
828
  id,
@@ -814,7 +835,10 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
814
835
  draggable: false,
815
836
  ariaRole: "button",
816
837
  ariaLabel: agentSummary(agent),
817
- domAttributes: { "aria-pressed": selected },
838
+ domAttributes: {
839
+ "aria-pressed": selected,
840
+ "aria-describedby": hovered?.id === id ? detailsId : void 0
841
+ },
818
842
  data: {
819
843
  agent,
820
844
  selected,
@@ -836,7 +860,8 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
836
860
  card,
837
861
  clock,
838
862
  onSelect,
839
- renderEventIcon
863
+ renderEventIcon,
864
+ hovered
840
865
  ]);
841
866
  const flowEdges = useMemo(() => {
842
867
  if (!layout) return [];
@@ -862,11 +887,11 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
862
887
  }
863
888
  });
864
889
  }
865
- const ids = new Set(topology.agentIds);
890
+ const ids = new Set(layout.positions.keys());
866
891
  const messages = /* @__PURE__ */ new Map();
867
892
  for (const e of edges) {
868
893
  if (e.kind === "spawn" || e.from === e.to || !ids.has(e.from) || !ids.has(e.to)) continue;
869
- if (![selectedId, hovered].some((id) => id === e.from || id === e.to)) continue;
894
+ if (![selectedId, hovered?.id].some((id) => id === e.from || id === e.to)) continue;
870
895
  const id = `${e.kind}:${e.from}:${e.to}`;
871
896
  const prior = messages.get(id);
872
897
  const count = (prior?.data?.count ?? 0) + Math.max(1, e.count ?? 1);
@@ -904,7 +929,6 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
904
929
  ]);
905
930
  const [ready, setReady] = useState(false);
906
931
  const [shown, setShown] = useState(false);
907
- const readerMoved = useRef(false);
908
932
  const selection = useRef(selectedId);
909
933
  selection.current = selectedId;
910
934
  const framed = useRef(null);
@@ -922,24 +946,22 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
922
946
  const inst = rfRef.current;
923
947
  if (!ready || !inst || !layoutIdentity || !canvas) return;
924
948
  const relayout = framed.current !== layoutIdentity;
925
- const preserveView = !relayout && readerMoved.current;
949
+ const preserveView = !relayout;
926
950
  if (preserveView && !selection.current) return;
927
951
  const animate = framed.current !== null && relayout && animateMotion;
928
952
  framed.current = layoutIdentity;
929
- readerMoved.current = preserveView;
930
953
  const frame = requestAnimationFrame(() => {
931
954
  if (preserveView) revealSelection(inst, wrapperRef.current, selection.current);
932
- else if (live) inst.setViewport({
933
- x: FIT_PADDING,
934
- y: FIT_PADDING / 2,
935
- zoom: 1
936
- }, { duration: animate ? tokenMilliseconds("--duration-base", wrapperRef.current) : 0 });
937
- else inst.fitView({
938
- padding: `${FIT_PADDING}px`,
939
- maxZoom: 1,
940
- minZoom,
941
- duration: animate ? tokenMilliseconds("--duration-base", wrapperRef.current) : 0
942
- });
955
+ else {
956
+ const id = selection.current ?? topology.agentIds[0], position = layout?.positions.get(id), width = inst.getInternalNode(id)?.width ?? card.width;
957
+ const x = layout.width + 32 <= canvas.width ? (canvas.width - layout.width) / 2 : position ? Math.max(FIT_PADDING - position.x, canvas.width / 2 - position.x - width / 2) : FIT_PADDING;
958
+ const y = layout.height + 32 <= canvas.height ? FIT_PADDING : FIT_PADDING - (position?.y ?? 0);
959
+ inst.setViewport({
960
+ x,
961
+ y,
962
+ zoom: 1
963
+ }, { duration: animate ? tokenMilliseconds("--duration-base", wrapperRef.current) : 0 });
964
+ }
943
965
  setShown(true);
944
966
  });
945
967
  return () => cancelAnimationFrame(frame);
@@ -949,7 +971,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
949
971
  canvas,
950
972
  minZoom,
951
973
  animateMotion,
952
- live
974
+ selectedId
953
975
  ]);
954
976
  const select = useCallback((id) => {
955
977
  if (byId.has(id)) {
@@ -987,11 +1009,11 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
987
1009
  event.preventDefault();
988
1010
  event.stopPropagation();
989
1011
  const position = layout?.positions.get(target), size = rfRef.current?.getInternalNode(target)?.measured;
990
- wrapperRef.current?.querySelector(`.react-flow__node[data-id="${CSS.escape(target)}"]`)?.focus();
991
- if (position) rfRef.current?.setCenter(position.x + (size?.width ?? card.width) / 2, position.y + (size?.height ?? card.height) / 2, {
992
- zoom: rfRef.current.getZoom(),
993
- duration: animateMotion ? tokenMilliseconds("--duration-base", wrapperRef.current) : 0
994
- });
1012
+ if (position) rfRef.current?.setCenter(position.x + (size?.width ?? card.width) / 2, position.y + (size?.height ?? card.height) / 2, { zoom: 1 });
1013
+ const focus = () => wrapperRef.current?.querySelector(`.react-flow__node[data-id="${CSS.escape(target)}"]`);
1014
+ const visible = focus();
1015
+ if (visible) visible.focus();
1016
+ else requestAnimationFrame(() => requestAnimationFrame(() => focus()?.focus()));
995
1017
  }, [
996
1018
  byId,
997
1019
  select,
@@ -1001,9 +1023,6 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
1001
1023
  animateMotion
1002
1024
  ]);
1003
1025
  const activityRank = (agent) => agent.status === "needs-you" ? 0 : agent.status === "failed" ? 1 : quietActivity(agent, clock) ? 2 : agent.status === "waiting" ? 3 : agent.status === "running" ? 4 : 5;
1004
- const readingOrder = useRef([]);
1005
- const known = new Set(readingOrder.current);
1006
- readingOrder.current = [...readingOrder.current.filter((id) => byId.has(id)), ...nodes.filter((agent) => !known.has(agent.id)).sort((a, b) => activityRank(a) - activityRank(b)).map((agent) => agent.id)];
1007
1026
  const parentOf = new Map(edges.filter((e) => e.kind === "spawn").map((e) => [e.to, e.from]));
1008
1027
  const depthOf = (id) => {
1009
1028
  const seen = /* @__PURE__ */ new Set([id]);
@@ -1015,6 +1034,23 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
1015
1034
  }
1016
1035
  return depth;
1017
1036
  };
1037
+ const readingOrder = useRef([]), known = new Set(readingOrder.current);
1038
+ readingOrder.current = [...readingOrder.current.filter((id) => byId.has(id)), ...nodes.filter((agent) => !known.has(agent.id)).sort((a, b) => activityRank(a) - activityRank(b)).map((agent) => agent.id)];
1039
+ const phoneNodes = readingOrder.current.map((id) => byId.get(id));
1040
+ useEffect(() => {
1041
+ const at = phoneNodes.findIndex((node) => node.id === selectedId);
1042
+ if (at >= 0) setPhonePage(Math.floor(at / PHONE_PAGE));
1043
+ }, [selectedId]);
1044
+ useEffect(() => {
1045
+ if (!narrow || !selectedId) return;
1046
+ const frame = requestAnimationFrame(() => wrapperRef.current?.querySelector(`[data-agent-id="${CSS.escape(selectedId)}"]`)?.scrollIntoView({ block: "nearest" }));
1047
+ return () => cancelAnimationFrame(frame);
1048
+ }, [
1049
+ selectedId,
1050
+ phonePage,
1051
+ narrow
1052
+ ]);
1053
+ const first = Math.min(phonePage * PHONE_PAGE, Math.max(0, Math.ceil(nodes.length / PHONE_PAGE) - 1) * PHONE_PAGE);
1018
1054
  const counts = [
1019
1055
  "needs-you",
1020
1056
  "failed",
@@ -1074,7 +1110,35 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
1074
1110
  },
1075
1111
  className: `agent-graph ag-live relative flex flex-col ${className ?? ""}`,
1076
1112
  tabIndex: -1,
1077
- onKeyDownCapture: handleKeyDown,
1113
+ onKeyDownCapture: (event) => {
1114
+ if (event.target instanceof HTMLElement && event.target.closest("[data-agent-metrics]")) return;
1115
+ if (event.key === "Tab" && !event.shiftKey && hovered) {
1116
+ event.preventDefault();
1117
+ document.getElementById(detailsId)?.focus();
1118
+ return;
1119
+ }
1120
+ if (event.key === "Escape") hideDetails();
1121
+ handleKeyDown(event);
1122
+ },
1123
+ onFocusCapture: (event) => {
1124
+ if (!(event.target instanceof HTMLElement) || returningFocus.current) return;
1125
+ keepDetails();
1126
+ if (event.target.closest("[data-agent-metrics]")) return;
1127
+ if (!event.target.matches(":focus-visible")) {
1128
+ hideDetails();
1129
+ return;
1130
+ }
1131
+ const node = event.target.closest(".react-flow__node, [data-agent-id]");
1132
+ const id = node?.dataset.id ?? node?.dataset.agentId;
1133
+ hoverTarget.current = node ?? null;
1134
+ setHovered(id && byId.has(id) ? {
1135
+ id,
1136
+ rect: node.getBoundingClientRect()
1137
+ } : null);
1138
+ },
1139
+ onBlurCapture: (event) => {
1140
+ if (!event.currentTarget.contains(event.relatedTarget) && event.relatedTarget?.id !== detailsId) hideDetails();
1141
+ },
1078
1142
  "data-layout": narrow ? "list" : layout && shown ? "ready" : "pending",
1079
1143
  "data-columns": layout?.columns,
1080
1144
  children: [
@@ -1086,10 +1150,10 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
1086
1150
  selectedId,
1087
1151
  onSelect: onSelect ? select : void 0
1088
1152
  }),
1089
- narrow ? /* @__PURE__ */ jsx("ul", {
1153
+ narrow ? /* @__PURE__ */ jsxs("ul", {
1090
1154
  className: "ag-graph-viewport ag-phone-list overflow-auto",
1091
1155
  "aria-label": "Agents by attention",
1092
- children: readingOrder.current.map((id) => byId.get(id)).map((agent) => /* @__PURE__ */ jsx("li", {
1156
+ children: [phoneNodes.slice(first, first + PHONE_PAGE).map((agent) => /* @__PURE__ */ jsx("li", {
1093
1157
  style: { ["--depth"]: depthOf(agent.id) },
1094
1158
  children: /* @__PURE__ */ jsx("div", {
1095
1159
  role: "button",
@@ -1121,7 +1185,39 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
1121
1185
  onOpen: onSelect ? () => select(agent.id) : void 0
1122
1186
  })
1123
1187
  })
1124
- }, agent.id))
1188
+ }, agent.id)), nodes.length > PHONE_PAGE && /* @__PURE__ */ jsxs("li", {
1189
+ className: "flex items-center justify-between gap-2 border-t border-border pt-3 text-sm",
1190
+ "aria-label": "Agent pages",
1191
+ children: [
1192
+ /* @__PURE__ */ jsx("button", {
1193
+ type: "button",
1194
+ className: "rounded px-2 py-1 hover:bg-muted disabled:opacity-40 focus-visible:outline",
1195
+ disabled: !first,
1196
+ onClick: () => {
1197
+ setPhonePage((page) => page - 1);
1198
+ wrapperRef.current?.querySelector(".ag-phone-list")?.scrollTo({ top: 0 });
1199
+ },
1200
+ children: "Previous"
1201
+ }),
1202
+ /* @__PURE__ */ jsxs("span", { children: [
1203
+ first + 1,
1204
+ "–",
1205
+ Math.min(nodes.length, first + PHONE_PAGE),
1206
+ " of ",
1207
+ nodes.length
1208
+ ] }),
1209
+ /* @__PURE__ */ jsx("button", {
1210
+ type: "button",
1211
+ className: "rounded px-2 py-1 hover:bg-muted disabled:opacity-40 focus-visible:outline",
1212
+ disabled: first + PHONE_PAGE >= nodes.length,
1213
+ onClick: () => {
1214
+ setPhonePage((page) => page + 1);
1215
+ wrapperRef.current?.querySelector(".ag-phone-list")?.scrollTo({ top: 0 });
1216
+ },
1217
+ children: "Next"
1218
+ })
1219
+ ]
1220
+ })]
1125
1221
  }) : /* @__PURE__ */ jsx("div", {
1126
1222
  className: "ag-graph-viewport min-h-0 flex-1",
1127
1223
  style: { opacity: shown ? 1 : 0 },
@@ -1135,13 +1231,18 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
1135
1231
  rfRef.current = inst;
1136
1232
  setReady(true);
1137
1233
  },
1138
- onMoveStart: (event) => {
1139
- if (event) readerMoved.current = true;
1140
- },
1141
1234
  onNodeClick: handleNodeClick,
1142
- onNodeMouseEnter: (_, node) => setHovered(node.id),
1143
- onNodeMouseLeave: () => setHovered(null),
1235
+ onNodeMouseEnter: (event, node) => {
1236
+ keepDetails();
1237
+ hoverTarget.current = event.target.closest(".react-flow__node");
1238
+ if (hoverTarget.current) setHovered({
1239
+ id: node.id,
1240
+ rect: hoverTarget.current.getBoundingClientRect()
1241
+ });
1242
+ },
1243
+ onNodeMouseLeave: leaveDetails,
1144
1244
  proOptions: { hideAttribution: true },
1245
+ onlyRenderVisibleElements: true,
1145
1246
  nodesDraggable: false,
1146
1247
  nodesConnectable: false,
1147
1248
  elementsSelectable: false,
@@ -1163,7 +1264,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
1163
1264
  size: 1,
1164
1265
  color: "hsl(var(--foreground) / 0.06)"
1165
1266
  }),
1166
- nodes.length > MINIMAP_MIN_NODES && /* @__PURE__ */ jsx(MiniMap, {
1267
+ layout && canvas && (layout.width > canvas.width || layout.height > canvas.height) && /* @__PURE__ */ jsx(MiniMap, {
1167
1268
  pannable: true,
1168
1269
  zoomable: true,
1169
1270
  position: "bottom-left",
@@ -1184,8 +1285,119 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
1184
1285
  })
1185
1286
  ]
1186
1287
  })
1288
+ }),
1289
+ hovered && byId.has(hovered.id) && /* @__PURE__ */ jsx(AgentMetrics, {
1290
+ anchor: wrapperRef.current,
1291
+ mode: colorMode,
1292
+ id: detailsId,
1293
+ agent: byId.get(hovered.id),
1294
+ rect: hovered.rect,
1295
+ onEnter: keepDetails,
1296
+ onLeave: leaveDetails,
1297
+ onClose: () => {
1298
+ hideDetails();
1299
+ returningFocus.current = true;
1300
+ hoverTarget.current?.focus({ preventScroll: true });
1301
+ returningFocus.current = false;
1302
+ }
1187
1303
  })
1188
1304
  ]
1189
1305
  });
1190
1306
  }
1307
+ /** Metrics stay at reading size outside the zoomed canvas and its clipping container. */
1308
+ function AgentMetrics({ anchor, mode, id, agent, rect, onEnter, onLeave, onClose }) {
1309
+ const [theme, setTheme] = useState({});
1310
+ useLayoutEffect(() => {
1311
+ if (!anchor) return;
1312
+ const read = () => {
1313
+ const css = getComputedStyle(anchor);
1314
+ setTheme(Object.fromEntries(Array.from(css).filter((name) => name.startsWith("--") && !name.startsWith("--tw-")).map((name) => [name, css.getPropertyValue(name)])));
1315
+ };
1316
+ read();
1317
+ const observer = new MutationObserver(read);
1318
+ for (let el = anchor; el; el = el.parentElement) observer.observe(el, {
1319
+ attributes: true,
1320
+ attributeFilter: [
1321
+ "class",
1322
+ "style",
1323
+ "data-theme",
1324
+ "data-sandbox-theme"
1325
+ ]
1326
+ });
1327
+ return () => observer.disconnect();
1328
+ }, [anchor]);
1329
+ const facts = new Map([
1330
+ {
1331
+ label: "Tokens",
1332
+ value: fmtTokenTotal(agent.tokens) ?? "Not recorded"
1333
+ },
1334
+ {
1335
+ label: "Cost",
1336
+ value: fmtCost(agent.costUsd ?? void 0) ?? "Not recorded"
1337
+ },
1338
+ ...agent.facts ?? []
1339
+ ].map((fact) => [fact.label.toLowerCase(), fact]));
1340
+ const width = Math.min(420, window.innerWidth - 24), height = Math.min(560, window.innerHeight - 24);
1341
+ const left = Math.max(12, Math.min(rect.right + 12 + width <= window.innerWidth ? rect.right + 12 : rect.left - width - 12, window.innerWidth - width - 12));
1342
+ return createPortal(/* @__PURE__ */ jsxs("div", {
1343
+ id,
1344
+ "data-agent-metrics": true,
1345
+ "data-theme": anchor?.closest("[data-theme]")?.getAttribute("data-theme") ?? mode,
1346
+ role: "tooltip",
1347
+ tabIndex: 0,
1348
+ onMouseEnter: onEnter,
1349
+ onMouseLeave: onLeave,
1350
+ onWheel: (event) => event.stopPropagation(),
1351
+ onBlur: onLeave,
1352
+ onKeyDown: (event) => {
1353
+ if (event.key === "Escape") {
1354
+ event.preventDefault();
1355
+ onClose();
1356
+ }
1357
+ },
1358
+ className: `${mode} rounded-xl border border-border bg-card p-4 text-sm text-foreground shadow-xl`,
1359
+ style: {
1360
+ ...theme,
1361
+ colorScheme: mode,
1362
+ position: "fixed",
1363
+ zIndex: 1e3,
1364
+ width,
1365
+ maxHeight: height,
1366
+ overflow: "auto",
1367
+ left,
1368
+ top: Math.max(12, Math.min(rect.top, window.innerHeight - height - 12))
1369
+ },
1370
+ children: [
1371
+ /* @__PURE__ */ jsxs("div", {
1372
+ className: "mb-2 flex items-center justify-between gap-3",
1373
+ children: [/* @__PURE__ */ jsx("strong", { children: agent.label }), /* @__PURE__ */ jsx("span", { children: agentStatus(agent.status).label })]
1374
+ }),
1375
+ /* @__PURE__ */ jsx("p", {
1376
+ className: "mb-3 text-muted-foreground",
1377
+ children: [
1378
+ agent.model,
1379
+ agent.harness,
1380
+ agent.role
1381
+ ].filter(Boolean).join(" · ")
1382
+ }),
1383
+ /* @__PURE__ */ jsx("dl", {
1384
+ className: "grid grid-cols-[minmax(0,1fr)_auto] gap-x-5 gap-y-1.5",
1385
+ children: [...facts.values()].map((fact) => /* @__PURE__ */ jsxs("div", {
1386
+ className: "contents",
1387
+ children: [/* @__PURE__ */ jsx("dt", {
1388
+ className: "min-w-0 break-words text-muted-foreground",
1389
+ children: fact.label
1390
+ }), /* @__PURE__ */ jsx("dd", {
1391
+ className: "m-0 min-w-0 max-w-64 whitespace-normal break-words text-right tabular-nums",
1392
+ children: fact.value
1393
+ })]
1394
+ }, fact.label))
1395
+ }),
1396
+ agent.status === "failed" && /* @__PURE__ */ jsx("p", {
1397
+ className: "mt-3 whitespace-pre-wrap break-words text-[var(--surface-danger-text)]",
1398
+ children: agent.result ?? agent.turn ?? "Failure cause not recorded"
1399
+ })
1400
+ ]
1401
+ }), document.body);
1402
+ }
1191
1403
  export { AgentGraphCanvas };
@@ -1,5 +1,5 @@
1
- import { a as fmtDuration, c as fmtTokens, i as fmtCost, r as clampPreview } from "./AgentGraph-DA5lyE06.js";
2
- import { A as worstSeverity, E as isStructuralKind, O as triggerNodeIndex, P as shortModel, T as buildWorkflowGraph, _ as problemBorder, a as ProblemMessages, b as statusBorder, c as STATUS_COLOR, f as StatusFooter, g as kindAccent, h as emptySlotLabel, i as ProblemMarker, k as wfEdgeId, l as STATUS_LABEL, m as edgeColor, o as RESTING_BORDER, p as StatusPill, r as NodeMark, s as RESTING_SHADOW, v as problemSurface, x as COMPACT_NODE_SIZE, y as problemTitle } from "./node-ui-DJo7yr4o.js";
1
+ import { a as fmtDuration, c as fmtTokens, i as fmtCost, r as clampPreview } from "./AgentGraph-YuBa5O8m.js";
2
+ import { A as worstSeverity, E as isStructuralKind, O as triggerNodeIndex, P as shortModel, T as buildWorkflowGraph, _ as problemBorder, a as ProblemMessages, b as statusBorder, c as STATUS_COLOR, f as StatusFooter, g as kindAccent, h as emptySlotLabel, i as ProblemMarker, k as wfEdgeId, l as STATUS_LABEL, m as edgeColor, o as RESTING_BORDER, p as StatusPill, r as NodeMark, s as RESTING_SHADOW, v as problemSurface, x as COMPACT_NODE_SIZE, y as problemTitle } from "./node-ui-Dst2R2M5.js";
3
3
  import { n as classifyOutput, t as NodeOutputBody } from "./node-output-DlySTwxr.js";
4
4
  import { i as loadElk, n as motionAllowed, r as useColorMode, t as SELECTION_OUTLINE } from "./canvas-theme-aqWjGbfu.js";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
package/dist/globals.css CHANGED
@@ -383,6 +383,9 @@
383
383
  .right-2 {
384
384
  right: calc(var(--spacing) * 2);
385
385
  }
386
+ .right-3 {
387
+ right: calc(var(--spacing) * 3);
388
+ }
386
389
  .right-4 {
387
390
  right: calc(var(--spacing) * 4);
388
391
  }
@@ -1520,6 +1523,9 @@
1520
1523
  .max-w-40 {
1521
1524
  max-width: calc(var(--spacing) * 40);
1522
1525
  }
1526
+ .max-w-64 {
1527
+ max-width: calc(var(--spacing) * 64);
1528
+ }
1523
1529
  .max-w-72 {
1524
1530
  max-width: calc(var(--spacing) * 72);
1525
1531
  }
@@ -1844,6 +1850,9 @@
1844
1850
  .scale-\[1\.02\] {
1845
1851
  scale: 1.02;
1846
1852
  }
1853
+ .-rotate-90 {
1854
+ rotate: calc(90deg * -1);
1855
+ }
1847
1856
  .rotate-90 {
1848
1857
  rotate: 90deg;
1849
1858
  }
@@ -2122,6 +2131,9 @@
2122
2131
  .gap-x-4 {
2123
2132
  column-gap: calc(var(--spacing) * 4);
2124
2133
  }
2134
+ .gap-x-5 {
2135
+ column-gap: calc(var(--spacing) * 5);
2136
+ }
2125
2137
  .gap-x-6 {
2126
2138
  column-gap: calc(var(--spacing) * 6);
2127
2139
  }
@@ -2136,6 +2148,9 @@
2136
2148
  .gap-y-1 {
2137
2149
  row-gap: var(--spacing);
2138
2150
  }
2151
+ .gap-y-1\.5 {
2152
+ row-gap: calc(var(--spacing) * 1.5);
2153
+ }
2139
2154
  .gap-y-2 {
2140
2155
  row-gap: calc(var(--spacing) * 2);
2141
2156
  }
@@ -5310,6 +5325,10 @@
5310
5325
  .focus-visible\:ring-ring:focus-visible {
5311
5326
  --tw-ring-color: hsl(var(--ring));
5312
5327
  }
5328
+ .focus-visible\:outline:focus-visible {
5329
+ outline-style: var(--tw-outline-style);
5330
+ outline-width: 1px;
5331
+ }
5313
5332
  .focus-visible\:outline-1:focus-visible {
5314
5333
  outline-style: var(--tw-outline-style);
5315
5334
  outline-width: 1px;
@@ -9189,6 +9208,10 @@
9189
9208
  grid-row: 3;
9190
9209
  font-size: var(--font-size-xs);
9191
9210
  font-variant-numeric: tabular-nums;
9211
+ overflow: hidden;
9212
+ text-overflow: ellipsis;
9213
+ white-space: nowrap;
9214
+ max-width: 100%;
9192
9215
  }
9193
9216
  .ag-live-pill {
9194
9217
  grid-column: 3;
@@ -9396,12 +9419,6 @@
9396
9419
  height: 80px;
9397
9420
  }
9398
9421
  }
9399
- .ag-live-accounting {
9400
- overflow: hidden;
9401
- text-overflow: ellipsis;
9402
- white-space: nowrap;
9403
- max-width: 100%;
9404
- }
9405
9422
  .ag-live-chip-status {
9406
9423
  grid-column: 3;
9407
9424
  grid-row: 1;
@@ -1,4 +1,4 @@
1
- import { d as AgentGraphNode, n as AgentGraphProps } from "./AgentGraph-K7OCgKcC.js";
1
+ import { d as AgentGraphNode, n as AgentGraphProps } from "./AgentGraph-DzdpPFEI.js";
2
2
  import { ReactNode } from "react";
3
3
  import { ApprovalCard, ApprovalCardProps, ApprovalDiffFile, ApprovalDiffSummary, ApprovalPlanStep, ExpandedToolDetail, ExpandedToolDetailProps, FeedSegment, InlineThinkingItem as InlineThinkingItem$1, InlineThinkingItemProps, InlineToolItem, InlineToolItemProps, LiveDuration, RunGroup, RunGroupProps, RunPhase, RunPhaseList, RunRowShell, RunRowShellProps, RunRowStatus, RunRowStatusDot, RunStatus, RunStatusIcon as RunStatusIcon$1, RunStatusIconProps, StatusMeta, TaskList, TaskListItem, TaskListProps, TaskStatus, ToolCallData, ToolCallData as ToolCallData$1, ToolCallFeed, ToolCallFeedProps, ToolCallStatus, ToolCallType, parseToolEvent, runStatus as runStatus$1, showsTaskPriority, statusMeta as statusMeta$1, taskProgress, taskStatus } from "@tangle-network/ui/run";
4
4
  import { FlowWaterfallProps } from "@tangle-network/charts/react";
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ import { C as ChangedFile, D as SandboxArtifactTerminal, E as SandboxArtifactPan
16
16
  import { n as DiffViewProps, t as DiffView } from "./diff-view-DDHWtR6X.js";
17
17
  import { a as PreviewReadiness, i as PreviewAccess, n as EmbeddedAppView, r as EmbeddedAppViewProps, t as EmbeddedApp } from "./embedded-app-view-Ta5GI4hx.js";
18
18
  import { OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIAction, OpenUIActionsNode, OpenUIArtifactRenderer, OpenUIArtifactRendererProps, OpenUIBadgeNode, OpenUICardNode, OpenUICodeNode, OpenUIComponentNode, OpenUIGridNode, OpenUIHeadingNode, OpenUIKeyValueNode, OpenUIMarkdownNode, OpenUIPrimitive, OpenUISchemaIssue, OpenUISchemaValidation, OpenUISeparatorNode, OpenUIStackNode, OpenUIStatNode, OpenUITableNode, OpenUITextNode, findUnsupportedOpenUINode, validateOpenUIJsonArtifact, validateOpenUIJsonNode } from "./openui.js";
19
- import { $ as traceSpanMark, A as ToolCallData, B as taskStatus, C as RunStatusIcon, D as TaskListItem, E as TaskList, F as parseToolEvent, G as AgentTranscriptEntry, H as LatestEventsProps, I as runStatus, J as TraceEvent, K as foldAgentRows, L as showsTaskPriority, M as ToolCallFeedProps, N as ToolCallStatus, O as TaskListProps, P as ToolCallType, Q as TraceWaterfallProps, R as statusMeta, S as RunStatus, T as StatusMeta, U as AgentStreamRow, V as LatestEvents, W as AgentStreamState, X as TraceWorkspaceProps, Y as TraceWorkspace, Z as TraceWaterfall, _ as RunPhaseList, a as ApprovalPlanStep, b as RunRowStatus, c as FeedSegment, d as InlineToolItem, f as InlineToolItemProps, g as RunPhase, h as RunGroupProps, i as ApprovalDiffSummary, j as ToolCallFeed, k as TaskStatus, l as InlineThinkingItem, m as RunGroup, n as ApprovalCardProps, o as ExpandedToolDetail, p as LiveDuration, q as useAgentStream, r as ApprovalDiffFile, s as ExpandedToolDetailProps, t as ApprovalCard, u as InlineThinkingItemProps, v as RunRowShell, w as RunStatusIconProps, x as RunRowStatusDot, y as RunRowShellProps, z as taskProgress } from "./index-DMX2q0_h.js";
19
+ import { $ as traceSpanMark, A as ToolCallData, B as taskStatus, C as RunStatusIcon, D as TaskListItem, E as TaskList, F as parseToolEvent, G as AgentTranscriptEntry, H as LatestEventsProps, I as runStatus, J as TraceEvent, K as foldAgentRows, L as showsTaskPriority, M as ToolCallFeedProps, N as ToolCallStatus, O as TaskListProps, P as ToolCallType, Q as TraceWaterfallProps, R as statusMeta, S as RunStatus, T as StatusMeta, U as AgentStreamRow, V as LatestEvents, W as AgentStreamState, X as TraceWorkspaceProps, Y as TraceWorkspace, Z as TraceWaterfall, _ as RunPhaseList, a as ApprovalPlanStep, b as RunRowStatus, c as FeedSegment, d as InlineToolItem, f as InlineToolItemProps, g as RunPhase, h as RunGroupProps, i as ApprovalDiffSummary, j as ToolCallFeed, k as TaskStatus, l as InlineThinkingItem, m as RunGroup, n as ApprovalCardProps, o as ExpandedToolDetail, p as LiveDuration, q as useAgentStream, r as ApprovalDiffFile, s as ExpandedToolDetailProps, t as ApprovalCard, u as InlineThinkingItemProps, v as RunRowShell, w as RunStatusIconProps, x as RunRowStatusDot, y as RunRowShellProps, z as taskProgress } from "./index-DFNf9mJ2.js";
20
20
  import { n as filterFileNodes, t as fileTreeFromPaths } from "./index-CrN4d8gd.js";
21
21
  import { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, ChatSession, RegisterActiveSessionOptions, SessionProjectKey, activeSessionsAtom, addMessage, addParts, bumpActiveSessionActivity, clearChat, connectSession, disconnectSession, getActiveSession, getAllActiveSessions, getAllProjectActivity, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getTotalRunningSessionCount, hasBackgroundRunningSessions, isStreamingAtom, messagesAtom, partMapAtom, registerActiveSession, resetActiveSessions, sessionAtom, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useHasBackgroundRunningSessions, useNavbarSessions, useProjectActivity, useProjectSessions, useSessionsByActivity, useTotalRunningSessions } from "./stores.js";
22
22
  import { AgentBranding, CustomToolRenderer, DisplayVariant, FinalTextPart, GroupedMessage, MessageRun, MessageUser, ReasoningPart, Run, RunStats, SessionMessage, SessionPart, TextPart, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime } from "./types.js";
package/dist/index.js CHANGED
@@ -12,8 +12,8 @@ import { a as CodeSurface, i as CODE_THEME, n as buildUnifiedPatch, r as compute
12
12
  import { n as PreviewView, t as EmbeddedAppView } from "./embedded-app-view-DXsoPale.js";
13
13
  import { OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIArtifactRenderer, findUnsupportedOpenUINode, validateOpenUIJsonArtifact, validateOpenUIJsonNode } from "./openui.js";
14
14
  import { S as ReasoningLevelPicker, _ as modelProvider, a as ChatMessage, b as DEFAULT_REASONING_LEVEL_OPTIONS, c as SpeakerHeader, d as UserMessage, f as agentTimelineStepKind, g as ArtifactAgentDock, h as partitionBySpeaker, i as ChatContainer, l as TRANSCRIPT_TEXT, m as isViewerMessage, n as AgentTimelineColumn, o as MessageAuthor, p as foldSummary, r as AgentTimelineStep, s as MessageList, t as AgentTimeline, u as ThinkingIndicator, v as snapHarnessToModel, x as HARNESS_REASONING_OPTIONS, y as AgentProfilePicker } from "./chat-BrlLa4rg.js";
15
- import { C as TraceWaterfall, S as useAgentStream, _ as statusMeta, a as InlineToolItem, b as TraceWorkspace, c as RunPhaseList, d as RunStatusIcon, f as TaskList, g as showsTaskPriority, h as runStatus, i as InlineThinkingItem, l as RunRowShell, m as parseToolEvent, n as ApprovalDiffSummary, o as LiveDuration, p as ToolCallFeed, r as ExpandedToolDetail, s as RunGroup, t as ApprovalCard, u as RunRowStatusDot, v as taskProgress, w as traceSpanMark, x as foldAgentRows, y as taskStatus } from "./run-X00BQO6i.js";
16
- import { t as LatestEvents } from "./latest-events-C_G-ekNg.js";
15
+ import { C as TraceWaterfall, S as useAgentStream, _ as statusMeta, a as InlineToolItem, b as TraceWorkspace, c as RunPhaseList, d as RunStatusIcon, f as TaskList, g as showsTaskPriority, h as runStatus, i as InlineThinkingItem, l as RunRowShell, m as parseToolEvent, n as ApprovalDiffSummary, o as LiveDuration, p as ToolCallFeed, r as ExpandedToolDetail, s as RunGroup, t as ApprovalCard, u as RunRowStatusDot, v as taskProgress, w as traceSpanMark, x as foldAgentRows, y as taskStatus } from "./run-wUKH3Qh6.js";
16
+ import { t as LatestEvents } from "./latest-events-CWvZr8Ut.js";
17
17
  import "./vault-tree.js";
18
18
  import { a as TangleLoginButton, i as UserMenu, n as GitHubLoginButton, r as LoginLayout, t as AuthHeader } from "./auth-BDpoZ_2f.js";
19
19
  import { t as AgentMessagingConnection } from "./connections-DymjzHe_.js";
@@ -1,4 +1,4 @@
1
- import { g as useRunNow, o as fmtEventTime, p as tokenMilliseconds } from "./AgentGraph-DA5lyE06.js";
1
+ import { g as useRunNow, o as fmtEventTime, p as tokenMilliseconds } from "./AgentGraph-YuBa5O8m.js";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { RunStatusIcon, statusMeta } from "@tangle-network/ui/run";
@@ -1,6 +1,6 @@
1
1
  import { i as modelBrandFor, t as ModelBrandStack } from "./model-brand-bTcMjQtB.js";
2
2
  import { t as ProviderIcon } from "./provider-logo-CPKRPHcS.js";
3
- import { r as clampPreview } from "./AgentGraph-DA5lyE06.js";
3
+ import { r as clampPreview } from "./AgentGraph-YuBa5O8m.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { AlertTriangle, Bell, Box, Cable, Camera, Circle, Clock, Code, GitBranch, Repeat, ScanSearch, Sparkles, UserCheck, Webhook, Zap } from "lucide-react";
6
6
  import { RunStatusIcon, statusMeta } from "@tangle-network/ui/run";
@@ -1,8 +1,8 @@
1
1
  import { t as cn } from "./utils-BER_bdgE.js";
2
2
  import { i as modelBrandFor, t as ModelBrandStack } from "./model-brand-bTcMjQtB.js";
3
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-DA5lyE06.js";
5
- import { t as LatestEvents } from "./latest-events-C_G-ekNg.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
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
8
8
  import { X } from "lucide-react";
package/dist/run.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as traceSpanMark, A as ToolCallData, B as taskStatus, C as RunStatusIcon, D as TaskListItem, E as TaskList, F as parseToolEvent, G as AgentTranscriptEntry, H as LatestEventsProps, I as runStatus, J as TraceEvent, K as foldAgentRows, L as showsTaskPriority, M as ToolCallFeedProps, N as ToolCallStatus, O as TaskListProps, P as ToolCallType, Q as TraceWaterfallProps, R as statusMeta, S as RunStatus, T as StatusMeta, U as AgentStreamRow, V as LatestEvents, W as AgentStreamState, X as TraceWorkspaceProps, Y as TraceWorkspace, Z as TraceWaterfall, _ as RunPhaseList, a as ApprovalPlanStep, b as RunRowStatus, c as FeedSegment, d as InlineToolItem, f as InlineToolItemProps, g as RunPhase, h as RunGroupProps, i as ApprovalDiffSummary, j as ToolCallFeed, k as TaskStatus, l as InlineThinkingItem, m as RunGroup, n as ApprovalCardProps, o as ExpandedToolDetail, p as LiveDuration, q as useAgentStream, r as ApprovalDiffFile, s as ExpandedToolDetailProps, t as ApprovalCard, u as InlineThinkingItemProps, v as RunRowShell, w as RunStatusIconProps, x as RunRowStatusDot, y as RunRowShellProps, z as taskProgress } from "./index-DMX2q0_h.js";
1
+ import { $ as traceSpanMark, A as ToolCallData, B as taskStatus, C as RunStatusIcon, D as TaskListItem, E as TaskList, F as parseToolEvent, G as AgentTranscriptEntry, H as LatestEventsProps, I as runStatus, J as TraceEvent, K as foldAgentRows, L as showsTaskPriority, M as ToolCallFeedProps, N as ToolCallStatus, O as TaskListProps, P as ToolCallType, Q as TraceWaterfallProps, R as statusMeta, S as RunStatus, T as StatusMeta, U as AgentStreamRow, V as LatestEvents, W as AgentStreamState, X as TraceWorkspaceProps, Y as TraceWorkspace, Z as TraceWaterfall, _ as RunPhaseList, a as ApprovalPlanStep, b as RunRowStatus, c as FeedSegment, d as InlineToolItem, f as InlineToolItemProps, g as RunPhase, h as RunGroupProps, i as ApprovalDiffSummary, j as ToolCallFeed, k as TaskStatus, l as InlineThinkingItem, m as RunGroup, n as ApprovalCardProps, o as ExpandedToolDetail, p as LiveDuration, q as useAgentStream, r as ApprovalDiffFile, s as ExpandedToolDetailProps, t as ApprovalCard, u as InlineThinkingItemProps, v as RunRowShell, w as RunStatusIconProps, x as RunRowStatusDot, y as RunRowShellProps, z as taskProgress } from "./index-DFNf9mJ2.js";
2
2
  export { type AgentStreamRow, type AgentStreamState, type AgentTranscriptEntry, ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, LatestEvents, type LatestEventsProps, LiveDuration, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, type RunStatus, RunStatusIcon, type RunStatusIconProps, type StatusMeta, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, type TraceEvent, TraceWaterfall, type TraceWaterfallProps, TraceWorkspace, type TraceWorkspaceProps, foldAgentRows, parseToolEvent, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus, traceSpanMark, useAgentStream };
package/dist/run.js CHANGED
@@ -1,3 +1,3 @@
1
- import { C as TraceWaterfall, S as useAgentStream, _ as statusMeta, a as InlineToolItem, b as TraceWorkspace, c as RunPhaseList, d as RunStatusIcon, f as TaskList, g as showsTaskPriority, h as runStatus, i as InlineThinkingItem, l as RunRowShell, m as parseToolEvent, n as ApprovalDiffSummary, o as LiveDuration, p as ToolCallFeed, r as ExpandedToolDetail, s as RunGroup, t as ApprovalCard, u as RunRowStatusDot, v as taskProgress, w as traceSpanMark, x as foldAgentRows, y as taskStatus } from "./run-X00BQO6i.js";
2
- import { t as LatestEvents } from "./latest-events-C_G-ekNg.js";
1
+ import { C as TraceWaterfall, S as useAgentStream, _ as statusMeta, a as InlineToolItem, b as TraceWorkspace, c as RunPhaseList, d as RunStatusIcon, f as TaskList, g as showsTaskPriority, h as runStatus, i as InlineThinkingItem, l as RunRowShell, m as parseToolEvent, n as ApprovalDiffSummary, o as LiveDuration, p as ToolCallFeed, r as ExpandedToolDetail, s as RunGroup, t as ApprovalCard, u as RunRowStatusDot, v as taskProgress, w as traceSpanMark, x as foldAgentRows, y as taskStatus } from "./run-wUKH3Qh6.js";
2
+ import { t as LatestEvents } from "./latest-events-CWvZr8Ut.js";
3
3
  export { ApprovalCard, ApprovalDiffSummary, ExpandedToolDetail, InlineThinkingItem, InlineToolItem, LatestEvents, LiveDuration, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, TaskList, ToolCallFeed, TraceWaterfall, TraceWorkspace, foldAgentRows, parseToolEvent, runStatus, showsTaskPriority, statusMeta, taskProgress, taskStatus, traceSpanMark, useAgentStream };
package/dist/styles.css CHANGED
@@ -383,6 +383,9 @@
383
383
  .right-2 {
384
384
  right: calc(var(--spacing) * 2);
385
385
  }
386
+ .right-3 {
387
+ right: calc(var(--spacing) * 3);
388
+ }
386
389
  .right-4 {
387
390
  right: calc(var(--spacing) * 4);
388
391
  }
@@ -1520,6 +1523,9 @@
1520
1523
  .max-w-40 {
1521
1524
  max-width: calc(var(--spacing) * 40);
1522
1525
  }
1526
+ .max-w-64 {
1527
+ max-width: calc(var(--spacing) * 64);
1528
+ }
1523
1529
  .max-w-72 {
1524
1530
  max-width: calc(var(--spacing) * 72);
1525
1531
  }
@@ -1844,6 +1850,9 @@
1844
1850
  .scale-\[1\.02\] {
1845
1851
  scale: 1.02;
1846
1852
  }
1853
+ .-rotate-90 {
1854
+ rotate: calc(90deg * -1);
1855
+ }
1847
1856
  .rotate-90 {
1848
1857
  rotate: 90deg;
1849
1858
  }
@@ -2122,6 +2131,9 @@
2122
2131
  .gap-x-4 {
2123
2132
  column-gap: calc(var(--spacing) * 4);
2124
2133
  }
2134
+ .gap-x-5 {
2135
+ column-gap: calc(var(--spacing) * 5);
2136
+ }
2125
2137
  .gap-x-6 {
2126
2138
  column-gap: calc(var(--spacing) * 6);
2127
2139
  }
@@ -2136,6 +2148,9 @@
2136
2148
  .gap-y-1 {
2137
2149
  row-gap: var(--spacing);
2138
2150
  }
2151
+ .gap-y-1\.5 {
2152
+ row-gap: calc(var(--spacing) * 1.5);
2153
+ }
2139
2154
  .gap-y-2 {
2140
2155
  row-gap: calc(var(--spacing) * 2);
2141
2156
  }
@@ -5310,6 +5325,10 @@
5310
5325
  .focus-visible\:ring-ring:focus-visible {
5311
5326
  --tw-ring-color: hsl(var(--ring));
5312
5327
  }
5328
+ .focus-visible\:outline:focus-visible {
5329
+ outline-style: var(--tw-outline-style);
5330
+ outline-width: 1px;
5331
+ }
5313
5332
  .focus-visible\:outline-1:focus-visible {
5314
5333
  outline-style: var(--tw-outline-style);
5315
5334
  outline-width: 1px;
@@ -9189,6 +9208,10 @@
9189
9208
  grid-row: 3;
9190
9209
  font-size: var(--font-size-xs);
9191
9210
  font-variant-numeric: tabular-nums;
9211
+ overflow: hidden;
9212
+ text-overflow: ellipsis;
9213
+ white-space: nowrap;
9214
+ max-width: 100%;
9192
9215
  }
9193
9216
  .ag-live-pill {
9194
9217
  grid-column: 3;
@@ -9396,12 +9419,6 @@
9396
9419
  height: 80px;
9397
9420
  }
9398
9421
  }
9399
- .ag-live-accounting {
9400
- overflow: hidden;
9401
- text-overflow: ellipsis;
9402
- white-space: nowrap;
9403
- max-width: 100%;
9404
- }
9405
9422
  .ag-live-chip-status {
9406
9423
  grid-column: 3;
9407
9424
  grid-row: 1;
package/dist/tailwind.css CHANGED
@@ -893,7 +893,7 @@
893
893
  .ag-live-name { grid-column: 2; grid-row: 1; font-size: var(--font-size-sm); line-height: 18px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
894
894
  .ag-live-meta, .ag-live-stats { font-size: var(--font-size-xs); line-height: 16px; color: var(--ag-muted); }
895
895
  .ag-live-meta { grid-column: 2 / -1; grid-row: 2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
896
- .ag-live-accounting { grid-column: 2 / -1; grid-row: 3; font-size: var(--font-size-xs); font-variant-numeric: tabular-nums; }
896
+ .ag-live-accounting { grid-column: 2 / -1; grid-row: 3; font-size: var(--font-size-xs); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
897
897
  .ag-live-pill { grid-column: 3; grid-row: 1; justify-self: end; display: inline-flex; align-items: center; gap: 4px; color: var(--ag-status); }
898
898
  .ag-running-dot { display: inline-block; flex: none; width: 6px; height: 6px; border-radius: var(--radius-pill); background: var(--surface-info-text); }
899
899
  [data-activity="quiet"] .ag-running-dot { background: var(--surface-warning-text); animation: none !important; }
@@ -931,7 +931,6 @@
931
931
  .ag-status-button { flex: 0 0 auto; }
932
932
  .ag-live-card.ag-live-chip { min-height: 80px; height: 80px; }
933
933
  }
934
- .ag-live-accounting { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
935
934
  .ag-live-chip-status { grid-column: 3; grid-row: 1; font-size: var(--font-size-xs); color: var(--ag-status); }
936
935
  .ag-live-chip-cost { grid-column: 4; grid-row: 1; font-size: var(--font-size-xs); color: var(--ag-muted); font-variant-numeric: tabular-nums; }
937
936
  .ag-parent-label { color: var(--ag-muted); font-size: var(--font-size-xs); font-weight: 400; }
@@ -1,5 +1,5 @@
1
1
  import { n as ModelBrandStack, o as modelBrandFor, t as ModelBrandIdentity } from "./model-brand-ZwVS49Fq.js";
2
- import { a as AgentGraphTool, c as AgentGraphEdge, d as AgentGraphNode, f as AgentGraphStatus, i as AgentGraphCallKind, l as AgentGraphEdgeKind, n as AgentGraphProps, o as AgentGraphActivity, r as AgentGraphCall, s as AgentGraphDirection, t as AgentGraph, u as AgentGraphEvent } from "./AgentGraph-K7OCgKcC.js";
2
+ import { a as AgentGraphTool, c as AgentGraphEdge, d as AgentGraphNode, f as AgentGraphStatus, i as AgentGraphCallKind, l as AgentGraphEdgeKind, n as AgentGraphProps, o as AgentGraphActivity, r as AgentGraphCall, s as AgentGraphDirection, t as AgentGraph, u as AgentGraphEvent } from "./AgentGraph-DzdpPFEI.js";
3
3
  import { StatusTone } from "@tangle-network/ui/primitives";
4
4
  import { Component, ReactElement, ReactNode } from "react";
5
5
  import { LucideIcon } from "lucide-react";
package/dist/workflows.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { i as modelBrandFor, t as ModelBrandStack } from "./model-brand-bTcMjQtB.js";
2
- import { n as WorkflowGraph, t as AgentGraph } from "./AgentGraph-DA5lyE06.js";
3
- import { C as actionNodeId, D as triggerNodeId, M as humanizeIdentifier, N as parseActionPath, O as triggerNodeIndex, P as shortModel, S as TRIGGER_NODE_ID, T as buildWorkflowGraph, c as STATUS_COLOR, d as STATUS_TONE, j as actionPathLabel, l as STATUS_LABEL, n as KIND_ICON, p as StatusPill, r as NodeMark, u as STATUS_PILL, w as branchNodeId } from "./node-ui-DJo7yr4o.js";
2
+ import { n as WorkflowGraph, t as AgentGraph } from "./AgentGraph-YuBa5O8m.js";
3
+ import { C as actionNodeId, D as triggerNodeId, M as humanizeIdentifier, N as parseActionPath, O as triggerNodeIndex, P as shortModel, S as TRIGGER_NODE_ID, T as buildWorkflowGraph, c as STATUS_COLOR, d as STATUS_TONE, j as actionPathLabel, l as STATUS_LABEL, n as KIND_ICON, p as StatusPill, r as NodeMark, u as STATUS_PILL, w as branchNodeId } from "./node-ui-Dst2R2M5.js";
4
4
  import { n as classifyOutput, r as condenseText, t as NodeOutputBody } from "./node-output-DlySTwxr.js";
5
5
  export { AgentGraph, KIND_ICON, ModelBrandStack, NodeMark, NodeOutputBody, StatusPill as NodeStatusPill, STATUS_COLOR, STATUS_LABEL, STATUS_PILL, STATUS_TONE, TRIGGER_NODE_ID, WorkflowGraph as WorkflowGraphLazy, actionNodeId, actionPathLabel, branchNodeId, buildWorkflowGraph, classifyOutput, condenseText, humanizeIdentifier, modelBrandFor, parseActionPath, shortModel, triggerNodeId, triggerNodeIndex };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "license": "MIT",
5
5
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
6
6
  "repository": {