@townco/ui 0.1.78 → 0.1.79

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.
@@ -73,6 +73,8 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
73
73
  compactionAction?: "compacted" | "truncated" | undefined;
74
74
  originalTokens?: number | undefined;
75
75
  finalTokens?: number | undefined;
76
+ originalContentPreview?: string | undefined;
77
+ originalContentPath?: string | undefined;
76
78
  } | undefined;
77
79
  subagentPort?: number | undefined;
78
80
  subagentSessionId?: string | undefined;
@@ -163,7 +165,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
163
165
  id: string;
164
166
  hookType: "context_size" | "tool_response";
165
167
  callback: string;
166
- status: "error" | "completed" | "triggered";
168
+ status: "completed" | "error" | "triggered";
167
169
  threshold?: number | undefined;
168
170
  currentPercentage?: number | undefined;
169
171
  metadata?: {
@@ -71,6 +71,8 @@ export declare function useToolCalls(client: AcpClient | null): {
71
71
  compactionAction?: "compacted" | "truncated" | undefined;
72
72
  originalTokens?: number | undefined;
73
73
  finalTokens?: number | undefined;
74
+ originalContentPreview?: string | undefined;
75
+ originalContentPath?: string | undefined;
74
76
  } | undefined;
75
77
  subagentPort?: number | undefined;
76
78
  subagentSessionId?: string | undefined;
@@ -22,8 +22,8 @@ export declare const HookNotificationDisplay: z.ZodObject<{
22
22
  }>;
23
23
  callback: z.ZodString;
24
24
  status: z.ZodEnum<{
25
- error: "error";
26
25
  completed: "completed";
26
+ error: "error";
27
27
  triggered: "triggered";
28
28
  }>;
29
29
  threshold: z.ZodOptional<z.ZodNumber>;
@@ -142,6 +142,8 @@ export declare const DisplayMessage: z.ZodObject<{
142
142
  }>>;
143
143
  originalTokens: z.ZodOptional<z.ZodNumber>;
144
144
  finalTokens: z.ZodOptional<z.ZodNumber>;
145
+ originalContentPreview: z.ZodOptional<z.ZodString>;
146
+ originalContentPath: z.ZodOptional<z.ZodString>;
145
147
  }, z.core.$strip>>;
146
148
  subagentPort: z.ZodOptional<z.ZodNumber>;
147
149
  subagentSessionId: z.ZodOptional<z.ZodString>;
@@ -246,8 +248,8 @@ export declare const DisplayMessage: z.ZodObject<{
246
248
  }>;
247
249
  callback: z.ZodString;
248
250
  status: z.ZodEnum<{
249
- error: "error";
250
251
  completed: "completed";
252
+ error: "error";
251
253
  triggered: "triggered";
252
254
  }>;
253
255
  threshold: z.ZodOptional<z.ZodNumber>;
@@ -394,6 +396,8 @@ export declare const ChatSessionState: z.ZodObject<{
394
396
  }>>;
395
397
  originalTokens: z.ZodOptional<z.ZodNumber>;
396
398
  finalTokens: z.ZodOptional<z.ZodNumber>;
399
+ originalContentPreview: z.ZodOptional<z.ZodString>;
400
+ originalContentPath: z.ZodOptional<z.ZodString>;
397
401
  }, z.core.$strip>>;
398
402
  subagentPort: z.ZodOptional<z.ZodNumber>;
399
403
  subagentSessionId: z.ZodOptional<z.ZodString>;
@@ -498,8 +502,8 @@ export declare const ChatSessionState: z.ZodObject<{
498
502
  }>;
499
503
  callback: z.ZodString;
500
504
  status: z.ZodEnum<{
501
- error: "error";
502
505
  completed: "completed";
506
+ error: "error";
503
507
  triggered: "triggered";
504
508
  }>;
505
509
  threshold: z.ZodOptional<z.ZodNumber>;
@@ -348,6 +348,8 @@ export declare const ToolCallSchema: z.ZodObject<{
348
348
  }>>;
349
349
  originalTokens: z.ZodOptional<z.ZodNumber>;
350
350
  finalTokens: z.ZodOptional<z.ZodNumber>;
351
+ originalContentPreview: z.ZodOptional<z.ZodString>;
352
+ originalContentPath: z.ZodOptional<z.ZodString>;
351
353
  }, z.core.$strip>>;
352
354
  subagentPort: z.ZodOptional<z.ZodNumber>;
353
355
  subagentSessionId: z.ZodOptional<z.ZodString>;
@@ -594,6 +596,17 @@ export declare const ToolCallUpdateSchema: z.ZodObject<{
594
596
  }, z.core.$strip>], "type">>>;
595
597
  isStreaming: z.ZodOptional<z.ZodBoolean>;
596
598
  }, z.core.$strip>>>;
599
+ _meta: z.ZodOptional<z.ZodObject<{
600
+ truncationWarning: z.ZodOptional<z.ZodString>;
601
+ compactionAction: z.ZodOptional<z.ZodEnum<{
602
+ compacted: "compacted";
603
+ truncated: "truncated";
604
+ }>>;
605
+ originalTokens: z.ZodOptional<z.ZodNumber>;
606
+ finalTokens: z.ZodOptional<z.ZodNumber>;
607
+ originalContentPreview: z.ZodOptional<z.ZodString>;
608
+ originalContentPath: z.ZodOptional<z.ZodString>;
609
+ }, z.core.$strip>>;
597
610
  }, z.core.$strip>;
598
611
  export type ToolCallUpdate = z.infer<typeof ToolCallUpdateSchema>;
599
612
  /**
@@ -171,6 +171,8 @@ export const ToolCallSchema = z.object({
171
171
  compactionAction: z.enum(["compacted", "truncated"]).optional(),
172
172
  originalTokens: z.number().optional(),
173
173
  finalTokens: z.number().optional(),
174
+ originalContentPreview: z.string().optional(),
175
+ originalContentPath: z.string().optional(),
174
176
  })
175
177
  .optional(),
176
178
  /** Sub-agent HTTP port for direct SSE connection (Task tool only) */
@@ -205,6 +207,17 @@ export const ToolCallUpdateSchema = z.object({
205
207
  subagentSessionId: z.string().optional(),
206
208
  /** Sub-agent messages for replay */
207
209
  subagentMessages: z.array(SubagentMessageSchema).optional(),
210
+ /** Internal metadata (e.g., compaction info) */
211
+ _meta: z
212
+ .object({
213
+ truncationWarning: z.string().optional(),
214
+ compactionAction: z.enum(["compacted", "truncated"]).optional(),
215
+ originalTokens: z.number().optional(),
216
+ finalTokens: z.number().optional(),
217
+ originalContentPreview: z.string().optional(),
218
+ originalContentPath: z.string().optional(),
219
+ })
220
+ .optional(),
208
221
  });
209
222
  /**
210
223
  * Helper to merge a tool call update into an existing tool call
@@ -232,6 +245,8 @@ export function mergeToolCallUpdate(existing, update) {
232
245
  subagentSessionId: update.subagentSessionId ?? existing.subagentSessionId,
233
246
  // Sub-agent messages for replay
234
247
  subagentMessages: update.subagentMessages ?? existing.subagentMessages,
248
+ // Internal metadata (compaction info)
249
+ _meta: update._meta ?? existing._meta,
235
250
  };
236
251
  return merged;
237
252
  }
@@ -1,7 +1,7 @@
1
1
  import { type VariantProps } from "class-variance-authority";
2
2
  import * as React from "react";
3
3
  declare const buttonVariants: (props?: ({
4
- variant?: "default" | "link" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
4
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
5
5
  size?: "default" | "icon" | "sm" | "lg" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -0,0 +1,9 @@
1
+ import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
2
+ export interface InvokingGroupProps {
3
+ toolCalls: ToolCallType[];
4
+ }
5
+ /**
6
+ * InvokingGroup component - displays a group of preliminary (invoking) tool calls
7
+ * Shows as "Invoking parallel operation (N)" with a summary of unique tool names
8
+ */
9
+ export declare function InvokingGroup({ toolCalls }: InvokingGroupProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ListVideo } from "lucide-react";
3
+ import React from "react";
4
+ /**
5
+ * InvokingGroup component - displays a group of preliminary (invoking) tool calls
6
+ * Shows as "Invoking parallel operation (N)" with a summary of unique tool names
7
+ */
8
+ export function InvokingGroup({ toolCalls }) {
9
+ // Get unique display names for the summary
10
+ const displayNames = toolCalls.map((tc) => tc.prettyName || tc.title);
11
+ const uniqueNames = [...new Set(displayNames)];
12
+ const summary = uniqueNames.length <= 2
13
+ ? uniqueNames.join(", ")
14
+ : `${uniqueNames.slice(0, 2).join(", ")} +${uniqueNames.length - 2} more`;
15
+ return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-paragraph-sm text-muted-foreground/50", children: [_jsx(ListVideo, { className: "h-3 w-3" }), _jsx("span", { children: "Invoking parallel operation" }), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-muted-foreground/50", children: toolCalls.length })] }), _jsx("span", { className: "text-paragraph-sm text-muted-foreground/50 pl-4.5", children: summary })] }));
16
+ }
@@ -0,0 +1,23 @@
1
+ export interface SubagentStreamProps {
2
+ /** Sub-agent HTTP port */
3
+ port: number;
4
+ /** Sub-agent session ID */
5
+ sessionId: string;
6
+ /** Optional host (defaults to localhost) */
7
+ host?: string;
8
+ /** Parent tool call status - use this to determine if sub-agent is running */
9
+ parentStatus?: "pending" | "in_progress" | "completed" | "failed";
10
+ /** Sub-agent name (for display) */
11
+ agentName?: string | undefined;
12
+ /** Query sent to the sub-agent */
13
+ query?: string | undefined;
14
+ }
15
+ /**
16
+ * SubagentStream component - displays streaming content from a sub-agent.
17
+ *
18
+ * This component:
19
+ * - Connects directly to the sub-agent's SSE endpoint
20
+ * - Displays streaming text and tool calls
21
+ * - Renders in a collapsible section (collapsed by default)
22
+ */
23
+ export declare function SubagentStream({ port, sessionId, host, parentStatus, agentName, query, }: SubagentStreamProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,98 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronDown, CircleDot, Loader2 } from "lucide-react";
3
+ import React, { useCallback, useEffect, useRef, useState } from "react";
4
+ import { useSubagentStream } from "../../core/hooks/use-subagent-stream.js";
5
+ const SCROLL_THRESHOLD = 50; // px from bottom to consider "at bottom"
6
+ /**
7
+ * SubagentStream component - displays streaming content from a sub-agent.
8
+ *
9
+ * This component:
10
+ * - Connects directly to the sub-agent's SSE endpoint
11
+ * - Displays streaming text and tool calls
12
+ * - Renders in a collapsible section (collapsed by default)
13
+ */
14
+ export function SubagentStream({ port, sessionId, host, parentStatus, agentName, query, }) {
15
+ const [isExpanded, setIsExpanded] = useState(false); // Start collapsed for parallel ops
16
+ const [isThinkingExpanded, setIsThinkingExpanded] = useState(true);
17
+ const [isNearBottom, setIsNearBottom] = useState(true);
18
+ const thinkingContainerRef = useRef(null);
19
+ const { messages, isStreaming: hookIsStreaming, error } = useSubagentStream({
20
+ port,
21
+ sessionId,
22
+ ...(host !== undefined ? { host } : {}),
23
+ });
24
+ // Use parent status as primary indicator, fall back to hook's streaming state
25
+ // Parent is "in_progress" means sub-agent is definitely still running
26
+ const isRunning = parentStatus === "in_progress" || parentStatus === "pending" || hookIsStreaming;
27
+ // Get the current/latest message
28
+ const currentMessage = messages[messages.length - 1];
29
+ const hasContent = currentMessage &&
30
+ (currentMessage.content ||
31
+ (currentMessage.toolCalls && currentMessage.toolCalls.length > 0));
32
+ // Auto-collapse Thinking when completed (so Output is the primary view)
33
+ const prevIsRunningRef = useRef(isRunning);
34
+ useEffect(() => {
35
+ if (prevIsRunningRef.current && !isRunning) {
36
+ // Just completed - collapse thinking to show output
37
+ setIsThinkingExpanded(false);
38
+ }
39
+ prevIsRunningRef.current = isRunning;
40
+ }, [isRunning]);
41
+ // Check if user is near bottom of scroll area
42
+ const checkScrollPosition = useCallback(() => {
43
+ const container = thinkingContainerRef.current;
44
+ if (!container)
45
+ return;
46
+ const { scrollTop, scrollHeight, clientHeight } = container;
47
+ const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
48
+ setIsNearBottom(distanceFromBottom < SCROLL_THRESHOLD);
49
+ }, []);
50
+ // Scroll to bottom
51
+ const scrollToBottom = useCallback(() => {
52
+ const container = thinkingContainerRef.current;
53
+ if (!container)
54
+ return;
55
+ container.scrollTop = container.scrollHeight;
56
+ }, []);
57
+ // Auto-scroll when content changes and user is near bottom
58
+ useEffect(() => {
59
+ if (isNearBottom && (isRunning || hasContent)) {
60
+ scrollToBottom();
61
+ }
62
+ }, [currentMessage?.content, currentMessage?.toolCalls, isNearBottom, isRunning, hasContent, scrollToBottom]);
63
+ // Set up scroll listener
64
+ useEffect(() => {
65
+ const container = thinkingContainerRef.current;
66
+ if (!container)
67
+ return;
68
+ const handleScroll = () => checkScrollPosition();
69
+ container.addEventListener("scroll", handleScroll, { passive: true });
70
+ checkScrollPosition(); // Check initial position
71
+ return () => container.removeEventListener("scroll", handleScroll);
72
+ }, [checkScrollPosition, isThinkingExpanded, isExpanded]);
73
+ // Get last line of streaming content for preview
74
+ const lastLine = currentMessage?.content
75
+ ? currentMessage.content.split("\n").filter(Boolean).pop() || ""
76
+ : "";
77
+ const previewText = lastLine.length > 100 ? `${lastLine.slice(0, 100)}...` : lastLine;
78
+ return (_jsxs("div", { children: [!isExpanded && (_jsx("button", { type: "button", onClick: () => setIsExpanded(true), className: "w-full max-w-md text-left cursor-pointer bg-transparent border-none p-0", children: previewText ? (_jsx("p", { className: `text-paragraph-sm text-muted-foreground truncate ${isRunning ? "animate-pulse" : ""}`, children: previewText })) : isRunning ? (_jsx("p", { className: "text-paragraph-sm text-muted-foreground/50 italic animate-pulse", children: "Waiting for response..." })) : null })), isExpanded && (_jsxs("div", { className: "space-y-3", children: [(agentName || query) && (_jsxs("div", { children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsxs("div", { className: "text-[11px] font-mono space-y-1", children: [agentName && (_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "agentName: " }), _jsx("span", { className: "text-foreground", children: agentName })] })), query && (_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "query: " }), _jsx("span", { className: "text-foreground", children: query })] }))] })] })), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setIsThinkingExpanded(!isThinkingExpanded), className: "flex items-center gap-2 cursor-pointer bg-transparent border-none p-0 text-left group", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider font-sans", children: "Thinking" }), _jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground/70 transition-transform duration-200 ${isThinkingExpanded ? "rotate-180" : ""}` })] }), isThinkingExpanded && (_jsxs("div", { ref: thinkingContainerRef, className: "mt-2 rounded-md overflow-hidden bg-muted/30 border border-border/50 max-h-[200px] overflow-y-auto", children: [error && (_jsxs("div", { className: "px-2 py-2 text-[11px] text-destructive", children: ["Error: ", error] })), !error && !hasContent && isRunning && (_jsx("div", { className: "px-2 py-2 text-[11px] text-muted-foreground", children: "Waiting for sub-agent response..." })), currentMessage && (_jsxs("div", { className: "px-2 py-2 space-y-2", children: [currentMessage.toolCalls &&
79
+ currentMessage.toolCalls.length > 0 && (_jsx("div", { className: "space-y-1", children: currentMessage.toolCalls.map((tc) => (_jsx(SubagentToolCallItem, { toolCall: tc }, tc.id))) })), currentMessage.content && (_jsxs("div", { className: "text-[11px] text-foreground whitespace-pre-wrap font-mono", children: [currentMessage.content, currentMessage.isStreaming && (_jsx("span", { className: "inline-block w-1.5 h-3 bg-primary/70 ml-0.5 animate-pulse" }))] }))] }))] }))] }), !isRunning && currentMessage?.content && (_jsxs("div", { children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Output" }), _jsx("div", { className: "text-[11px] text-foreground whitespace-pre-wrap font-mono max-h-[200px] overflow-y-auto rounded-md bg-muted/30 border border-border/50 px-2 py-2", children: currentMessage.content })] }))] }))] }));
80
+ }
81
+ /**
82
+ * Simple tool call display for sub-agent tool calls
83
+ */
84
+ function SubagentToolCallItem({ toolCall }) {
85
+ const statusIcon = {
86
+ pending: "...",
87
+ in_progress: "",
88
+ completed: "",
89
+ failed: "",
90
+ }[toolCall.status];
91
+ const statusColor = {
92
+ pending: "text-muted-foreground",
93
+ in_progress: "text-blue-500",
94
+ completed: "text-green-500",
95
+ failed: "text-destructive",
96
+ }[toolCall.status];
97
+ return (_jsxs("div", { className: "flex items-center gap-2 text-[10px] text-muted-foreground", children: [_jsx("span", { className: statusColor, children: statusIcon }), _jsx("span", { className: "font-medium", children: toolCall.prettyName || toolCall.title }), toolCall.status === "in_progress" && (_jsx(Loader2, { className: "h-2.5 w-2.5 animate-spin" }))] }));
98
+ }
@@ -0,0 +1,8 @@
1
+ import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
2
+ export interface ToolCallProps {
3
+ toolCall: ToolCallType;
4
+ }
5
+ /**
6
+ * ToolCall component - displays a single tool call with collapsible details
7
+ */
8
+ export declare function ToolCall({ toolCall }: ToolCallProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,234 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import JsonView from "@uiw/react-json-view";
3
+ import { AlertCircle, CheckSquare, ChevronDown, ChevronRight, CircleDot, Cloud, Edit, FileText, Globe, Image, Link, Search, Wrench, } from "lucide-react";
4
+ import React, { useState } from "react";
5
+ import { ChatLayout } from "./index.js";
6
+ import { SubAgentDetails } from "./SubAgentDetails.js";
7
+ import { useTheme } from "./ThemeProvider.js";
8
+ /**
9
+ * Map of icon names to Lucide components
10
+ */
11
+ const ICON_MAP = {
12
+ Globe: Globe,
13
+ Image: Image,
14
+ Link: Link,
15
+ Cloud: Cloud,
16
+ CheckSquare: CheckSquare,
17
+ Search: Search,
18
+ FileText: FileText,
19
+ Edit: Edit,
20
+ Wrench: Wrench,
21
+ CircleDot: CircleDot,
22
+ };
23
+ /**
24
+ * Tool call kind icons (using emoji for simplicity)
25
+ */
26
+ const _kindIcons = {
27
+ read: "\u{1F4C4}",
28
+ edit: "\u{270F}\u{FE0F}",
29
+ delete: "\u{1F5D1}\u{FE0F}",
30
+ move: "\u{1F4E6}",
31
+ search: "\u{1F50D}",
32
+ execute: "\u{2699}\u{FE0F}",
33
+ think: "\u{1F4AD}",
34
+ fetch: "\u{1F310}",
35
+ switch_mode: "\u{1F501}",
36
+ other: "\u{1F527}",
37
+ };
38
+ /**
39
+ * ToolCall component - displays a single tool call with collapsible details
40
+ */
41
+ export function ToolCall({ toolCall }) {
42
+ const [isExpanded, setIsExpanded] = useState(false);
43
+ const [isSubagentExpanded, setIsSubagentExpanded] = useState(false);
44
+ const { resolvedTheme } = useTheme();
45
+ // Detect TodoWrite tool and subagent
46
+ const isTodoWrite = toolCall.title === "todo_write";
47
+ // A subagent call can be detected by:
48
+ // - Live: has port and sessionId (but no stored messages yet)
49
+ // - Replay: has stored subagentMessages
50
+ const hasLiveSubagent = !!(toolCall.subagentPort && toolCall.subagentSessionId);
51
+ const hasStoredSubagent = !!(toolCall.subagentMessages && toolCall.subagentMessages.length > 0);
52
+ const isSubagentCall = hasLiveSubagent || hasStoredSubagent;
53
+ // Use replay mode if we have stored messages - they should take precedence
54
+ // over trying to connect to SSE (which won't work for replayed sessions)
55
+ const isReplaySubagent = hasStoredSubagent;
56
+ // Safely access ChatLayout context - will be undefined if not within ChatLayout
57
+ const layoutContext = React.useContext(ChatLayout.Context);
58
+ // Click handler: toggle sidepanel for TodoWrite, subagent details for subagents, expand for others
59
+ const handleHeaderClick = React.useCallback(() => {
60
+ if (isTodoWrite && layoutContext) {
61
+ // Toggle sidepanel - close if already open on todo tab, otherwise open
62
+ if (layoutContext.panelSize !== "hidden" &&
63
+ layoutContext.activeTab === "todo") {
64
+ layoutContext.setPanelSize("hidden");
65
+ }
66
+ else {
67
+ layoutContext.setPanelSize("small");
68
+ layoutContext.setActiveTab("todo");
69
+ }
70
+ }
71
+ else if (isSubagentCall) {
72
+ // Toggle subagent details
73
+ setIsSubagentExpanded(!isSubagentExpanded);
74
+ }
75
+ else {
76
+ // Normal expand/collapse
77
+ setIsExpanded(!isExpanded);
78
+ }
79
+ }, [
80
+ isTodoWrite,
81
+ layoutContext,
82
+ isExpanded,
83
+ isSubagentCall,
84
+ isSubagentExpanded,
85
+ ]);
86
+ // Determine which icon to show
87
+ const IconComponent = toolCall.icon && ICON_MAP[toolCall.icon]
88
+ ? ICON_MAP[toolCall.icon]
89
+ : CircleDot;
90
+ // Determine display name
91
+ const displayName = toolCall.prettyName || toolCall.title;
92
+ // Determine icon color based on status (especially for subagents)
93
+ const isSubagentRunning = isSubagentCall &&
94
+ (toolCall.status === "in_progress" || toolCall.status === "pending");
95
+ const isSubagentFailed = isSubagentCall && toolCall.status === "failed";
96
+ const iconColorClass = isSubagentCall
97
+ ? isSubagentFailed
98
+ ? "text-destructive"
99
+ : isSubagentRunning
100
+ ? "text-foreground animate-pulse"
101
+ : "text-green-500"
102
+ : "text-muted-foreground";
103
+ const statusTooltip = isSubagentCall
104
+ ? isSubagentFailed
105
+ ? "Sub-agent failed"
106
+ : isSubagentRunning
107
+ ? "Sub-agent running"
108
+ : "Sub-agent completed"
109
+ : undefined;
110
+ // Check if there's an error
111
+ const hasError = toolCall.status === "failed" || !!toolCall.error;
112
+ // Check if this is a preliminary (pending) tool call without full details yet
113
+ const isPreliminary = toolCall.status === "pending" &&
114
+ (!toolCall.rawInput || Object.keys(toolCall.rawInput).length === 0);
115
+ // JSON View style based on theme
116
+ const jsonStyle = {
117
+ fontSize: "11px",
118
+ backgroundColor: "transparent",
119
+ fontFamily: "inherit",
120
+ "--w-rjv-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
121
+ "--w-rjv-key-string": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
122
+ "--w-rjv-background-color": "transparent",
123
+ "--w-rjv-line-color": resolvedTheme === "dark" ? "#27272a" : "#e4e4e7",
124
+ "--w-rjv-arrow-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
125
+ "--w-rjv-edit-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
126
+ "--w-rjv-info-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
127
+ "--w-rjv-update-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
128
+ "--w-rjv-copied-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
129
+ "--w-rjv-copied-success-color": resolvedTheme === "dark" ? "#22c55e" : "#16a34a",
130
+ "--w-rjv-curlybraces-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
131
+ "--w-rjv-colon-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
132
+ "--w-rjv-brackets-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
133
+ "--w-rjv-quotes-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
134
+ "--w-rjv-quotes-string-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
135
+ "--w-rjv-type-string-color": resolvedTheme === "dark" ? "#22c55e" : "#16a34a",
136
+ "--w-rjv-type-int-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
137
+ "--w-rjv-type-float-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
138
+ "--w-rjv-type-bigint-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
139
+ "--w-rjv-type-boolean-color": resolvedTheme === "dark" ? "#3b82f6" : "#2563eb",
140
+ "--w-rjv-type-date-color": resolvedTheme === "dark" ? "#ec4899" : "#db2777",
141
+ "--w-rjv-type-url-color": resolvedTheme === "dark" ? "#3b82f6" : "#2563eb",
142
+ "--w-rjv-type-null-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
143
+ "--w-rjv-type-nan-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
144
+ "--w-rjv-type-undefined-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
145
+ };
146
+ // Preliminary tool calls show as simple light gray text without expansion
147
+ if (isPreliminary) {
148
+ return (_jsx("div", { className: "flex flex-col my-4", children: _jsxs("span", { className: "text-paragraph-sm text-muted-foreground/50", children: ["Invoking ", displayName] }) }));
149
+ }
150
+ return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsxs("button", { type: "button", className: "flex flex-col items-start gap-0.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: handleHeaderClick, "aria-expanded": isTodoWrite ? undefined : isExpanded, children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [_jsx("div", { className: iconColorClass, title: statusTooltip, children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-muted-foreground", children: displayName }), hasError && _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }), isTodoWrite ? (_jsx(ChevronRight, { className: "h-3 w-3 text-muted-foreground/70" })) : (_jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground/70 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` }))] }), toolCall.subline && (_jsx("span", { className: "text-paragraph-sm text-muted-foreground/70 pl-4.5", children: toolCall.subline }))] }), !isTodoWrite && isSubagentCall && (_jsx("div", { className: "pl-4.5", children: _jsx(SubAgentDetails, { port: toolCall.subagentPort, sessionId: toolCall.subagentSessionId, parentStatus: toolCall.status, agentName: toolCall.rawInput?.agentName, query: toolCall.rawInput?.query, isExpanded: isSubagentExpanded, onExpandChange: setIsSubagentExpanded, storedMessages: toolCall.subagentMessages, isReplay: isReplaySubagent }) })), !isTodoWrite && !isSubagentCall && isExpanded && (_jsxs("div", { className: "mt-2 text-sm border border-border rounded-lg bg-card overflow-hidden w-full", children: [toolCall.rawInput &&
151
+ Object.keys(toolCall.rawInput).length > 0 &&
152
+ !toolCall.subagentPort && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsx("div", { className: "text-[11px] font-mono text-foreground", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: jsonStyle }) })] })), toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Files" }), _jsx("ul", { className: "space-y-1", children: toolCall.locations.map((loc) => (_jsxs("li", { className: "font-mono text-[11px] text-foreground bg-muted px-1.5 py-0.5 rounded w-fit", children: [loc.path, loc.line !== null &&
153
+ loc.line !== undefined &&
154
+ `:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), (toolCall.content && toolCall.content.length > 0) ||
155
+ toolCall.error ? (_jsxs("div", { className: "p-3 border-b border-border last:border-0", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Output" }), _jsxs("div", { className: "space-y-2 text-[11px] text-foreground", children: [toolCall.content?.map((block, idx) => {
156
+ // Generate a stable key based on content
157
+ const getBlockKey = () => {
158
+ if (block.type === "diff" && "path" in block) {
159
+ return `diff-${block.path}-${idx}`;
160
+ }
161
+ if (block.type === "terminal" && "terminalId" in block) {
162
+ return `terminal-${block.terminalId}`;
163
+ }
164
+ if (block.type === "text" && "text" in block) {
165
+ return `text-${block.text.substring(0, 20)}-${idx}`;
166
+ }
167
+ if (block.type === "content" && "content" in block) {
168
+ const innerContent = block.content;
169
+ return `content-${innerContent.text?.substring(0, 20)}-${idx}`;
170
+ }
171
+ return `block-${idx}`;
172
+ };
173
+ // Helper to render text content (with JSON parsing if applicable)
174
+ const renderTextContent = (text, key) => {
175
+ // Try to parse as JSON
176
+ try {
177
+ const parsed = JSON.parse(text);
178
+ // If it's an object or array, render with JsonView
179
+ if (typeof parsed === "object" && parsed !== null) {
180
+ return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: parsed, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: jsonStyle }) }, key));
181
+ }
182
+ }
183
+ catch {
184
+ // Not valid JSON, render as plain text
185
+ }
186
+ // Render as plain text
187
+ return (_jsx("pre", { className: "whitespace-pre-wrap font-mono text-[11px] text-foreground overflow-x-auto", children: text }, key));
188
+ };
189
+ // Handle nested content blocks (ACP format)
190
+ if (block.type === "content" && "content" in block) {
191
+ const innerContent = block.content;
192
+ if (innerContent.type === "text" && innerContent.text) {
193
+ return renderTextContent(innerContent.text, getBlockKey());
194
+ }
195
+ }
196
+ // Handle direct text blocks
197
+ if (block.type === "text" && "text" in block) {
198
+ return renderTextContent(block.text, getBlockKey());
199
+ }
200
+ // Handle image blocks
201
+ if (block.type === "image") {
202
+ const alt = block.alt || "Generated image";
203
+ let imageSrc;
204
+ if ("data" in block) {
205
+ // Base64 encoded image
206
+ const mimeType = block.mimeType || "image/png";
207
+ imageSrc = `data:${mimeType};base64,${block.data}`;
208
+ }
209
+ else if ("url" in block) {
210
+ // URL or file path
211
+ imageSrc = block.url;
212
+ }
213
+ else {
214
+ return null;
215
+ }
216
+ return (_jsx("div", { className: "my-2", children: _jsx("img", { src: imageSrc, alt: alt, className: "max-w-full h-auto rounded-md border border-border" }) }, getBlockKey()));
217
+ }
218
+ // Handle diff blocks
219
+ if (block.type === "diff" &&
220
+ "path" in block &&
221
+ "oldText" in block &&
222
+ "newText" in block) {
223
+ return (_jsxs("div", { className: "border border-border rounded bg-card", children: [_jsxs("div", { className: "bg-muted px-2 py-1 text-[10px] font-mono text-muted-foreground border-b border-border", children: [block.path, "line" in block &&
224
+ block.line !== null &&
225
+ block.line !== undefined &&
226
+ `:${block.line}`] }), _jsxs("div", { className: "p-2 font-mono text-[11px]", children: [_jsxs("div", { className: "text-red-500 dark:text-red-400", children: ["- ", block.oldText] }), _jsxs("div", { className: "text-green-500 dark:text-green-400", children: ["+ ", block.newText] })] })] }, getBlockKey()));
227
+ }
228
+ // Handle terminal blocks
229
+ if (block.type === "terminal" && "terminalId" in block) {
230
+ return (_jsxs("div", { className: "bg-neutral-900 text-neutral-100 p-2 rounded text-[11px] font-mono", children: ["Terminal: ", block.terminalId] }, getBlockKey()));
231
+ }
232
+ return null;
233
+ }), toolCall.error && (_jsxs("div", { className: "text-destructive font-mono text-[11px] mt-2", children: ["Error: ", toolCall.error] }))] })] })) : null, toolCall._meta?.truncationWarning && (_jsxs("div", { className: "mx-3 mt-3 mb-0 flex items-center gap-2 rounded-md bg-yellow-50 dark:bg-yellow-950/20 px-3 py-2 text-[11px] text-yellow-800 dark:text-yellow-200 border border-yellow-200 dark:border-yellow-900", children: [_jsx("span", { className: "text-yellow-600 dark:text-yellow-500", children: "\u26A0\uFE0F" }), _jsx("span", { children: toolCall._meta.truncationWarning })] })), (toolCall.tokenUsage || toolCall.startedAt) && (_jsxs("div", { className: "p-2 bg-muted/50 border-t border-border flex flex-wrap gap-4 text-[10px] text-muted-foreground font-sans", children: [toolCall.tokenUsage && (_jsxs("div", { className: "flex gap-3", children: [toolCall.tokenUsage.inputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Input:" }), toolCall.tokenUsage.inputTokens.toLocaleString()] })), toolCall.tokenUsage.outputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Output:" }), toolCall.tokenUsage.outputTokens.toLocaleString()] })), toolCall.tokenUsage.totalTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Total:" }), toolCall.tokenUsage.totalTokens.toLocaleString()] }))] })), toolCall.startedAt && (_jsxs("div", { className: "flex gap-3 ml-auto", children: [_jsxs("span", { children: ["Started: ", new Date(toolCall.startedAt).toLocaleTimeString()] }), toolCall.completedAt && (_jsxs("span", { children: ["Completed:", " ", new Date(toolCall.completedAt).toLocaleTimeString(), " (", Math.round((toolCall.completedAt - toolCall.startedAt) / 1000), "s)"] }))] }))] }))] }))] }));
234
+ }
@@ -0,0 +1,8 @@
1
+ import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
2
+ export interface ToolCallGroupProps {
3
+ toolCalls: ToolCallType[];
4
+ }
5
+ /**
6
+ * ToolCallGroup component - displays a group of parallel tool calls with collapsible details
7
+ */
8
+ export declare function ToolCallGroup({ toolCalls }: ToolCallGroupProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronDown, ListVideo } from "lucide-react";
3
+ import React, { useState } from "react";
4
+ import { ToolCall } from "./ToolCall.js";
5
+ /**
6
+ * ToolCallGroup component - displays a group of parallel tool calls with collapsible details
7
+ */
8
+ export function ToolCallGroup({ toolCalls }) {
9
+ const [isExpanded, setIsExpanded] = useState(false);
10
+ // Calculate group status based on individual tool call statuses
11
+ const getGroupStatus = () => {
12
+ const statuses = toolCalls.map((tc) => tc.status);
13
+ if (statuses.some((s) => s === "failed"))
14
+ return "failed";
15
+ if (statuses.some((s) => s === "in_progress"))
16
+ return "in_progress";
17
+ if (statuses.every((s) => s === "completed"))
18
+ return "completed";
19
+ return "pending";
20
+ };
21
+ const groupStatus = getGroupStatus();
22
+ // Generate summary of tool names
23
+ const toolNames = toolCalls.map((tc) => tc.prettyName || tc.title);
24
+ const uniqueNames = [...new Set(toolNames)];
25
+ const summary = uniqueNames.length <= 2
26
+ ? uniqueNames.join(", ")
27
+ : `${uniqueNames.slice(0, 2).join(", ")} +${uniqueNames.length - 2} more`;
28
+ return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsxs("button", { type: "button", className: "flex flex-col items-start gap-0.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [_jsx("div", { className: "text-muted-foreground", children: _jsx(ListVideo, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-muted-foreground", children: "Parallel operation" }), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-muted-foreground/70", children: toolCalls.length }), _jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground/70 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })] }), !isExpanded && (_jsx("span", { className: "text-paragraph-sm text-muted-foreground/70 pl-4.5", children: summary }))] }), isExpanded && (_jsx("div", { className: "mt-1", children: toolCalls.map((toolCall) => (_jsxs("div", { className: "flex items-start", children: [_jsx("div", { className: "w-2.5 h-4 border-l-2 border-b-2 border-border rounded-bl-[6px] mt-1 mr-0.5 shrink-0" }), _jsx("div", { className: "flex-1 -mt-2", children: _jsx(ToolCall, { toolCall: toolCall }) })] }, toolCall.id))) }))] }));
29
+ }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import JsonView from "@uiw/react-json-view";
3
- import { AlertCircle, BrainCircuit, CheckSquare, ChevronDown, ChevronRight, CircleDot, Cloud, Edit, FileText, Globe, Image, Link, ListVideo, Search, Wrench, } from "lucide-react";
3
+ import { AlertCircle, BrainCircuit, CheckSquare, ChevronDown, ChevronRight, CircleDot, Cloud, Edit, FileText, FoldVertical, Globe, Image, Link, ListVideo, ScissorsLineDashed, Search, Wrench, } from "lucide-react";
4
4
  import React, { useEffect, useState } from "react";
5
5
  import { getGroupDisplayState, getToolCallDisplayState, getToolCallStateVerbiage, isPreliminaryToolCall, } from "../../core/utils/tool-call-state.js";
6
6
  import { generateSmartSummary } from "../../core/utils/tool-summary.js";
@@ -8,6 +8,7 @@ import * as ChatLayout from "./ChatLayout.js";
8
8
  import { SubAgentDetails } from "./SubAgentDetails.js";
9
9
  import { useTheme } from "./ThemeProvider.js";
10
10
  import { TodoSubline } from "./TodoSubline.js";
11
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
11
12
  /**
12
13
  * Map of icon names to Lucide components
13
14
  */
@@ -24,6 +25,17 @@ const ICON_MAP = {
24
25
  BrainCircuit: BrainCircuit,
25
26
  CircleDot: CircleDot,
26
27
  };
28
+ /**
29
+ * CompactionDetails component - shows detailed stats when tool response was compacted
30
+ */
31
+ function CompactionDetails({ compactionAction, originalTokens, finalTokens, originalContentPath, }) {
32
+ // Calculate stats
33
+ const tokensSaved = originalTokens && finalTokens ? originalTokens - finalTokens : undefined;
34
+ const reductionPercent = originalTokens && finalTokens
35
+ ? Math.round((1 - finalTokens / originalTokens) * 100)
36
+ : undefined;
37
+ return (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [compactionAction === "compacted" ? (_jsx(FoldVertical, { className: "w-3.5 h-3.5 text-text-secondary" })) : (_jsx(ScissorsLineDashed, { className: "w-3.5 h-3.5 text-destructive" })), _jsxs("span", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider font-sans", children: ["Response", " ", compactionAction === "compacted" ? "Compacted" : "Truncated"] })] }), _jsxs("div", { className: "grid grid-cols-4 gap-3 mb-3", children: [originalTokens !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-[9px] text-text-secondary uppercase tracking-wide font-sans mb-0.5", children: "Original" }), _jsxs("div", { className: "text-[12px] font-medium text-foreground font-sans", children: [originalTokens.toLocaleString(), " tokens"] })] })), finalTokens !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-[9px] text-text-secondary uppercase tracking-wide font-sans mb-0.5", children: "Compacted" }), _jsxs("div", { className: "text-[12px] font-medium text-foreground font-sans", children: [finalTokens.toLocaleString(), " tokens"] })] })), tokensSaved !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-[9px] text-text-secondary uppercase tracking-wide font-sans mb-0.5", children: "Saved" }), _jsxs("div", { className: "text-[12px] font-medium text-foreground font-sans", children: [tokensSaved.toLocaleString(), " tokens"] })] })), reductionPercent !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-[9px] text-text-secondary uppercase tracking-wide font-sans mb-0.5", children: "Reduction" }), _jsxs("div", { className: "text-[12px] font-medium text-foreground font-sans", children: [reductionPercent, "%"] })] }))] }), originalContentPath && (_jsxs("div", { className: "text-[10px] text-text-secondary font-sans", children: [_jsx("span", { className: "font-medium", children: "Original saved to:" }), " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded text-[9px]", children: originalContentPath })] }))] }));
38
+ }
27
39
  /**
28
40
  * ToolOperation component - unified display for tool calls
29
41
  * Handles both individual and grouped tool calls with smooth transitions
@@ -39,6 +51,31 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
39
51
  const toolHookNotification = singleToolCall
40
52
  ? hookNotifications.find((n) => n.toolCallId === singleToolCall.id)
41
53
  : undefined;
54
+ // Detect if compaction was applied (from hook notification or persisted _meta)
55
+ const hasCompaction = !!((toolHookNotification?.status === "completed" &&
56
+ toolHookNotification.metadata?.action &&
57
+ toolHookNotification.metadata.action !== "no_action_needed" &&
58
+ toolHookNotification.metadata.action !== "none") ||
59
+ singleToolCall?._meta?.compactionAction);
60
+ // Detect if truncation was used (as opposed to intelligent compaction)
61
+ const isTruncation = !!(toolHookNotification?.metadata?.action === "truncated" ||
62
+ toolHookNotification?.metadata?.action === "compacted_then_truncated" ||
63
+ singleToolCall?._meta?.compactionAction === "truncated");
64
+ // For grouped tool calls, check if any have compaction
65
+ const groupHasCompaction = isGrouped
66
+ ? toolCalls.some((tc) => tc._meta?.compactionAction ||
67
+ hookNotifications.some((n) => n.toolCallId === tc.id &&
68
+ n.status === "completed" &&
69
+ n.metadata?.action &&
70
+ n.metadata.action !== "no_action_needed" &&
71
+ n.metadata.action !== "none"))
72
+ : false;
73
+ const groupHasTruncation = isGrouped
74
+ ? toolCalls.some((tc) => tc._meta?.compactionAction === "truncated" ||
75
+ hookNotifications.some((n) => n.toolCallId === tc.id &&
76
+ (n.metadata?.action === "truncated" ||
77
+ n.metadata?.action === "compacted_then_truncated")))
78
+ : false;
42
79
  // Detect subagent calls
43
80
  const hasLiveSubagent = !!(singleToolCall?.subagentPort && singleToolCall?.subagentSessionId);
44
81
  const hasStoredSubagent = !!(singleToolCall?.subagentMessages &&
@@ -137,7 +174,24 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
137
174
  return (_jsxs("div", { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "text-text-secondary/70", children: _jsx(ListVideo, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Selecting tools" }), _jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })] }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: displayText })] }));
138
175
  }
139
176
  // Full display (for single tool call or expanded group, includes minimized state)
140
- return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsxs("button", { type: "button", className: "flex flex-col items-start gap-0.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit rounded-md px-1 -mx-1", onClick: handleHeaderClick, "aria-expanded": isTodoWrite ? undefined : isExpanded, children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: [isGrouped && _jsx("span", { className: "mr-1", children: "Parallel operation" }), !isGrouped && displayText] }), isGrouped && (_jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })), isFailed && _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }), isTodoWrite ? (_jsx(ChevronRight, { className: "h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors" })) : (_jsx("div", { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-all duration-200 ${isExpanded ? "rotate-180" : ""}`, children: _jsx(ChevronDown, { className: "h-3 w-3" }) }))] }), !isGrouped &&
177
+ return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsxs("button", { type: "button", className: "flex flex-col items-start gap-0.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit rounded-md px-1 -mx-1", onClick: handleHeaderClick, "aria-expanded": isTodoWrite ? undefined : isExpanded, children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: [isGrouped && _jsx("span", { className: "mr-1", children: "Parallel operation" }), !isGrouped && displayText] }), isGrouped && (_jsx("span", { className: "text-[10px] bg-muted px-1.5 py-0.5 rounded text-text-secondary/70", children: toolCalls.length })), isFailed && (_jsx("span", { title: isGrouped
178
+ ? `${toolCalls.filter((tc) => tc.status === "failed").length} of ${toolCalls.length} operations failed`
179
+ : singleToolCall?.error || "Operation failed", children: _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }) })), isGrouped && groupHasCompaction && (_jsx(TooltipProvider, { delayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: groupHasTruncation ? (_jsx(ScissorsLineDashed, { className: "h-3 w-3 text-destructive" })) : (_jsx(FoldVertical, { className: "h-3 w-3 text-text-secondary/70" })) }) }), _jsx(TooltipContent, { children: groupHasTruncation
180
+ ? "Some responses were truncated"
181
+ : "Some responses were compacted" })] }) })), !isGrouped && hasCompaction && (_jsx(TooltipProvider, { delayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: isTruncation ? (_jsx(ScissorsLineDashed, { className: "h-3 w-3 text-destructive" })) : (_jsx(FoldVertical, { className: "h-3 w-3 text-text-secondary/70" })) }) }), _jsx(TooltipContent, { children: (() => {
182
+ const meta = singleToolCall?._meta;
183
+ const percentage = meta?.originalTokens && meta?.finalTokens
184
+ ? Math.round((1 - meta.finalTokens / meta.originalTokens) * 100)
185
+ : null;
186
+ if (isTruncation) {
187
+ return percentage
188
+ ? `Response truncated (${percentage}% reduction)`
189
+ : "Response was truncated";
190
+ }
191
+ return percentage
192
+ ? `Response compacted (${percentage}% reduction)`
193
+ : "Response was compacted";
194
+ })() })] }) })), isTodoWrite ? (_jsx(ChevronRight, { className: "h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors" })) : (_jsx("div", { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-all duration-200 ${isExpanded ? "rotate-180" : ""}`, children: _jsx(ChevronDown, { className: "h-3 w-3" }) }))] }), !isGrouped &&
141
195
  singleToolCall &&
142
196
  (isTodoWrite && singleToolCall.rawInput?.todos ? (_jsx(TodoSubline, { todos: singleToolCall.rawInput.todos, className: "text-paragraph-sm text-text-secondary/70 pl-4.5" })) : singleToolCall.subline ? (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: singleToolCall.subline })) : null), !isGrouped && toolHookNotification && (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: toolHookNotification.status === "triggered"
143
197
  ? "Compacting response..."
@@ -175,21 +229,57 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
175
229
  }
176
230
  /**
177
231
  * Component to render a single tool call within a grouped parallel operation
178
- * Handles subagent calls with their own expansion state
232
+ * Each tool call is individually expandable and collapsed by default
179
233
  */
180
234
  function GroupedToolCallItem({ toolCall, hookNotification, }) {
181
- const [isSubagentExpanded, setIsSubagentExpanded] = useState(false);
235
+ const [isExpanded, setIsExpanded] = useState(false);
182
236
  // Detect subagent calls
183
237
  const hasLiveSubagent = !!(toolCall.subagentPort && toolCall.subagentSessionId);
184
238
  const hasStoredSubagent = !!(toolCall.subagentMessages && toolCall.subagentMessages.length > 0);
185
239
  const isSubagentCall = hasLiveSubagent || hasStoredSubagent;
186
240
  const isReplaySubagent = hasStoredSubagent;
241
+ // Detect compaction for this individual tool call
242
+ const hasCompaction = !!((hookNotification?.status === "completed" &&
243
+ hookNotification.metadata?.action &&
244
+ hookNotification.metadata.action !== "no_action_needed" &&
245
+ hookNotification.metadata.action !== "none") ||
246
+ toolCall._meta?.compactionAction);
247
+ const isTruncation = !!(hookNotification?.metadata?.action === "truncated" ||
248
+ hookNotification?.metadata?.action === "compacted_then_truncated" ||
249
+ toolCall._meta?.compactionAction === "truncated");
250
+ const isFailed = toolCall.status === "failed";
187
251
  if (isSubagentCall) {
188
252
  // Render subagent with clickable header and SubAgentDetails component
189
- return (_jsxs("div", { className: "flex flex-col ml-5", children: [_jsxs("button", { type: "button", className: "flex items-center gap-1.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsSubagentExpanded(!isSubagentExpanded), "aria-expanded": isSubagentExpanded, children: [_jsx("div", { className: "text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(CircleDot, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: toolCall.rawInput?.agentName || "Subagent" }), _jsx(ChevronDown, { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors transition-transform duration-200 ${isSubagentExpanded ? "rotate-180" : ""}` })] }), _jsx("div", { className: "pl-4.5", children: _jsx(SubAgentDetails, { port: toolCall.subagentPort, sessionId: toolCall.subagentSessionId, parentStatus: toolCall.status, agentName: toolCall.rawInput?.agentName, query: toolCall.rawInput?.query, isExpanded: isSubagentExpanded, onExpandChange: setIsSubagentExpanded, storedMessages: toolCall.subagentMessages, isReplay: isReplaySubagent }) })] }));
253
+ return (_jsxs("div", { className: "flex flex-col ml-5", children: [_jsxs("button", { type: "button", className: "flex items-center gap-1.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, children: [_jsx("div", { className: "text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(CircleDot, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: toolCall.rawInput?.agentName || "Subagent" }), isFailed && (_jsx("span", { title: toolCall.error || "Operation failed", children: _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }) })), hasCompaction && (_jsx(TooltipProvider, { delayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: isTruncation ? (_jsx(ScissorsLineDashed, { className: "h-3 w-3 text-destructive" })) : (_jsx(FoldVertical, { className: "h-3 w-3 text-text-secondary/70" })) }) }), _jsx(TooltipContent, { children: (() => {
254
+ const meta = toolCall._meta;
255
+ const percentage = meta?.originalTokens && meta?.finalTokens
256
+ ? Math.round((1 - meta.finalTokens / meta.originalTokens) * 100)
257
+ : null;
258
+ if (isTruncation) {
259
+ return percentage
260
+ ? `Response truncated (${percentage}% reduction)`
261
+ : "Response was truncated";
262
+ }
263
+ return percentage
264
+ ? `Response compacted (${percentage}% reduction)`
265
+ : "Response was compacted";
266
+ })() })] }) })), _jsx(ChevronDown, { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })] }), _jsx("div", { className: "pl-4.5", children: _jsx(SubAgentDetails, { port: toolCall.subagentPort, sessionId: toolCall.subagentSessionId, parentStatus: toolCall.status, agentName: toolCall.rawInput?.agentName, query: toolCall.rawInput?.query, isExpanded: isExpanded, onExpandChange: setIsExpanded, storedMessages: toolCall.subagentMessages, isReplay: isReplaySubagent }) })] }));
190
267
  }
191
- // Regular tool call - show details
192
- return (_jsx("div", { className: "flex items-start gap-1.5", children: _jsx("div", { className: "flex-1 ml-5", children: _jsx(ToolOperationDetails, { toolCall: toolCall, ...(hookNotification ? { hookNotification } : {}) }) }) }));
268
+ // Regular tool call - collapsible with clickable header
269
+ return (_jsxs("div", { className: "flex flex-col ml-5", children: [_jsxs("button", { type: "button", className: "flex items-center gap-1.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, children: [_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: toolCall.prettyName || toolCall.title }), isFailed && (_jsx("span", { title: toolCall.error || "Operation failed", children: _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }) })), hasCompaction && (_jsx(TooltipProvider, { delayDuration: 0, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: isTruncation ? (_jsx(ScissorsLineDashed, { className: "h-3 w-3 text-destructive" })) : (_jsx(FoldVertical, { className: "h-3 w-3 text-text-secondary/70" })) }) }), _jsx(TooltipContent, { children: (() => {
270
+ const meta = toolCall._meta;
271
+ const percentage = meta?.originalTokens && meta?.finalTokens
272
+ ? Math.round((1 - meta.finalTokens / meta.originalTokens) * 100)
273
+ : null;
274
+ if (isTruncation) {
275
+ return percentage
276
+ ? `Response truncated (${percentage}% reduction)`
277
+ : "Response was truncated";
278
+ }
279
+ return percentage
280
+ ? `Response compacted (${percentage}% reduction)`
281
+ : "Response was compacted";
282
+ })() })] }) })), _jsx(ChevronDown, { className: `h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })] }), isExpanded && (_jsx("div", { className: "mt-1", children: _jsx(ToolOperationDetails, { toolCall: toolCall, ...(hookNotification ? { hookNotification } : {}) }) }))] }));
193
283
  }
194
284
  /**
195
285
  * Component to display detailed tool call information
@@ -231,7 +321,7 @@ function ToolOperationDetails({ toolCall, hookNotification, }) {
231
321
  "--w-rjv-type-nan-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
232
322
  "--w-rjv-type-undefined-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
233
323
  };
234
- return (_jsxs("div", { className: "text-sm border border-border rounded-lg bg-card overflow-hidden w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Files" }), _jsx("ul", { className: "space-y-1", children: toolCall.locations.map((loc) => (_jsxs("li", { className: "font-mono text-[11px] text-foreground bg-muted px-1.5 py-0.5 rounded w-fit", children: [loc.path, loc.line !== null && loc.line !== undefined && `:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), toolCall.rawInput && Object.keys(toolCall.rawInput).length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsx("div", { className: "text-[11px] font-mono text-foreground", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, shortenTextAfterLength: 80, style: jsonStyle }) })] })), ((toolCall.content && toolCall.content.length > 0) ||
324
+ return (_jsxs("div", { className: "text-sm border border-border rounded-lg bg-card overflow-hidden w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Files" }), _jsx("ul", { className: "space-y-1", children: toolCall.locations.map((loc) => (_jsxs("li", { className: "font-mono text-[11px] text-foreground bg-muted px-1.5 py-0.5 rounded w-fit", children: [loc.path, loc.line !== null && loc.line !== undefined && `:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), toolCall.rawInput && Object.keys(toolCall.rawInput).length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsx("div", { className: "text-[11px] font-mono text-foreground", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, shortenTextAfterLength: 80, style: jsonStyle }) })] })), toolCall._meta?.compactionAction && (_jsx(CompactionDetails, { compactionAction: toolCall._meta.compactionAction, originalTokens: toolCall._meta.originalTokens, finalTokens: toolCall._meta.finalTokens, originalContentPath: toolCall._meta.originalContentPath })), ((toolCall.content && toolCall.content.length > 0) ||
235
325
  toolCall.error) && (_jsxs("div", { className: "p-3 border-b border-border last:border-0", children: [_jsx("div", { className: "text-[10px] font-bold text-text-secondary uppercase tracking-wider mb-1.5 font-sans", children: "Output" }), _jsxs("div", { className: "space-y-2 text-[11px] text-foreground", children: [toolCall.content?.map((block, idx) => {
236
326
  // Generate a stable key based on content
237
327
  const getBlockKey = () => {
@@ -255,7 +345,12 @@ function ToolOperationDetails({ toolCall, hookNotification, }) {
255
345
  try {
256
346
  const parsed = JSON.parse(text);
257
347
  if (typeof parsed === "object" && parsed !== null) {
258
- return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: parsed, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, shortenTextAfterLength: 80, style: jsonStyle }) }, key));
348
+ // Filter out internal metadata fields from display
349
+ const displayValue = { ...parsed };
350
+ if ("_compactionMeta" in displayValue) {
351
+ delete displayValue._compactionMeta;
352
+ }
353
+ return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: displayValue, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, shortenTextAfterLength: 80, style: jsonStyle }) }, key));
259
354
  }
260
355
  }
261
356
  catch {
@@ -13,9 +13,9 @@ export type MessageRole = z.infer<typeof MessageRole>;
13
13
  * Content type for messages
14
14
  */
15
15
  export declare const ContentType: z.ZodEnum<{
16
- file: "file";
17
16
  text: "text";
18
17
  image: "image";
18
+ file: "file";
19
19
  tool_call: "tool_call";
20
20
  tool_result: "tool_result";
21
21
  }>;
@@ -25,9 +25,9 @@ export type ContentType = z.infer<typeof ContentType>;
25
25
  */
26
26
  export declare const BaseContent: z.ZodObject<{
27
27
  type: z.ZodEnum<{
28
- file: "file";
29
28
  text: "text";
30
29
  image: "image";
30
+ file: "file";
31
31
  tool_call: "tool_call";
32
32
  tool_result: "tool_result";
33
33
  }>;
@@ -3,8 +3,8 @@ import { z } from "zod";
3
3
  * Session status
4
4
  */
5
5
  export declare const SessionStatus: z.ZodEnum<{
6
- error: "error";
7
6
  active: "active";
7
+ error: "error";
8
8
  idle: "idle";
9
9
  connecting: "connecting";
10
10
  connected: "connected";
@@ -40,8 +40,8 @@ export type SessionMetadata = z.infer<typeof SessionMetadata>;
40
40
  export declare const Session: z.ZodObject<{
41
41
  id: z.ZodString;
42
42
  status: z.ZodEnum<{
43
- error: "error";
44
43
  active: "active";
44
+ error: "error";
45
45
  idle: "idle";
46
46
  connecting: "connecting";
47
47
  connected: "connected";
@@ -116,8 +116,8 @@ export type Session = z.infer<typeof Session>;
116
116
  export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
117
117
  sessionId: z.ZodString;
118
118
  status: z.ZodOptional<z.ZodEnum<{
119
- error: "error";
120
119
  active: "active";
120
+ error: "error";
121
121
  idle: "idle";
122
122
  connecting: "connecting";
123
123
  connected: "connected";
@@ -253,6 +253,8 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
253
253
  }>>;
254
254
  originalTokens: z.ZodOptional<z.ZodNumber>;
255
255
  finalTokens: z.ZodOptional<z.ZodNumber>;
256
+ originalContentPreview: z.ZodOptional<z.ZodString>;
257
+ originalContentPath: z.ZodOptional<z.ZodString>;
256
258
  }, z.core.$strip>>;
257
259
  subagentPort: z.ZodOptional<z.ZodNumber>;
258
260
  subagentSessionId: z.ZodOptional<z.ZodString>;
@@ -353,8 +355,8 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
353
355
  }, z.core.$strip>, z.ZodObject<{
354
356
  sessionId: z.ZodString;
355
357
  status: z.ZodOptional<z.ZodEnum<{
356
- error: "error";
357
358
  active: "active";
359
+ error: "error";
358
360
  idle: "idle";
359
361
  connecting: "connecting";
360
362
  connected: "connected";
@@ -555,13 +557,24 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
555
557
  }, z.core.$strip>], "type">>>;
556
558
  isStreaming: z.ZodOptional<z.ZodBoolean>;
557
559
  }, z.core.$strip>>>;
560
+ _meta: z.ZodOptional<z.ZodObject<{
561
+ truncationWarning: z.ZodOptional<z.ZodString>;
562
+ compactionAction: z.ZodOptional<z.ZodEnum<{
563
+ compacted: "compacted";
564
+ truncated: "truncated";
565
+ }>>;
566
+ originalTokens: z.ZodOptional<z.ZodNumber>;
567
+ finalTokens: z.ZodOptional<z.ZodNumber>;
568
+ originalContentPreview: z.ZodOptional<z.ZodString>;
569
+ originalContentPath: z.ZodOptional<z.ZodString>;
570
+ }, z.core.$strip>>;
558
571
  }, z.core.$strip>;
559
572
  messageId: z.ZodOptional<z.ZodString>;
560
573
  }, z.core.$strip>, z.ZodObject<{
561
574
  sessionId: z.ZodString;
562
575
  status: z.ZodOptional<z.ZodEnum<{
563
- error: "error";
564
576
  active: "active";
577
+ error: "error";
565
578
  idle: "idle";
566
579
  connecting: "connecting";
567
580
  connected: "connected";
@@ -625,8 +638,8 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
625
638
  }, z.core.$strip>, z.ZodObject<{
626
639
  sessionId: z.ZodString;
627
640
  status: z.ZodOptional<z.ZodEnum<{
628
- error: "error";
629
641
  active: "active";
642
+ error: "error";
630
643
  idle: "idle";
631
644
  connecting: "connecting";
632
645
  connected: "connected";
@@ -723,6 +723,37 @@ export class HttpTransport {
723
723
  Array.isArray(update._meta.subagentMessages)
724
724
  ? update._meta.subagentMessages
725
725
  : undefined;
726
+ // Extract compaction metadata from _meta (for initial tool call during replay)
727
+ const compactionActionInitial = update._meta &&
728
+ typeof update._meta === "object" &&
729
+ "compactionAction" in update._meta &&
730
+ typeof update._meta.compactionAction === "string"
731
+ ? update._meta.compactionAction
732
+ : undefined;
733
+ const originalTokensInitial = update._meta &&
734
+ typeof update._meta === "object" &&
735
+ "originalTokens" in update._meta &&
736
+ typeof update._meta.originalTokens === "number"
737
+ ? update._meta.originalTokens
738
+ : undefined;
739
+ const finalTokensInitial = update._meta &&
740
+ typeof update._meta === "object" &&
741
+ "finalTokens" in update._meta &&
742
+ typeof update._meta.finalTokens === "number"
743
+ ? update._meta.finalTokens
744
+ : undefined;
745
+ const originalContentPreviewInitial = update._meta &&
746
+ typeof update._meta === "object" &&
747
+ "originalContentPreview" in update._meta &&
748
+ typeof update._meta.originalContentPreview === "string"
749
+ ? update._meta.originalContentPreview
750
+ : undefined;
751
+ const originalContentPathInitial = update._meta &&
752
+ typeof update._meta === "object" &&
753
+ "originalContentPath" in update._meta &&
754
+ typeof update._meta.originalContentPath === "string"
755
+ ? update._meta.originalContentPath
756
+ : undefined;
726
757
  // Initial tool call notification
727
758
  const toolCall = {
728
759
  id: update.toolCallId ?? "",
@@ -787,6 +818,20 @@ export class HttpTransport {
787
818
  subagentPort,
788
819
  subagentSessionId,
789
820
  subagentMessages,
821
+ // Compaction metadata
822
+ _meta: compactionActionInitial ||
823
+ originalTokensInitial ||
824
+ finalTokensInitial ||
825
+ originalContentPreviewInitial ||
826
+ originalContentPathInitial
827
+ ? {
828
+ compactionAction: compactionActionInitial,
829
+ originalTokens: originalTokensInitial,
830
+ finalTokens: finalTokensInitial,
831
+ originalContentPreview: originalContentPreviewInitial,
832
+ originalContentPath: originalContentPathInitial,
833
+ }
834
+ : undefined,
790
835
  };
791
836
  const sessionUpdate = {
792
837
  type: "tool_call",
@@ -800,7 +845,6 @@ export class HttpTransport {
800
845
  typeof update._meta === "object" &&
801
846
  "isReplay" in update._meta &&
802
847
  update._meta.isReplay === true;
803
- // Debug: log tool call creation
804
848
  logger.info("Creating tool_call session update", {
805
849
  toolCallId: toolCall.id,
806
850
  title: toolCall.title,
@@ -885,6 +929,37 @@ export class HttpTransport {
885
929
  subagentMessagesCount: subagentMessages?.length,
886
930
  });
887
931
  }
932
+ // Extract compaction metadata from _meta
933
+ const compactionAction = update._meta &&
934
+ typeof update._meta === "object" &&
935
+ "compactionAction" in update._meta &&
936
+ typeof update._meta.compactionAction === "string"
937
+ ? update._meta.compactionAction
938
+ : undefined;
939
+ const originalTokens = update._meta &&
940
+ typeof update._meta === "object" &&
941
+ "originalTokens" in update._meta &&
942
+ typeof update._meta.originalTokens === "number"
943
+ ? update._meta.originalTokens
944
+ : undefined;
945
+ const finalTokens = update._meta &&
946
+ typeof update._meta === "object" &&
947
+ "finalTokens" in update._meta &&
948
+ typeof update._meta.finalTokens === "number"
949
+ ? update._meta.finalTokens
950
+ : undefined;
951
+ const originalContentPreview = update._meta &&
952
+ typeof update._meta === "object" &&
953
+ "originalContentPreview" in update._meta &&
954
+ typeof update._meta.originalContentPreview === "string"
955
+ ? update._meta.originalContentPreview
956
+ : undefined;
957
+ const originalContentPath = update._meta &&
958
+ typeof update._meta === "object" &&
959
+ "originalContentPath" in update._meta &&
960
+ typeof update._meta.originalContentPath === "string"
961
+ ? update._meta.originalContentPath
962
+ : undefined;
888
963
  // Tool call update notification
889
964
  const toolCallUpdate = {
890
965
  id: update.toolCallId ?? "",
@@ -952,6 +1027,20 @@ export class HttpTransport {
952
1027
  subagentSessionId,
953
1028
  // Sub-agent messages for replay
954
1029
  subagentMessages,
1030
+ // Compaction metadata
1031
+ _meta: compactionAction ||
1032
+ originalTokens ||
1033
+ finalTokens ||
1034
+ originalContentPreview ||
1035
+ originalContentPath
1036
+ ? {
1037
+ compactionAction,
1038
+ originalTokens,
1039
+ finalTokens,
1040
+ originalContentPreview,
1041
+ originalContentPath,
1042
+ }
1043
+ : undefined,
955
1044
  };
956
1045
  const sessionUpdate = {
957
1046
  type: "tool_call_update",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/ui",
3
- "version": "0.1.78",
3
+ "version": "0.1.79",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -49,7 +49,7 @@
49
49
  "@radix-ui/react-slot": "^1.2.4",
50
50
  "@radix-ui/react-tabs": "^1.1.13",
51
51
  "@radix-ui/react-tooltip": "^1.2.8",
52
- "@townco/core": "0.0.56",
52
+ "@townco/core": "0.0.57",
53
53
  "@uiw/react-json-view": "^2.0.0-alpha.39",
54
54
  "class-variance-authority": "^0.7.1",
55
55
  "clsx": "^2.1.1",
@@ -65,7 +65,7 @@
65
65
  "zustand": "^5.0.8"
66
66
  },
67
67
  "devDependencies": {
68
- "@townco/tsconfig": "0.1.75",
68
+ "@townco/tsconfig": "0.1.76",
69
69
  "@types/node": "^24.10.0",
70
70
  "@types/react": "^19.2.2",
71
71
  "ink": "^6.4.0",