@tangle-network/sandbox-ui 4.0.1 → 6.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.
@@ -3,7 +3,7 @@ import { i as modelBrandFor, t as ModelBrandStack } from "./model-brand-BQlarCIo
3
3
  import { t as ProviderIcon } from "./provider-logo-CPKRPHcS.js";
4
4
  import { t as AgentGraph$1 } from "./AgentGraph-BGWx9hhS.js";
5
5
  import { s as timelineItems, t as useFollowTail } from "./use-follow-tail-DZFn7QDO.js";
6
- import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
8
8
  import { X } from "lucide-react";
9
9
  import { activityTiming, ambientStyle, syncAmbient } from "@tangle-network/charts/react/agent-graph/presentation";
@@ -176,6 +176,7 @@ function foldAgentRows(entries, rows) {
176
176
  const empty = (identity, history) => ({
177
177
  identity,
178
178
  rows: [],
179
+ entries: [],
179
180
  first: null,
180
181
  lastSeq: -1,
181
182
  alive: !!identity && (history?.alive ?? true),
@@ -195,9 +196,11 @@ function receive(prior, page, direction, fromHead) {
195
196
  if (trimmed) rows = prior.reading ? rows.slice(0, 2e3) : rows.slice(-2e3);
196
197
  const first = fresh ? page.firstSeq ?? rows[0]?.seq ?? null : direction === "before" ? page.firstSeq ?? prior.first : trimmed && !prior.reading ? rows[0]?.seq ?? prior.first : prior.first;
197
198
  const lastSeq = fresh ? page.lastSeq ?? rows.at(-1)?.seq ?? -1 : direction === "before" ? prior.lastSeq : page.lastSeq ?? rows.at(-1)?.seq ?? prior.lastSeq;
199
+ const entries = fresh ? foldAgentRows([], rows) : direction === "after" ? foldAgentRows(prior.entries, page.events) : foldAgentRows([], rows);
198
200
  return {
199
201
  ...prior,
200
202
  rows,
203
+ entries,
201
204
  first,
202
205
  lastSeq,
203
206
  generation: page.generation ?? prior.generation,
@@ -356,9 +359,9 @@ function useAgentStream(url, openEventSource, history) {
356
359
  })
357
360
  }
358
361
  });
359
- let entries = [], error = state.error;
360
- try {
361
- entries = foldAgentRows([], history?.projectRows?.(state.rows) ?? state.rows);
362
+ let entries = state.entries, error = state.error;
363
+ if (history?.projectRows) try {
364
+ entries = foldAgentRows([], history.projectRows(state.rows) ?? state.rows);
362
365
  } catch (cause) {
363
366
  error = cause instanceof Error ? cause.message : String(cause);
364
367
  }
@@ -482,7 +485,7 @@ function agentGraphAt(spans, byId, at, epoch, cards, extraEdges = []) {
482
485
  * streaming in a right-hand drawer, and the page's code workspace and scoreboard following the time cursor. Scrubbing is time travel:
483
486
  * every panel shows what was true at the cursor, and the conversation shows what had been said by then.
484
487
  */
485
- function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChange, files, scoreboard, selectedAgentId, conversationOpen, onSelectAgent, agentCards, agentGraph, className, ...waterfall }) {
488
+ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChange, files, scoreboard, selectedAgentId, conversationOpen, onSelectAgent, renderAgentDrawer, docked, commission, agentCards, agentGraph, className, ...waterfall }) {
486
489
  const byId = useMemo(() => new Map(trace.spans.flatMap((span) => span.id ? [[span.id, span]] : [])), [trace]);
487
490
  const [ownSelection, setOwnSelection] = useState();
488
491
  const selected = selectedAgentId ?? ownSelection;
@@ -493,6 +496,7 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
493
496
  const agent = selected ? byId.get(selected) : void 0, card = selected ? agentCards?.[selected] : void 0;
494
497
  const agentId = agent?.id ?? (card ? selected : void 0), agentName = agent?.name ?? card?.label ?? agentId, agentModel = agent?.detail ?? card?.model;
495
498
  const showConversation = !!agentId && conversationOpen !== false;
499
+ const detail = showConversation && agentId ? renderAgentDrawer?.(agentId) : void 0;
496
500
  const { defaultLayout = "tree", ...graphOptions } = agentGraph ?? {};
497
501
  const [graphLayout, setGraphLayout] = useState(defaultLayout);
498
502
  const [cursor, setCursor] = useState(null);
@@ -523,165 +527,181 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
523
527
  const graphNow = cursor !== null && waterfall.startedAt !== void 0 ? waterfall.startedAt + cursor : void 0;
524
528
  const shown = cursor === null || origin === void 0 ? stream.entries : stream.entries.filter((entry) => entry.at - origin <= cursor);
525
529
  const live = stream.alive && !stream.error && cursor === null;
530
+ const ended = !stream.alive && !stream.error;
526
531
  const items = useMemo(() => timelineItems(shown, live), [shown, live]);
527
532
  const scroller = useRef(null);
528
533
  const { unseen, jump } = useFollowTail(scroller, {
529
534
  identity: showConversation ? selected : void 0,
530
535
  content: items
531
536
  });
532
- return /* @__PURE__ */ jsxs("div", {
533
- className: cn("sandbox-trace-workspace flex min-h-0 flex-col gap-3", className),
534
- children: [
535
- /* @__PURE__ */ jsx("div", {
536
- className: showConversation ? "min-w-0 lg:mr-[420px]" : "min-w-0",
537
- children: /* @__PURE__ */ jsx(TraceWaterfall, {
538
- ...waterfall,
539
- trace,
540
- markers: events,
537
+ const left = /* @__PURE__ */ jsxs(Fragment, { children: [
538
+ /* @__PURE__ */ jsx("div", {
539
+ className: showConversation && !docked ? "min-w-0 lg:mr-[420px]" : "min-w-0",
540
+ children: /* @__PURE__ */ jsx(TraceWaterfall, {
541
+ ...waterfall,
542
+ trace,
543
+ markers: events,
544
+ selectedId: agentId,
545
+ onDeselect: () => select(void 0),
546
+ onSelect: (span) => {
547
+ const owner = agentOf(span, byId);
548
+ if (owner?.id) select(owner.id);
549
+ },
550
+ onTimeChange: (time) => {
551
+ setCursor(time);
552
+ onTimeChange?.(time);
553
+ },
554
+ latestEvents: /* @__PURE__ */ jsx(LatestEvents, {
555
+ agents: graph.nodes,
556
+ now: graphNow,
557
+ live: cursor === null,
541
558
  selectedId: agentId,
542
- onDeselect: () => select(void 0),
543
- onSelect: (span) => {
544
- const owner = agentOf(span, byId);
545
- if (owner?.id) select(owner.id);
546
- },
547
- onTimeChange: (time) => {
548
- setCursor(time);
549
- onTimeChange?.(time);
550
- },
551
- latestEvents: /* @__PURE__ */ jsx(LatestEvents, {
552
- agents: graph.nodes,
553
- now: graphNow,
554
- live: cursor === null,
555
- selectedId: agentId,
556
- onSelect: select
557
- }),
558
- graph: /* @__PURE__ */ jsxs("section", {
559
- className: "flex h-[60vh] min-h-[480px] flex-col",
560
- "aria-label": "Agent graph",
561
- children: [/* @__PURE__ */ jsxs("label", {
562
- className: "flex h-8 shrink-0 items-center justify-end gap-2 px-3 text-xs text-muted-foreground",
563
- children: ["Layout", /* @__PURE__ */ jsxs("select", {
564
- "aria-label": "Graph layout",
565
- value: graphLayout,
566
- onChange: (event) => setGraphLayout(event.currentTarget.value),
567
- className: "rounded-[var(--radius-control)] border border-border bg-card px-2 py-1 text-foreground",
568
- children: [/* @__PURE__ */ jsx("option", {
569
- value: "tree",
570
- children: "Tree"
571
- }), /* @__PURE__ */ jsx("option", {
572
- value: "hub",
573
- children: "Hub"
574
- })]
559
+ onSelect: select
560
+ }),
561
+ graph: /* @__PURE__ */ jsxs("section", {
562
+ className: "flex h-[60vh] min-h-[480px] flex-col",
563
+ "aria-label": "Agent graph",
564
+ children: [/* @__PURE__ */ jsxs("label", {
565
+ className: "flex h-8 shrink-0 items-center justify-end gap-2 px-3 text-xs text-muted-foreground",
566
+ children: ["Layout", /* @__PURE__ */ jsxs("select", {
567
+ "aria-label": "Graph layout",
568
+ value: graphLayout,
569
+ onChange: (event) => setGraphLayout(event.currentTarget.value),
570
+ className: "rounded-[var(--radius-control)] border border-border bg-card px-2 py-1 text-foreground",
571
+ children: [/* @__PURE__ */ jsx("option", {
572
+ value: "tree",
573
+ children: "Tree"
574
+ }), /* @__PURE__ */ jsx("option", {
575
+ value: "hub",
576
+ children: "Hub"
575
577
  })]
576
- }), /* @__PURE__ */ jsx(AgentGraph$1, {
577
- ...graphOptions,
578
- layout: graphLayout,
579
- renderEventIcon: (event) => toolGlyph(event.label),
580
- nodes: graph.nodes,
581
- edges: graph.edges,
582
- selectedId: agentId ?? null,
583
- onSelect: select,
584
- showLatestEvents: false,
585
- now: graphNow,
586
- live: cursor === null && graph.nodes.some((node) => [
587
- "running",
588
- "waiting",
589
- "needs-you",
590
- "failed"
591
- ].includes(node.status)),
592
- className: cn("min-h-0 flex-1", graphOptions.className)
593
578
  })]
594
- })
579
+ }), /* @__PURE__ */ jsx(AgentGraph$1, {
580
+ ...graphOptions,
581
+ layout: graphLayout,
582
+ renderEventIcon: (event) => toolGlyph(event.label),
583
+ nodes: graph.nodes,
584
+ edges: graph.edges,
585
+ selectedId: agentId ?? null,
586
+ onSelect: select,
587
+ showLatestEvents: false,
588
+ now: graphNow,
589
+ live: cursor === null && graph.nodes.some((node) => [
590
+ "running",
591
+ "waiting",
592
+ "needs-you",
593
+ "failed"
594
+ ].includes(node.status)),
595
+ className: cn("min-h-0 flex-1", graphOptions.className)
596
+ })]
595
597
  })
596
- }),
597
- files && /* @__PURE__ */ jsx("section", {
598
- "aria-label": "Code at the cursor",
599
- className: "flex h-[70vh] min-h-0 flex-col overflow-hidden rounded-xl border border-border bg-card",
600
- children: files
601
- }),
602
- scoreboard && /* @__PURE__ */ jsx("section", {
603
- "aria-label": "Scoreboard at the cursor",
604
- className: "overflow-hidden rounded-xl border border-border bg-card",
605
- children: scoreboard
606
- }),
607
- showConversation && /* @__PURE__ */ jsxs("aside", {
608
- "aria-label": `${agentName} conversation`,
609
- onKeyDown: (event) => {
610
- if (event.key === "Escape") select(void 0);
611
- },
612
- className: "fixed inset-y-0 right-0 z-40 flex w-full max-w-[420px] flex-col border-l border-border bg-card shadow-2xl",
613
- children: [
614
- /* @__PURE__ */ jsxs("header", {
615
- className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2",
616
- children: [
617
- /* @__PURE__ */ jsx("span", {
618
- className: cn("h-1.5 w-1.5 shrink-0 rounded-full", live ? "bg-[var(--surface-info-text)]" : "bg-muted-foreground"),
598
+ })
599
+ }),
600
+ files && /* @__PURE__ */ jsx("section", {
601
+ "aria-label": "Code at the cursor",
602
+ className: "flex h-[70vh] min-h-0 flex-col overflow-hidden rounded-xl border border-border bg-card",
603
+ children: files
604
+ }),
605
+ scoreboard && /* @__PURE__ */ jsx("section", {
606
+ "aria-label": "Scoreboard at the cursor",
607
+ className: "overflow-hidden rounded-xl border border-border bg-card",
608
+ children: scoreboard
609
+ })
610
+ ] });
611
+ return /* @__PURE__ */ jsxs("div", {
612
+ className: cn("sandbox-trace-workspace flex min-h-0 gap-3", docked && showConversation ? "flex-row" : "flex-col", className),
613
+ children: [docked && showConversation ? /* @__PURE__ */ jsx("div", {
614
+ className: "flex min-w-0 flex-1 flex-col gap-3",
615
+ children: left
616
+ }) : left, showConversation && /* @__PURE__ */ jsxs("aside", {
617
+ "aria-label": `${agentName} conversation`,
618
+ onKeyDown: (event) => {
619
+ if (event.key === "Escape") select(void 0);
620
+ },
621
+ 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",
622
+ children: [
623
+ /* @__PURE__ */ jsxs("header", {
624
+ className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2",
625
+ children: [
626
+ /* @__PURE__ */ jsx("span", {
627
+ className: cn("h-1.5 w-1.5 shrink-0 rounded-full", live ? "bg-[var(--surface-info-text)]" : "bg-muted-foreground"),
628
+ "aria-hidden": true,
629
+ "data-motion": live ? "essential" : void 0,
630
+ style: live ? ambientStyle : void 0,
631
+ onAnimationStart: syncAmbient
632
+ }),
633
+ /* @__PURE__ */ jsx("h3", {
634
+ className: "min-w-0 flex-1 truncate text-sm font-semibold",
635
+ children: agentName
636
+ }),
637
+ agentModel && /* @__PURE__ */ jsx("span", {
638
+ className: "truncate text-xs text-muted-foreground",
639
+ children: agentModel
640
+ }),
641
+ /* @__PURE__ */ jsx("button", {
642
+ type: "button",
643
+ "aria-label": "Close conversation",
644
+ title: "Close (Esc)",
645
+ onClick: () => select(void 0),
646
+ className: "shrink-0 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
647
+ children: /* @__PURE__ */ jsx(X, {
619
648
  "aria-hidden": true,
620
- "data-motion": live ? "essential" : void 0,
621
- style: live ? ambientStyle : void 0,
622
- onAnimationStart: syncAmbient
623
- }),
624
- /* @__PURE__ */ jsx("h3", {
625
- className: "min-w-0 flex-1 truncate text-sm font-semibold",
626
- children: agentName
627
- }),
628
- agentModel && /* @__PURE__ */ jsx("span", {
629
- className: "truncate text-xs text-muted-foreground",
630
- children: agentModel
631
- }),
632
- /* @__PURE__ */ jsx("button", {
633
- type: "button",
634
- "aria-label": "Close conversation",
635
- title: "Close (Esc)",
636
- onClick: () => select(void 0),
637
- className: "shrink-0 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
638
- children: /* @__PURE__ */ jsx(X, {
639
- "aria-hidden": true,
640
- className: "h-4 w-4"
641
- })
649
+ className: "h-4 w-4"
642
650
  })
643
- ]
644
- }),
645
- facts.length > 0 && /* @__PURE__ */ jsx("section", {
646
- "aria-label": "Recorded agent details",
647
- tabIndex: 0,
648
- className: "max-h-[35vh] shrink-0 overflow-y-auto border-b border-border px-3 py-2 text-sm",
649
- children: /* @__PURE__ */ jsx("dl", {
650
- className: "space-y-2",
651
- children: facts.map((fact, index) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
652
- className: "text-xs text-muted-foreground",
653
- children: fact.label
654
- }), /* @__PURE__ */ jsx("dd", {
655
- className: "m-0 whitespace-pre-wrap break-words",
656
- children: fact.value
657
- })] }, index))
658
651
  })
659
- }),
660
- /* @__PURE__ */ jsx("div", {
661
- ref: scroller,
662
- "data-primary-content": "",
663
- className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
664
- children: streamUrl && (cursor === null || origin !== void 0) ? /* @__PURE__ */ jsx(AgentTimeline, {
665
- items,
666
- isThinking: live && items.length === 0,
667
- emptyState: /* @__PURE__ */ jsx("p", {
668
- className: "p-4 text-sm text-muted-foreground",
669
- children: stream.error ?? "No messages yet."
670
- })
671
- }) : /* @__PURE__ */ jsx("p", {
652
+ ]
653
+ }),
654
+ (facts.length > 0 || detail) && /* @__PURE__ */ jsxs("section", {
655
+ "aria-label": "Recorded agent details",
656
+ tabIndex: 0,
657
+ className: "max-h-[35vh] shrink-0 overflow-y-auto border-b border-border px-3 py-2 text-sm",
658
+ children: [facts.length > 0 && /* @__PURE__ */ jsx("dl", {
659
+ className: "space-y-2",
660
+ children: facts.map((fact, index) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
661
+ className: "text-xs text-muted-foreground",
662
+ children: fact.label
663
+ }), /* @__PURE__ */ jsx("dd", {
664
+ className: "m-0 whitespace-pre-wrap break-words",
665
+ children: fact.value
666
+ })] }, index))
667
+ }), detail]
668
+ }),
669
+ commission && (commission.text || commission.by) && /* @__PURE__ */ jsxs("section", {
670
+ "aria-label": "Commission",
671
+ className: "shrink-0 border-b border-border bg-muted/40 px-3 py-2 text-sm",
672
+ children: [commission.by && /* @__PURE__ */ jsxs("p", {
673
+ className: "m-0 mb-1 text-xs text-muted-foreground",
674
+ children: ["commissioned by ", commission.by]
675
+ }), commission.text && /* @__PURE__ */ jsx("p", {
676
+ className: "m-0 line-clamp-6 whitespace-pre-wrap break-words",
677
+ children: commission.text
678
+ })]
679
+ }),
680
+ /* @__PURE__ */ jsx("div", {
681
+ ref: scroller,
682
+ "data-primary-content": "",
683
+ className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
684
+ children: streamUrl && (cursor === null || origin !== void 0) ? /* @__PURE__ */ jsx(AgentTimeline, {
685
+ items,
686
+ isThinking: live && items.length === 0,
687
+ emptyState: /* @__PURE__ */ jsx("p", {
672
688
  className: "p-4 text-sm text-muted-foreground",
673
- children: "Conversation: not time-indexed"
689
+ "data-stream": stream.error ? "reconnecting" : ended ? "ended" : "empty",
690
+ children: stream.error ? ended ? stream.error : `Reconnecting… ${stream.error}` : ended ? "The conversation ended." : "No messages yet."
674
691
  })
675
- }),
676
- unseen && /* @__PURE__ */ jsx("button", {
677
- type: "button",
678
- onClick: () => jump(),
679
- 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",
680
- children: "New output"
692
+ }) : /* @__PURE__ */ jsx("p", {
693
+ className: "p-4 text-sm text-muted-foreground",
694
+ children: "Conversation: not time-indexed"
681
695
  })
682
- ]
683
- })
684
- ]
696
+ }),
697
+ unseen && /* @__PURE__ */ jsx("button", {
698
+ type: "button",
699
+ onClick: () => jump(),
700
+ className: "absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full border border-border bg-background px-3 py-1 text-xs shadow-sm",
701
+ children: "New output"
702
+ })
703
+ ]
704
+ })]
685
705
  });
686
706
  }
687
707
  export { useEventStream as a, useAgentStream as i, LatestEvents as n, TraceWaterfall as o, foldAgentRows as r, traceSpanMark as s, TraceWorkspace as t };