@tangle-network/ui 7.0.0 → 8.1.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.
package/dist/hooks.d.ts CHANGED
@@ -5,7 +5,7 @@ import './message-BHWbxBtT.js';
5
5
  import './parts-dj7AcUg0.js';
6
6
  import './active-sessions-store-CeOmXgv5.js';
7
7
  import 'nanostores';
8
- import './tool-call-feed-Bs3MyQMT.js';
8
+ import './tool-call-feed-D9iofJgW.js';
9
9
  import 'react/jsx-runtime';
10
10
 
11
11
  /**
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ export { B as Button, a as ButtonProps, b as buttonVariants } from './button-CMQ
2
2
  export { Avatar, AvatarFallback, AvatarImage, Badge, BadgeProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, EmptyStateProps, InlineCode, InlineCodeProps, Input, InputProps, Label, Progress, SegmentedControl, SegmentedControlOption, SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarDropZone, SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, StatCard, StatCardProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, Textarea, TextareaProps, ThemeToggle, Toast, ToastContainer, ToastProvider, UploadFile, UploadProgress, UploadProgressProps, badgeVariants, useTheme, useToast } from './primitives.js';
3
3
  export { Logo, LogoProps, TangleKnot } from '@tangle-network/brand';
4
4
  export { A as ArtifactPane, a as ArtifactPaneProps } from './artifact-pane-DvJyPWV4.js';
5
- export { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineCustomItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, ChatContainer, ChatContainerProps, ChatInput, ChatInputProps, ChatMessage, ChatMessageProps, MessageList, MessageListProps, MessageRole, PendingFile, ThinkingIndicator, ThinkingIndicatorProps, UserMessage, UserMessageProps } from './chat.js';
6
- export { ExpandedToolDetail, ExpandedToolDetailProps, InlineThinkingItem, InlineThinkingItemProps, InlineToolItem, InlineToolItemProps, LiveDuration, RunGroup, RunGroupProps } from './run.js';
7
- export { F as FeedSegment, T as ToolCallData, a as ToolCallFeed, b as ToolCallFeedProps, c as ToolCallGroup, d as ToolCallGroupProps, e as ToolCallStatus, f as ToolCallStep, g as ToolCallStepProps, h as ToolCallType, p as parseToolEvent } from './tool-call-feed-Bs3MyQMT.js';
5
+ export { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineCustomItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, ChatContainer, ChatContainerProps, ChatMessage, ChatMessageProps, MessageList, MessageListProps, MessageRole, ThinkingIndicator, ThinkingIndicatorProps, UserMessage, UserMessageProps } from './chat.js';
6
+ export { AssistantRunShell, AssistantRunShellProps, ExpandedToolDetail, ExpandedToolDetailProps, InlineThinkingItem, InlineThinkingItemProps, InlineToolItem, InlineToolItemProps, LiveDuration, RunGroup, RunGroupProps } from './run.js';
7
+ export { F as FeedSegment, T as ToolCallData, a as ToolCallFeed, b as ToolCallFeedProps, c as ToolCallStatus, d as ToolCallType, p as parseToolEvent } from './tool-call-feed-D9iofJgW.js';
8
8
  export { OpenUIAction, OpenUIActionsNode, OpenUIArtifactRenderer, OpenUIArtifactRendererProps, OpenUIBadgeNode, OpenUICardNode, OpenUICodeNode, OpenUIComponentNode, OpenUIGridNode, OpenUIHeadingNode, OpenUIKeyValueNode, OpenUIMarkdownNode, OpenUIPrimitive, OpenUISeparatorNode, OpenUIStackNode, OpenUIStatNode, OpenUITableNode, OpenUITextNode } from './openui.js';
9
9
  export { FileArtifactPane, FileArtifactPaneProps, FileFormat, FileNode, FilePreview, FilePreviewProps, FileTabData, FileTabs, FileTabsProps, FileTree, FileTreeProps, FileTreeVisibilityOptions, RichFileTree, RichFileTreeGitEntry, RichFileTreeGitStatus, RichFileTreeProps, RichFileTreeThemeVars, detectFileFormat, fileExtension, filterFileTree, getCodeLanguage, getFormatLabel, getSyntaxLanguage } from './files.js';
10
10
  export { Markdown, MarkdownProps } from './markdown.js';
package/dist/index.js CHANGED
@@ -133,12 +133,11 @@ import {
133
133
  import {
134
134
  AgentTimeline,
135
135
  ChatContainer,
136
- ChatInput,
137
136
  ChatMessage,
138
137
  MessageList,
139
138
  ThinkingIndicator,
140
139
  UserMessage
141
- } from "./chunk-5CS3I7Y3.js";
140
+ } from "./chunk-QUAU6ZNC.js";
142
141
  import {
143
142
  useAutoScroll,
144
143
  useRunCollapseState,
@@ -150,15 +149,14 @@ import {
150
149
  parseToolEvent
151
150
  } from "./chunk-IWQZXL6A.js";
152
151
  import {
152
+ AssistantRunShell,
153
153
  InlineThinkingItem,
154
154
  RunGroup
155
- } from "./chunk-QIRVZMQY.js";
155
+ } from "./chunk-C3BIVG72.js";
156
156
  import {
157
157
  ExpandedToolDetail,
158
158
  InlineToolItem,
159
- LiveDuration,
160
- ToolCallGroup,
161
- ToolCallStep
159
+ LiveDuration
162
160
  } from "./chunk-RKQDBRTC.js";
163
161
  import {
164
162
  TOOL_CATEGORY_ICONS,
@@ -341,6 +339,7 @@ function RedactedDocument({
341
339
  export {
342
340
  AgentTimeline,
343
341
  ArtifactPane,
342
+ AssistantRunShell,
344
343
  AuthHeader,
345
344
  Avatar,
346
345
  AvatarFallback,
@@ -354,7 +353,6 @@ export {
354
353
  CardHeader,
355
354
  CardTitle,
356
355
  ChatContainer,
357
- ChatInput,
358
356
  ChatMessage,
359
357
  CodeBlock,
360
358
  CommandPreview,
@@ -453,8 +451,6 @@ export {
453
451
  ToastContainer,
454
452
  ToastProvider,
455
453
  ToolCallFeed,
456
- ToolCallGroup,
457
- ToolCallStep,
458
454
  UploadProgress,
459
455
  UserMenu,
460
456
  UserMessage,
package/dist/run.d.ts CHANGED
@@ -5,7 +5,7 @@ import { R as Run } from './run-PfLmDAox.js';
5
5
  import { S as SessionPart, a as ToolPart, R as ReasoningPart } from './parts-dj7AcUg0.js';
6
6
  import { AgentBranding } from './types.js';
7
7
  import { C as CustomToolRenderer } from './tool-display-z4JcDmMQ.js';
8
- export { F as FeedSegment, T as ToolCallData, a as ToolCallFeed, b as ToolCallFeedProps, c as ToolCallGroup, d as ToolCallGroupProps, e as ToolCallStatus, f as ToolCallStep, g as ToolCallStepProps, h as ToolCallType, p as parseToolEvent } from './tool-call-feed-Bs3MyQMT.js';
8
+ export { F as FeedSegment, T as ToolCallData, a as ToolCallFeed, b as ToolCallFeedProps, c as ToolCallStatus, d as ToolCallType, p as parseToolEvent } from './tool-call-feed-D9iofJgW.js';
9
9
  import './message-BHWbxBtT.js';
10
10
 
11
11
  interface RunGroupProps {
@@ -28,6 +28,34 @@ interface RunGroupProps {
28
28
  */
29
29
  declare const RunGroup: React.MemoExoticComponent<({ run, partMap, collapsed, onToggle, branding, renderToolDetail, headerActions, renderToolActions, }: RunGroupProps) => react_jsx_runtime.JSX.Element | null>;
30
30
 
31
+ interface AssistantRunShellProps {
32
+ /** Header label, e.g. the agent name or "Tools". */
33
+ label: string;
34
+ /** Terse stat line beside the label, e.g. "3 tools, 2s thinking". */
35
+ summary?: string;
36
+ /** One-line preview shown next to the label AND below the header when collapsed. */
37
+ collapsedPreview?: string;
38
+ /** Small trailing glyphs before the status pill (e.g. category badges). */
39
+ badges?: ReactNode;
40
+ /** Drives the status pill and header spinner. */
41
+ isStreaming?: boolean;
42
+ collapsed: boolean;
43
+ onToggle: () => void;
44
+ /** Actions rendered outside the collapse trigger, right of the header. */
45
+ headerActions?: ReactNode;
46
+ children: ReactNode;
47
+ className?: string;
48
+ }
49
+ /**
50
+ * The collapsible "assistant run" container shared by `RunGroup` (session-model
51
+ * driven) and `AgentTimeline` (declarative item list). Owns the header
52
+ * (label · summary · badges · status pill · chevron), the collapsed preview, and
53
+ * the Radix collapse — so both transcripts fold agent activity the same way and
54
+ * there is one implementation of a run, not two. It renders only chrome; callers
55
+ * pass the run body (tool rows, reasoning, text) as `children`.
56
+ */
57
+ declare function AssistantRunShell({ label, summary, collapsedPreview, badges, isStreaming, collapsed, onToggle, headerActions, children, className, }: AssistantRunShellProps): react_jsx_runtime.JSX.Element;
58
+
31
59
  interface InlineToolItemProps {
32
60
  part: ToolPart;
33
61
  renderToolDetail?: CustomToolRenderer;
@@ -70,4 +98,4 @@ declare function LiveDuration({ startTime }: {
70
98
  startTime: number;
71
99
  }): react_jsx_runtime.JSX.Element;
72
100
 
73
- export { ExpandedToolDetail, type ExpandedToolDetailProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, LiveDuration, RunGroup, type RunGroupProps };
101
+ export { AssistantRunShell, type AssistantRunShellProps, ExpandedToolDetail, type ExpandedToolDetailProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, LiveDuration, RunGroup, type RunGroupProps };
package/dist/run.js CHANGED
@@ -4,15 +4,14 @@ import {
4
4
  parseToolEvent
5
5
  } from "./chunk-IWQZXL6A.js";
6
6
  import {
7
+ AssistantRunShell,
7
8
  InlineThinkingItem,
8
9
  RunGroup
9
- } from "./chunk-QIRVZMQY.js";
10
+ } from "./chunk-C3BIVG72.js";
10
11
  import {
11
12
  ExpandedToolDetail,
12
13
  InlineToolItem,
13
- LiveDuration,
14
- ToolCallGroup,
15
- ToolCallStep
14
+ LiveDuration
16
15
  } from "./chunk-RKQDBRTC.js";
17
16
  import "./chunk-ULDNFLIM.js";
18
17
  import "./chunk-AAUNOHVL.js";
@@ -24,13 +23,12 @@ import "./chunk-FJBTCTZM.js";
24
23
  import "./chunk-WUQDUBJG.js";
25
24
  import "./chunk-RQHJBTEU.js";
26
25
  export {
26
+ AssistantRunShell,
27
27
  ExpandedToolDetail,
28
28
  InlineThinkingItem,
29
29
  InlineToolItem,
30
30
  LiveDuration,
31
31
  RunGroup,
32
32
  ToolCallFeed,
33
- ToolCallGroup,
34
- ToolCallStep,
35
33
  parseToolEvent
36
34
  };
@@ -4,7 +4,7 @@ import { R as Run, G as GroupedMessage } from './run-PfLmDAox.js';
4
4
  import { S as SessionMessage } from './message-BHWbxBtT.js';
5
5
  import { S as SessionPart } from './parts-dj7AcUg0.js';
6
6
  import { R as RegisterActiveSessionOptions, g as ActiveSessionTransportMode, c as ActiveSessionConnectionState } from './active-sessions-store-CeOmXgv5.js';
7
- import { F as FeedSegment } from './tool-call-feed-Bs3MyQMT.js';
7
+ import { F as FeedSegment } from './tool-call-feed-D9iofJgW.js';
8
8
  import 'nanostores';
9
9
  import 'react/jsx-runtime';
10
10
 
@@ -1,29 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
2
 
4
3
  type ToolCallType = "bash" | "read" | "write" | "edit" | "glob" | "grep" | "list" | "download" | "inspect" | "audit" | "unknown";
5
4
  type ToolCallStatus = "running" | "success" | "error";
6
- interface ToolCallStepProps {
7
- type: ToolCallType;
8
- label: string;
9
- status: ToolCallStatus;
10
- detail?: string;
11
- output?: string;
12
- /** Override syntax highlighting language; inferred from detail path if omitted */
13
- language?: string;
14
- duration?: number;
15
- className?: string;
16
- }
17
- declare function ToolCallStep({ type, label, status, detail, output, language, duration, className, }: ToolCallStepProps): react_jsx_runtime.JSX.Element;
18
- /**
19
- * ToolCallGroup — groups multiple tool calls under a heading.
20
- */
21
- interface ToolCallGroupProps {
22
- title?: string;
23
- children: ReactNode;
24
- className?: string;
25
- }
26
- declare function ToolCallGroup({ title, children, className }: ToolCallGroupProps): react_jsx_runtime.JSX.Element;
27
5
 
28
6
  interface ToolCallData {
29
7
  id: string;
@@ -65,4 +43,4 @@ declare function parseToolEvent(event: {
65
43
  data: Record<string, unknown>;
66
44
  }): ToolCallData | null;
67
45
 
68
- export { type FeedSegment as F, type ToolCallData as T, ToolCallFeed as a, type ToolCallFeedProps as b, ToolCallGroup as c, type ToolCallGroupProps as d, type ToolCallStatus as e, ToolCallStep as f, type ToolCallStepProps as g, type ToolCallType as h, parseToolEvent as p };
46
+ export { type FeedSegment as F, type ToolCallData as T, ToolCallFeed as a, type ToolCallFeedProps as b, type ToolCallStatus as c, type ToolCallType as d, parseToolEvent as p };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "7.0.0",
3
+ "version": "8.1.0",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -132,7 +132,7 @@
132
132
  "react": "^18 || ^19",
133
133
  "react-dom": "^18 || ^19",
134
134
  "react-router": "^7",
135
- "@tangle-network/brand": "^0.8.0"
135
+ "@tangle-network/brand": "^0.8.1"
136
136
  },
137
137
  "peerDependenciesMeta": {
138
138
  "@nanostores/react": {
@@ -1,4 +1,4 @@
1
- import { type KeyboardEvent, type ReactNode } from "react";
1
+ import { type KeyboardEvent, type ReactNode, useState } from "react";
2
2
  import {
3
3
  AlertTriangle,
4
4
  CheckCircle2,
@@ -12,6 +12,7 @@ import { Markdown } from "../markdown/markdown";
12
12
  import { ThinkingIndicator } from "./thinking-indicator";
13
13
  import { type ToolCallData } from "../run/tool-call-feed";
14
14
  import { ToolCallGroup, ToolCallStep } from "../run/tool-call-step";
15
+ import { AssistantRunShell } from "../run/assistant-run-shell";
15
16
 
16
17
  export type AgentTimelineTone = "default" | "info" | "success" | "warning" | "error";
17
18
 
@@ -78,6 +79,71 @@ export interface AgentTimelineProps {
78
79
  isThinking?: boolean;
79
80
  emptyState?: ReactNode;
80
81
  className?: string;
82
+ /**
83
+ * Fold consecutive tool / tool-group items into one collapsible run shell
84
+ * (the same `AssistantRunShell` `RunGroup` uses), so a burst of tool activity
85
+ * reads as a single toggleable step instead of a long ladder of rows.
86
+ * Default true; pass false for the flat one-row-per-tool timeline.
87
+ */
88
+ collapsibleToolRuns?: boolean;
89
+ /** Start collapsed tool runs open (true) or collapsed (false). Default open. */
90
+ defaultToolRunsOpen?: boolean;
91
+ }
92
+
93
+ /** A run of consecutive tool / tool-group items folded into one shell. */
94
+ interface ToolRunGroup {
95
+ id: string;
96
+ kind: "tool_run";
97
+ items: (AgentTimelineToolItem | AgentTimelineToolGroupItem)[];
98
+ }
99
+
100
+ type TimelineNode = AgentTimelineItem | ToolRunGroup;
101
+
102
+ function foldToolRuns(items: AgentTimelineItem[]): TimelineNode[] {
103
+ const nodes: TimelineNode[] = [];
104
+ let run: (AgentTimelineToolItem | AgentTimelineToolGroupItem)[] = [];
105
+
106
+ const flush = () => {
107
+ if (run.length === 0) return;
108
+ // A single tool stays a plain row; two or more fold into a collapsible run.
109
+ if (run.length === 1) {
110
+ nodes.push(run[0]);
111
+ } else {
112
+ nodes.push({ id: `tool-run-${run[0].id}`, kind: "tool_run", items: run });
113
+ }
114
+ run = [];
115
+ };
116
+
117
+ for (const item of items) {
118
+ if (item.kind === "tool" || item.kind === "tool_group") {
119
+ run.push(item);
120
+ } else {
121
+ flush();
122
+ nodes.push(item);
123
+ }
124
+ }
125
+ flush();
126
+ return nodes;
127
+ }
128
+
129
+ function countTools(group: ToolRunGroup): number {
130
+ return group.items.reduce(
131
+ (n, item) => n + (item.kind === "tool_group" ? item.calls.length : 1),
132
+ 0,
133
+ );
134
+ }
135
+
136
+ function ToolCallRow({ call }: { call: ToolCallData }) {
137
+ return (
138
+ <ToolCallStep
139
+ type={call.type}
140
+ label={call.label}
141
+ status={call.status}
142
+ detail={call.detail}
143
+ output={call.output}
144
+ duration={call.duration}
145
+ />
146
+ );
81
147
  }
82
148
 
83
149
  const TONE_STYLES: Record<AgentTimelineTone, { dot: string; card: string; text: string; icon: typeof Info }> = {
@@ -253,7 +319,17 @@ export function AgentTimeline({
253
319
  isThinking,
254
320
  emptyState,
255
321
  className,
322
+ collapsibleToolRuns = true,
323
+ defaultToolRunsOpen = true,
256
324
  }: AgentTimelineProps) {
325
+ // Collapse state for folded tool runs, keyed by run id. Absent → default.
326
+ const [collapsedRuns, setCollapsedRuns] = useState<Record<string, boolean>>({});
327
+ const toggleRun = (id: string) =>
328
+ setCollapsedRuns((prev) => ({
329
+ ...prev,
330
+ [id]: prev[id] === undefined ? defaultToolRunsOpen : !prev[id],
331
+ }));
332
+
257
333
  if (items.length === 0 && !isThinking) {
258
334
  return emptyState ? (
259
335
  <div className={cn("flex h-full items-center justify-center p-4", className)}>
@@ -266,92 +342,110 @@ export function AgentTimeline({
266
342
  ? [...items, { id: "__thinking__", kind: "custom", content: <ThinkingIndicator /> }]
267
343
  : items;
268
344
 
269
- // Determine which items participate in the timeline connector (non-user-message items)
270
- // User messages are rendered outside the timeline grid
271
- const timelineItems = renderedItems.filter((item) => !(item.kind === "message" && item.role === "user"));
345
+ const nodes: TimelineNode[] = collapsibleToolRuns
346
+ ? foldToolRuns(renderedItems)
347
+ : renderedItems;
348
+
349
+ // Non-user rows participate in the connector spine.
350
+ const timelineNodes = nodes.filter(
351
+ (node) => !(node.kind === "message" && node.role === "user"),
352
+ );
272
353
 
273
354
  return (
274
355
  <div className={cn("mx-auto w-full max-w-5xl px-4 py-4", className)}>
275
- {renderedItems.map((item, index) => {
356
+ {nodes.map((node) => {
276
357
  // User messages: right-aligned bubble, no connector
277
- if (item.kind === "message" && item.role === "user") {
278
- return <UserMessage key={item.id} item={item} />;
358
+ if (node.kind === "message" && node.role === "user") {
359
+ return <UserMessage key={node.id} item={node} />;
279
360
  }
280
361
 
281
- const timelineIndex = timelineItems.indexOf(item);
282
- const isLast = timelineIndex === timelineItems.length - 1;
362
+ const isLast = timelineNodes.indexOf(node) === timelineNodes.length - 1;
363
+
364
+ if (node.kind === "tool_run") {
365
+ const collapsed = collapsedRuns[node.id] ?? !defaultToolRunsOpen;
366
+ const total = countTools(node);
367
+ return (
368
+ <AgentTimelineRow key={node.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
369
+ <AssistantRunShell
370
+ label="Tools"
371
+ summary={`${total} ${total === 1 ? "tool" : "tools"}`}
372
+ collapsed={collapsed}
373
+ onToggle={() => toggleRun(node.id)}
374
+ >
375
+ <div className="space-y-px">
376
+ {node.items.map((item) =>
377
+ item.kind === "tool_group" ? (
378
+ <ToolCallGroup key={item.id} title={item.title}>
379
+ {item.calls.map((call) => (
380
+ <ToolCallRow key={call.id} call={call} />
381
+ ))}
382
+ </ToolCallGroup>
383
+ ) : (
384
+ <ToolCallRow key={item.id} call={item.call} />
385
+ ),
386
+ )}
387
+ </div>
388
+ </AssistantRunShell>
389
+ </AgentTimelineRow>
390
+ );
391
+ }
283
392
 
284
- if (item.kind === "message") {
393
+ if (node.kind === "message") {
285
394
  return (
286
- <AgentTimelineRow key={item.id} isLast={isLast} accentClassName="bg-[var(--brand-glow)]">
287
- <AssistantMessage item={item} />
395
+ <AgentTimelineRow key={node.id} isLast={isLast} accentClassName="bg-[var(--brand-glow)]">
396
+ <AssistantMessage item={node} />
288
397
  </AgentTimelineRow>
289
398
  );
290
399
  }
291
400
 
292
- if (item.kind === "tool") {
401
+ if (node.kind === "tool") {
293
402
  return (
294
- <AgentTimelineRow key={item.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
295
- <ToolCallStep
296
- type={item.call.type}
297
- label={item.call.label}
298
- status={item.call.status}
299
- detail={item.call.detail}
300
- output={item.call.output}
301
- duration={item.call.duration}
302
- />
403
+ <AgentTimelineRow key={node.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
404
+ <ToolCallRow call={node.call} />
303
405
  </AgentTimelineRow>
304
406
  );
305
407
  }
306
408
 
307
- if (item.kind === "tool_group") {
409
+ if (node.kind === "tool_group") {
308
410
  return (
309
- <AgentTimelineRow key={item.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
310
- <ToolCallGroup title={item.title}>
311
- {item.calls.map((call) => (
312
- <ToolCallStep
313
- key={call.id}
314
- type={call.type}
315
- label={call.label}
316
- status={call.status}
317
- detail={call.detail}
318
- output={call.output}
319
- duration={call.duration}
320
- />
411
+ <AgentTimelineRow key={node.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
412
+ <ToolCallGroup title={node.title}>
413
+ {node.calls.map((call) => (
414
+ <ToolCallRow key={call.id} call={call} />
321
415
  ))}
322
416
  </ToolCallGroup>
323
417
  </AgentTimelineRow>
324
418
  );
325
419
  }
326
420
 
327
- if (item.kind === "status") {
421
+ if (node.kind === "status") {
328
422
  return (
329
423
  <AgentTimelineRow
330
- key={item.id}
424
+ key={node.id}
331
425
  isLast={isLast}
332
- accentClassName={TONE_STYLES[item.tone ?? "default"].dot}
426
+ accentClassName={TONE_STYLES[node.tone ?? "default"].dot}
333
427
  >
334
- <StatusCard item={item} />
428
+ <StatusCard item={node} />
335
429
  </AgentTimelineRow>
336
430
  );
337
431
  }
338
432
 
339
- if (item.kind === "artifact") {
433
+ if (node.kind === "artifact") {
340
434
  return (
341
435
  <AgentTimelineRow
342
- key={item.id}
436
+ key={node.id}
343
437
  isLast={isLast}
344
- accentClassName={TONE_STYLES[item.tone ?? "default"].dot}
438
+ accentClassName={TONE_STYLES[node.tone ?? "default"].dot}
345
439
  >
346
- <ArtifactCard item={item} />
440
+ <ArtifactCard item={node} />
347
441
  </AgentTimelineRow>
348
442
  );
349
443
  }
350
444
 
351
445
  // custom
352
446
  return (
353
- <AgentTimelineRow key={item.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
354
- {(item as AgentTimelineCustomItem).content}
447
+ <AgentTimelineRow key={node.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
448
+ {(node as AgentTimelineCustomItem).content}
355
449
  </AgentTimelineRow>
356
450
  );
357
451
  })}
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  memo,
3
3
  type ReactNode,
4
- useCallback,
5
4
  useMemo,
6
5
  useRef,
7
6
  } from "react";
@@ -20,7 +19,6 @@ import {
20
19
  AgentTimeline,
21
20
  type AgentTimelineItem,
22
21
  } from "./agent-timeline";
23
- import { ChatInput, type PendingFile } from "./chat-input";
24
22
  import { InlineThinkingItem } from "../run/inline-thinking-item";
25
23
  import { getToolDisplayMetadata } from "../utils/tool-display";
26
24
  import {
@@ -29,27 +27,21 @@ import {
29
27
  type OpenUIComponentNode,
30
28
  } from "../openui/openui-artifact-renderer";
31
29
 
30
+ /**
31
+ * Transcript-only container: message list + auto-scroll. Composers are
32
+ * composed BELOW this by the app (the canonical one is `AgentComposer` in
33
+ * `@tangle-network/sandbox-ui`) — this component never renders an input.
34
+ */
32
35
  export interface ChatContainerProps {
33
36
  messages: SessionMessage[];
34
37
  partMap: Record<string, SessionPart[]>;
35
38
  isStreaming: boolean;
36
- onSend?: (text: string) => void;
37
- onCancel?: () => void;
38
39
  branding?: AgentBranding;
39
- placeholder?: string;
40
40
  className?: string;
41
- /** Hide the input area (useful for read-only views). */
42
- hideInput?: boolean;
43
41
  /** Custom renderer for tool details. Return ReactNode to override, null to use default. */
44
42
  renderToolDetail?: CustomToolRenderer;
45
43
  /** Presentation mode for the session view. */
46
44
  presentation?: "runs" | "timeline";
47
- modelLabel?: string;
48
- onModelClick?: () => void;
49
- pendingFiles?: PendingFile[];
50
- onRemoveFile?: (id: string) => void;
51
- onAttach?: (files: FileList) => void;
52
- disabled?: boolean;
53
45
  /** Callback when an OpenUI action button is pressed within inline OpenUI blocks. */
54
46
  onOpenUIAction?: (action: OpenUIAction) => void;
55
47
  /** Enable rendering OpenUI schemas inline in the chat timeline. Defaults to true. */
@@ -355,7 +347,7 @@ function buildTimelineItems(
355
347
  }
356
348
 
357
349
  /**
358
- * Full chat container: message list + auto-scroll + input area.
350
+ * Chat transcript container: message list + auto-scroll.
359
351
  * Orchestrates useRunGroups, useRunCollapseState, and useAutoScroll.
360
352
  */
361
353
  export const ChatContainer = memo(
@@ -363,20 +355,10 @@ export const ChatContainer = memo(
363
355
  messages,
364
356
  partMap,
365
357
  isStreaming,
366
- onSend,
367
- onCancel,
368
358
  branding,
369
- placeholder = "Type a message...",
370
359
  className,
371
- hideInput = false,
372
360
  renderToolDetail,
373
361
  presentation = "runs",
374
- modelLabel,
375
- onModelClick,
376
- pendingFiles,
377
- onRemoveFile,
378
- onAttach,
379
- disabled = false,
380
362
  onOpenUIAction,
381
363
  enableOpenUI = true,
382
364
  renderRunActions,
@@ -404,13 +386,6 @@ export const ChatContainer = memo(
404
386
  [messages, partMap, isStreaming, onOpenUIAction, enableOpenUI],
405
387
  );
406
388
 
407
- const handleSend = useCallback(
408
- (text: string) => {
409
- onSend?.(text);
410
- },
411
- [onSend],
412
- );
413
-
414
389
  return (
415
390
  <div className={cn("flex h-full flex-col", className)}>
416
391
  {/* Message area */}
@@ -462,23 +437,6 @@ export const ChatContainer = memo(
462
437
  </button>
463
438
  </div>
464
439
  )}
465
-
466
- {/* Input area */}
467
- {!hideInput && onSend && (
468
- <ChatInput
469
- onSend={handleSend}
470
- onCancel={onCancel}
471
- isStreaming={isStreaming}
472
- placeholder={placeholder}
473
- modelLabel={modelLabel}
474
- onModelClick={onModelClick}
475
- pendingFiles={pendingFiles}
476
- onRemoveFile={onRemoveFile}
477
- onAttach={onAttach}
478
- disabled={disabled}
479
- className="shrink-0 border-t border-border bg-background"
480
- />
481
- )}
482
440
  </div>
483
441
  );
484
442
  },
@@ -27,10 +27,6 @@ export interface ChatMessageProps {
27
27
  assistantLabel?: string;
28
28
  /** Hide the role label row entirely */
29
29
  hideRoleLabel?: boolean;
30
- /** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
31
- hideAvatar?: boolean;
32
- /** @deprecated Avatars were removed from the bubble design; this prop is ignored. */
33
- avatar?: ReactNode;
34
30
  }
35
31
 
36
32
  export function ChatMessage({
package/src/chat/index.ts CHANGED
@@ -2,7 +2,6 @@ export { ChatContainer, type ChatContainerProps } from "./chat-container";
2
2
  export { MessageList, type MessageListProps } from "./message-list";
3
3
  export { UserMessage, type UserMessageProps } from "./user-message";
4
4
  export { ChatMessage, type ChatMessageProps, type MessageRole } from "./chat-message";
5
- export { ChatInput, type ChatInputProps, type PendingFile } from "./chat-input";
6
5
  export { ThinkingIndicator, type ThinkingIndicatorProps } from "./thinking-indicator";
7
6
  export {
8
7
  AgentTimeline,
@@ -39,7 +39,7 @@ pnpm add @tangle/sandbox-ui
39
39
  Import the components you need:
40
40
 
41
41
  \`\`\`tsx
42
- import { ChatInput, DropZone, UploadProgress } from '@tangle/sandbox-ui'
42
+ import { AgentComposer, DropZone, UploadProgress } from '@tangle-network/sandbox-ui'
43
43
  \`\`\`
44
44
 
45
45
  > **Note:** Components assume a Tailwind CSS v4 setup with the design tokens