@tangle-network/sandbox-ui 5.0.0 → 6.0.1

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";
@@ -43,6 +43,40 @@ function TraceWaterfall({ renderIcon = traceSpanMark, ...props }) {
43
43
  }
44
44
  // src/run/agent-stream.ts
45
45
  const textOf = (value) => value == null ? void 0 : typeof value === "string" ? value : JSON.stringify(value);
46
+ /** A native harness row (live-stream evidence and retained captures: `{ type, data }`) as a producer event, so
47
+ * non-root agents render: the wall's transcript pages and per-agent streams serve these rows verbatim, and before
48
+ * this adapter every one of them was dropped by the fold — a live child agent showed nothing at all. Text and
49
+ * reasoning parts (opencode `raw` events and the SDK's `message.part.updated`) become deltas; a `message.updated`
50
+ * carrying a user role becomes the user's words. Native tool shapes vary by harness and stay unrendered until a
51
+ * captured sample exists to map them honestly. */
52
+ function nativeEventOf(row) {
53
+ const data = row.data;
54
+ if (!data || typeof data !== "object") return void 0;
55
+ const wrapped = data.event;
56
+ const source = wrapped && (wrapped.type === "text" || wrapped.type === "reasoning" || wrapped.type === "message") ? wrapped : data;
57
+ const type = source.type;
58
+ const part = source.part;
59
+ if ((type === "text" || type === "message.part.updated") && part?.type === "text" && typeof part.text === "string") return {
60
+ kind: "text_delta",
61
+ text: part.text
62
+ };
63
+ if ((type === "reasoning" || type === "message.part.updated") && part?.type === "reasoning" && typeof part.text === "string") return {
64
+ kind: "reasoning_delta",
65
+ text: part.text
66
+ };
67
+ if (type === "text" && typeof source.text === "string") return {
68
+ kind: "text_delta",
69
+ text: source.text
70
+ };
71
+ const info = source.properties?.info ?? source.info;
72
+ if ((type === "message" || type === "message.updated") && info?.role === "user") {
73
+ const words = typeof info.content === "string" ? info.content : typeof info.text === "string" ? info.text : void 0;
74
+ if (words) return {
75
+ kind: "user",
76
+ text: words
77
+ };
78
+ }
79
+ }
46
80
  const failed = (value) => typeof value === "object" && value !== null && ("error" in value || value.is_error === true || value.isError === true);
47
81
  const recorded = (text) => {
48
82
  try {
@@ -67,7 +101,7 @@ const TOOL_TYPES = /* @__PURE__ */ new Set([
67
101
  function foldAgentRows(entries, rows) {
68
102
  const next = [...entries];
69
103
  for (const row of rows) {
70
- const event = row.event;
104
+ const event = row.event ?? nativeEventOf(row);
71
105
  if (!event) continue;
72
106
  const at = Date.parse(row.at ?? ""), scope = JSON.stringify([row.stream ?? null, row.attempt ?? null]);
73
107
  const base = {
@@ -176,6 +210,7 @@ function foldAgentRows(entries, rows) {
176
210
  const empty = (identity, history) => ({
177
211
  identity,
178
212
  rows: [],
213
+ entries: [],
179
214
  first: null,
180
215
  lastSeq: -1,
181
216
  alive: !!identity && (history?.alive ?? true),
@@ -195,9 +230,11 @@ function receive(prior, page, direction, fromHead) {
195
230
  if (trimmed) rows = prior.reading ? rows.slice(0, 2e3) : rows.slice(-2e3);
196
231
  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
232
  const lastSeq = fresh ? page.lastSeq ?? rows.at(-1)?.seq ?? -1 : direction === "before" ? prior.lastSeq : page.lastSeq ?? rows.at(-1)?.seq ?? prior.lastSeq;
233
+ const entries = fresh ? foldAgentRows([], rows) : direction === "after" ? foldAgentRows(prior.entries, page.events) : foldAgentRows([], rows);
198
234
  return {
199
235
  ...prior,
200
236
  rows,
237
+ entries,
201
238
  first,
202
239
  lastSeq,
203
240
  generation: page.generation ?? prior.generation,
@@ -356,9 +393,9 @@ function useAgentStream(url, openEventSource, history) {
356
393
  })
357
394
  }
358
395
  });
359
- let entries = [], error = state.error;
360
- try {
361
- entries = foldAgentRows([], history?.projectRows?.(state.rows) ?? state.rows);
396
+ let entries = state.entries, error = state.error;
397
+ if (history?.projectRows) try {
398
+ entries = foldAgentRows([], history.projectRows(state.rows) ?? state.rows);
362
399
  } catch (cause) {
363
400
  error = cause instanceof Error ? cause.message : String(cause);
364
401
  }
@@ -482,7 +519,7 @@ function agentGraphAt(spans, byId, at, epoch, cards, extraEdges = []) {
482
519
  * streaming in a right-hand drawer, and the page's code workspace and scoreboard following the time cursor. Scrubbing is time travel:
483
520
  * every panel shows what was true at the cursor, and the conversation shows what had been said by then.
484
521
  */
485
- function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChange, files, scoreboard, selectedAgentId, conversationOpen, onSelectAgent, renderAgentDrawer, agentCards, agentGraph, className, ...waterfall }) {
522
+ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChange, files, scoreboard, selectedAgentId, conversationOpen, onSelectAgent, renderAgentDrawer, docked, commission, agentCards, agentGraph, className, ...waterfall }) {
486
523
  const byId = useMemo(() => new Map(trace.spans.flatMap((span) => span.id ? [[span.id, span]] : [])), [trace]);
487
524
  const [ownSelection, setOwnSelection] = useState();
488
525
  const selected = selectedAgentId ?? ownSelection;
@@ -524,165 +561,181 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
524
561
  const graphNow = cursor !== null && waterfall.startedAt !== void 0 ? waterfall.startedAt + cursor : void 0;
525
562
  const shown = cursor === null || origin === void 0 ? stream.entries : stream.entries.filter((entry) => entry.at - origin <= cursor);
526
563
  const live = stream.alive && !stream.error && cursor === null;
564
+ const ended = !stream.alive && !stream.error;
527
565
  const items = useMemo(() => timelineItems(shown, live), [shown, live]);
528
566
  const scroller = useRef(null);
529
567
  const { unseen, jump } = useFollowTail(scroller, {
530
568
  identity: showConversation ? selected : void 0,
531
569
  content: items
532
570
  });
533
- return /* @__PURE__ */ jsxs("div", {
534
- className: cn("sandbox-trace-workspace flex min-h-0 flex-col gap-3", className),
535
- children: [
536
- /* @__PURE__ */ jsx("div", {
537
- className: showConversation ? "min-w-0 lg:mr-[420px]" : "min-w-0",
538
- children: /* @__PURE__ */ jsx(TraceWaterfall, {
539
- ...waterfall,
540
- trace,
541
- markers: events,
571
+ const left = /* @__PURE__ */ jsxs(Fragment, { children: [
572
+ /* @__PURE__ */ jsx("div", {
573
+ className: showConversation && !docked ? "min-w-0 lg:mr-[420px]" : "min-w-0",
574
+ children: /* @__PURE__ */ jsx(TraceWaterfall, {
575
+ ...waterfall,
576
+ trace,
577
+ markers: events,
578
+ selectedId: agentId,
579
+ onDeselect: () => select(void 0),
580
+ onSelect: (span) => {
581
+ const owner = agentOf(span, byId);
582
+ if (owner?.id) select(owner.id);
583
+ },
584
+ onTimeChange: (time) => {
585
+ setCursor(time);
586
+ onTimeChange?.(time);
587
+ },
588
+ latestEvents: /* @__PURE__ */ jsx(LatestEvents, {
589
+ agents: graph.nodes,
590
+ now: graphNow,
591
+ live: cursor === null,
542
592
  selectedId: agentId,
543
- onDeselect: () => select(void 0),
544
- onSelect: (span) => {
545
- const owner = agentOf(span, byId);
546
- if (owner?.id) select(owner.id);
547
- },
548
- onTimeChange: (time) => {
549
- setCursor(time);
550
- onTimeChange?.(time);
551
- },
552
- latestEvents: /* @__PURE__ */ jsx(LatestEvents, {
553
- agents: graph.nodes,
554
- now: graphNow,
555
- live: cursor === null,
556
- selectedId: agentId,
557
- onSelect: select
558
- }),
559
- graph: /* @__PURE__ */ jsxs("section", {
560
- className: "flex h-[60vh] min-h-[480px] flex-col",
561
- "aria-label": "Agent graph",
562
- children: [/* @__PURE__ */ jsxs("label", {
563
- className: "flex h-8 shrink-0 items-center justify-end gap-2 px-3 text-xs text-muted-foreground",
564
- children: ["Layout", /* @__PURE__ */ jsxs("select", {
565
- "aria-label": "Graph layout",
566
- value: graphLayout,
567
- onChange: (event) => setGraphLayout(event.currentTarget.value),
568
- className: "rounded-[var(--radius-control)] border border-border bg-card px-2 py-1 text-foreground",
569
- children: [/* @__PURE__ */ jsx("option", {
570
- value: "tree",
571
- children: "Tree"
572
- }), /* @__PURE__ */ jsx("option", {
573
- value: "hub",
574
- children: "Hub"
575
- })]
593
+ onSelect: select
594
+ }),
595
+ graph: /* @__PURE__ */ jsxs("section", {
596
+ className: "flex h-[60vh] min-h-[480px] flex-col",
597
+ "aria-label": "Agent graph",
598
+ children: [/* @__PURE__ */ jsxs("label", {
599
+ className: "flex h-8 shrink-0 items-center justify-end gap-2 px-3 text-xs text-muted-foreground",
600
+ children: ["Layout", /* @__PURE__ */ jsxs("select", {
601
+ "aria-label": "Graph layout",
602
+ value: graphLayout,
603
+ onChange: (event) => setGraphLayout(event.currentTarget.value),
604
+ className: "rounded-[var(--radius-control)] border border-border bg-card px-2 py-1 text-foreground",
605
+ children: [/* @__PURE__ */ jsx("option", {
606
+ value: "tree",
607
+ children: "Tree"
608
+ }), /* @__PURE__ */ jsx("option", {
609
+ value: "hub",
610
+ children: "Hub"
576
611
  })]
577
- }), /* @__PURE__ */ jsx(AgentGraph$1, {
578
- ...graphOptions,
579
- layout: graphLayout,
580
- renderEventIcon: (event) => toolGlyph(event.label),
581
- nodes: graph.nodes,
582
- edges: graph.edges,
583
- selectedId: agentId ?? null,
584
- onSelect: select,
585
- showLatestEvents: false,
586
- now: graphNow,
587
- live: cursor === null && graph.nodes.some((node) => [
588
- "running",
589
- "waiting",
590
- "needs-you",
591
- "failed"
592
- ].includes(node.status)),
593
- className: cn("min-h-0 flex-1", graphOptions.className)
594
612
  })]
595
- })
613
+ }), /* @__PURE__ */ jsx(AgentGraph$1, {
614
+ ...graphOptions,
615
+ layout: graphLayout,
616
+ renderEventIcon: (event) => toolGlyph(event.label),
617
+ nodes: graph.nodes,
618
+ edges: graph.edges,
619
+ selectedId: agentId ?? null,
620
+ onSelect: select,
621
+ showLatestEvents: false,
622
+ now: graphNow,
623
+ live: cursor === null && graph.nodes.some((node) => [
624
+ "running",
625
+ "waiting",
626
+ "needs-you",
627
+ "failed"
628
+ ].includes(node.status)),
629
+ className: cn("min-h-0 flex-1", graphOptions.className)
630
+ })]
596
631
  })
597
- }),
598
- files && /* @__PURE__ */ jsx("section", {
599
- "aria-label": "Code at the cursor",
600
- className: "flex h-[70vh] min-h-0 flex-col overflow-hidden rounded-xl border border-border bg-card",
601
- children: files
602
- }),
603
- scoreboard && /* @__PURE__ */ jsx("section", {
604
- "aria-label": "Scoreboard at the cursor",
605
- className: "overflow-hidden rounded-xl border border-border bg-card",
606
- children: scoreboard
607
- }),
608
- showConversation && /* @__PURE__ */ jsxs("aside", {
609
- "aria-label": `${agentName} conversation`,
610
- onKeyDown: (event) => {
611
- if (event.key === "Escape") select(void 0);
612
- },
613
- 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",
614
- children: [
615
- /* @__PURE__ */ jsxs("header", {
616
- className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2",
617
- children: [
618
- /* @__PURE__ */ jsx("span", {
619
- className: cn("h-1.5 w-1.5 shrink-0 rounded-full", live ? "bg-[var(--surface-info-text)]" : "bg-muted-foreground"),
632
+ })
633
+ }),
634
+ files && /* @__PURE__ */ jsx("section", {
635
+ "aria-label": "Code at the cursor",
636
+ className: "flex h-[70vh] min-h-0 flex-col overflow-hidden rounded-xl border border-border bg-card",
637
+ children: files
638
+ }),
639
+ scoreboard && /* @__PURE__ */ jsx("section", {
640
+ "aria-label": "Scoreboard at the cursor",
641
+ className: "overflow-hidden rounded-xl border border-border bg-card",
642
+ children: scoreboard
643
+ })
644
+ ] });
645
+ return /* @__PURE__ */ jsxs("div", {
646
+ className: cn("sandbox-trace-workspace flex min-h-0 gap-3", docked && showConversation ? "flex-row" : "flex-col", className),
647
+ children: [docked && showConversation ? /* @__PURE__ */ jsx("div", {
648
+ className: "flex min-w-0 flex-1 flex-col gap-3",
649
+ children: left
650
+ }) : left, showConversation && /* @__PURE__ */ jsxs("aside", {
651
+ "aria-label": `${agentName} conversation`,
652
+ onKeyDown: (event) => {
653
+ if (event.key === "Escape") select(void 0);
654
+ },
655
+ className: docked ? "flex min-h-0 w-[clamp(320px,32vw,560px)] shrink-0 resize-x flex-col overflow-hidden rounded-xl border border-border bg-card" : "fixed inset-y-0 right-0 z-40 flex w-full max-w-[420px] flex-col border-l border-border bg-card shadow-2xl",
656
+ children: [
657
+ /* @__PURE__ */ jsxs("header", {
658
+ className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2",
659
+ children: [
660
+ /* @__PURE__ */ jsx("span", {
661
+ className: cn("h-1.5 w-1.5 shrink-0 rounded-full", live ? "bg-[var(--surface-info-text)]" : "bg-muted-foreground"),
662
+ "aria-hidden": true,
663
+ "data-motion": live ? "essential" : void 0,
664
+ style: live ? ambientStyle : void 0,
665
+ onAnimationStart: syncAmbient
666
+ }),
667
+ /* @__PURE__ */ jsx("h3", {
668
+ className: "min-w-0 flex-1 truncate text-sm font-semibold",
669
+ children: agentName
670
+ }),
671
+ agentModel && /* @__PURE__ */ jsx("span", {
672
+ className: "truncate text-xs text-muted-foreground",
673
+ children: agentModel
674
+ }),
675
+ /* @__PURE__ */ jsx("button", {
676
+ type: "button",
677
+ "aria-label": "Close conversation",
678
+ title: "Close (Esc)",
679
+ onClick: () => select(void 0),
680
+ className: "shrink-0 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
681
+ children: /* @__PURE__ */ jsx(X, {
620
682
  "aria-hidden": true,
621
- "data-motion": live ? "essential" : void 0,
622
- style: live ? ambientStyle : void 0,
623
- onAnimationStart: syncAmbient
624
- }),
625
- /* @__PURE__ */ jsx("h3", {
626
- className: "min-w-0 flex-1 truncate text-sm font-semibold",
627
- children: agentName
628
- }),
629
- agentModel && /* @__PURE__ */ jsx("span", {
630
- className: "truncate text-xs text-muted-foreground",
631
- children: agentModel
632
- }),
633
- /* @__PURE__ */ jsx("button", {
634
- type: "button",
635
- "aria-label": "Close conversation",
636
- title: "Close (Esc)",
637
- onClick: () => select(void 0),
638
- className: "shrink-0 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
639
- children: /* @__PURE__ */ jsx(X, {
640
- "aria-hidden": true,
641
- className: "h-4 w-4"
642
- })
643
- })
644
- ]
645
- }),
646
- (facts.length > 0 || detail) && /* @__PURE__ */ jsxs("section", {
647
- "aria-label": "Recorded agent details",
648
- tabIndex: 0,
649
- className: "max-h-[35vh] shrink-0 overflow-y-auto border-b border-border px-3 py-2 text-sm",
650
- children: [facts.length > 0 && /* @__PURE__ */ jsx("dl", {
651
- className: "space-y-2",
652
- children: facts.map((fact, index) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
653
- className: "text-xs text-muted-foreground",
654
- children: fact.label
655
- }), /* @__PURE__ */ jsx("dd", {
656
- className: "m-0 whitespace-pre-wrap break-words",
657
- children: fact.value
658
- })] }, index))
659
- }), detail]
660
- }),
661
- /* @__PURE__ */ jsx("div", {
662
- ref: scroller,
663
- "data-primary-content": "",
664
- className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
665
- children: streamUrl && (cursor === null || origin !== void 0) ? /* @__PURE__ */ jsx(AgentTimeline, {
666
- items,
667
- isThinking: live && items.length === 0,
668
- emptyState: /* @__PURE__ */ jsx("p", {
669
- className: "p-4 text-sm text-muted-foreground",
670
- children: stream.error ?? "No messages yet."
683
+ className: "h-4 w-4"
671
684
  })
672
- }) : /* @__PURE__ */ jsx("p", {
685
+ })
686
+ ]
687
+ }),
688
+ (facts.length > 0 || detail) && /* @__PURE__ */ jsxs("section", {
689
+ "aria-label": "Recorded agent details",
690
+ tabIndex: 0,
691
+ className: "max-h-[35vh] shrink-0 overflow-y-auto border-b border-border px-3 py-2 text-sm",
692
+ children: [facts.length > 0 && /* @__PURE__ */ jsx("dl", {
693
+ className: "space-y-2",
694
+ children: facts.map((fact, index) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
695
+ className: "text-xs text-muted-foreground",
696
+ children: fact.label
697
+ }), /* @__PURE__ */ jsx("dd", {
698
+ className: "m-0 whitespace-pre-wrap break-words",
699
+ children: fact.value
700
+ })] }, index))
701
+ }), detail]
702
+ }),
703
+ commission && (commission.text || commission.by) && /* @__PURE__ */ jsxs("section", {
704
+ "aria-label": "Commission",
705
+ className: "shrink-0 border-b border-border bg-muted/40 px-3 py-2 text-sm",
706
+ children: [commission.by && /* @__PURE__ */ jsxs("p", {
707
+ className: "m-0 mb-1 text-xs text-muted-foreground",
708
+ children: ["commissioned by ", commission.by]
709
+ }), commission.text && /* @__PURE__ */ jsx("p", {
710
+ className: "m-0 line-clamp-6 whitespace-pre-wrap break-words",
711
+ children: commission.text
712
+ })]
713
+ }),
714
+ /* @__PURE__ */ jsx("div", {
715
+ ref: scroller,
716
+ "data-primary-content": "",
717
+ className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
718
+ children: streamUrl && (cursor === null || origin !== void 0) ? /* @__PURE__ */ jsx(AgentTimeline, {
719
+ items,
720
+ isThinking: live && items.length === 0,
721
+ emptyState: /* @__PURE__ */ jsx("p", {
673
722
  className: "p-4 text-sm text-muted-foreground",
674
- children: "Conversation: not time-indexed"
723
+ "data-stream": stream.error ? "reconnecting" : ended ? "ended" : "empty",
724
+ children: stream.error ? ended ? stream.error : `Reconnecting… ${stream.error}` : ended ? "The conversation ended." : "No messages yet."
675
725
  })
676
- }),
677
- unseen && /* @__PURE__ */ jsx("button", {
678
- type: "button",
679
- onClick: () => jump(),
680
- 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",
681
- children: "New output"
726
+ }) : /* @__PURE__ */ jsx("p", {
727
+ className: "p-4 text-sm text-muted-foreground",
728
+ children: "Conversation: not time-indexed"
682
729
  })
683
- ]
684
- })
685
- ]
730
+ }),
731
+ unseen && /* @__PURE__ */ jsx("button", {
732
+ type: "button",
733
+ onClick: () => jump(),
734
+ className: "absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full border border-border bg-background px-3 py-1 text-xs shadow-sm",
735
+ children: "New output"
736
+ })
737
+ ]
738
+ })]
686
739
  });
687
740
  }
688
741
  export { useEventStream as a, useAgentStream as i, LatestEvents as n, TraceWaterfall as o, foldAgentRows as r, traceSpanMark as s, TraceWorkspace as t };