@townco/ui 0.1.75 → 0.1.76

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.
@@ -15,7 +15,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
15
15
  id: string;
16
16
  title: string;
17
17
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
18
- status: "pending" | "in_progress" | "completed" | "failed";
18
+ status: "completed" | "pending" | "in_progress" | "failed";
19
19
  batchId?: string | undefined;
20
20
  prettyName?: string | undefined;
21
21
  icon?: string | undefined;
@@ -82,7 +82,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
82
82
  toolCalls?: {
83
83
  id: string;
84
84
  title: string;
85
- status: "pending" | "in_progress" | "completed" | "failed";
85
+ status: "completed" | "pending" | "in_progress" | "failed";
86
86
  prettyName?: string | undefined;
87
87
  icon?: string | undefined;
88
88
  content?: ({
@@ -122,7 +122,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
122
122
  toolCall: {
123
123
  id: string;
124
124
  title: string;
125
- status: "pending" | "in_progress" | "completed" | "failed";
125
+ status: "completed" | "pending" | "in_progress" | "failed";
126
126
  prettyName?: string | undefined;
127
127
  icon?: string | undefined;
128
128
  content?: ({
@@ -163,7 +163,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
163
163
  id: string;
164
164
  hookType: "context_size" | "tool_response";
165
165
  callback: string;
166
- status: "error" | "completed" | "triggered";
166
+ status: "error" | "triggered" | "completed";
167
167
  threshold?: number | undefined;
168
168
  currentPercentage?: number | undefined;
169
169
  metadata?: {
@@ -181,6 +181,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
181
181
  triggeredAt?: number | undefined;
182
182
  completedAt?: number | undefined;
183
183
  contentPosition?: number | undefined;
184
+ toolCallId?: string | undefined;
184
185
  }[] | undefined;
185
186
  tokenUsage?: {
186
187
  inputTokens?: number | undefined;
@@ -3,7 +3,7 @@ import type { AcpClient } from "../../sdk/client/index.js";
3
3
  * Hook for managing chat session lifecycle
4
4
  */
5
5
  export declare function useChatSession(client: AcpClient | null, initialSessionId?: string | null): {
6
- connectionStatus: "error" | "connecting" | "connected" | "disconnected";
6
+ connectionStatus: "error" | "disconnected" | "connecting" | "connected";
7
7
  sessionId: string | null;
8
8
  connect: () => Promise<void>;
9
9
  loadSession: (sessionIdToLoad: string) => Promise<void>;
@@ -13,7 +13,7 @@ export declare function useToolCalls(client: AcpClient | null): {
13
13
  id: string;
14
14
  title: string;
15
15
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
16
- status: "pending" | "in_progress" | "completed" | "failed";
16
+ status: "completed" | "pending" | "in_progress" | "failed";
17
17
  batchId?: string | undefined;
18
18
  prettyName?: string | undefined;
19
19
  icon?: string | undefined;
@@ -80,7 +80,7 @@ export declare function useToolCalls(client: AcpClient | null): {
80
80
  toolCalls?: {
81
81
  id: string;
82
82
  title: string;
83
- status: "pending" | "in_progress" | "completed" | "failed";
83
+ status: "completed" | "pending" | "in_progress" | "failed";
84
84
  prettyName?: string | undefined;
85
85
  icon?: string | undefined;
86
86
  content?: ({
@@ -120,7 +120,7 @@ export declare function useToolCalls(client: AcpClient | null): {
120
120
  toolCall: {
121
121
  id: string;
122
122
  title: string;
123
- status: "pending" | "in_progress" | "completed" | "failed";
123
+ status: "completed" | "pending" | "in_progress" | "failed";
124
124
  prettyName?: string | undefined;
125
125
  icon?: string | undefined;
126
126
  content?: ({
@@ -23,8 +23,8 @@ export declare const HookNotificationDisplay: z.ZodObject<{
23
23
  callback: z.ZodString;
24
24
  status: z.ZodEnum<{
25
25
  error: "error";
26
- completed: "completed";
27
26
  triggered: "triggered";
27
+ completed: "completed";
28
28
  }>;
29
29
  threshold: z.ZodOptional<z.ZodNumber>;
30
30
  currentPercentage: z.ZodOptional<z.ZodNumber>;
@@ -42,6 +42,7 @@ export declare const HookNotificationDisplay: z.ZodObject<{
42
42
  triggeredAt: z.ZodOptional<z.ZodNumber>;
43
43
  completedAt: z.ZodOptional<z.ZodNumber>;
44
44
  contentPosition: z.ZodOptional<z.ZodNumber>;
45
+ toolCallId: z.ZodOptional<z.ZodString>;
45
46
  }, z.core.$strip>;
46
47
  export type HookNotificationDisplay = z.infer<typeof HookNotificationDisplay>;
47
48
  export { HookType, HookNotification };
@@ -85,9 +86,9 @@ export declare const DisplayMessage: z.ZodObject<{
85
86
  other: "other";
86
87
  }>;
87
88
  status: z.ZodEnum<{
89
+ completed: "completed";
88
90
  pending: "pending";
89
91
  in_progress: "in_progress";
90
- completed: "completed";
91
92
  failed: "failed";
92
93
  }>;
93
94
  contentPosition: z.ZodOptional<z.ZodNumber>;
@@ -153,9 +154,9 @@ export declare const DisplayMessage: z.ZodObject<{
153
154
  prettyName: z.ZodOptional<z.ZodString>;
154
155
  icon: z.ZodOptional<z.ZodString>;
155
156
  status: z.ZodEnum<{
157
+ completed: "completed";
156
158
  pending: "pending";
157
159
  in_progress: "in_progress";
158
- completed: "completed";
159
160
  failed: "failed";
160
161
  }>;
161
162
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -198,9 +199,9 @@ export declare const DisplayMessage: z.ZodObject<{
198
199
  prettyName: z.ZodOptional<z.ZodString>;
199
200
  icon: z.ZodOptional<z.ZodString>;
200
201
  status: z.ZodEnum<{
202
+ completed: "completed";
201
203
  pending: "pending";
202
204
  in_progress: "in_progress";
203
- completed: "completed";
204
205
  failed: "failed";
205
206
  }>;
206
207
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -246,8 +247,8 @@ export declare const DisplayMessage: z.ZodObject<{
246
247
  callback: z.ZodString;
247
248
  status: z.ZodEnum<{
248
249
  error: "error";
249
- completed: "completed";
250
250
  triggered: "triggered";
251
+ completed: "completed";
251
252
  }>;
252
253
  threshold: z.ZodOptional<z.ZodNumber>;
253
254
  currentPercentage: z.ZodOptional<z.ZodNumber>;
@@ -265,6 +266,7 @@ export declare const DisplayMessage: z.ZodObject<{
265
266
  triggeredAt: z.ZodOptional<z.ZodNumber>;
266
267
  completedAt: z.ZodOptional<z.ZodNumber>;
267
268
  contentPosition: z.ZodOptional<z.ZodNumber>;
269
+ toolCallId: z.ZodOptional<z.ZodString>;
268
270
  }, z.core.$strip>>>;
269
271
  tokenUsage: z.ZodOptional<z.ZodObject<{
270
272
  inputTokens: z.ZodOptional<z.ZodNumber>;
@@ -336,9 +338,9 @@ export declare const ChatSessionState: z.ZodObject<{
336
338
  other: "other";
337
339
  }>;
338
340
  status: z.ZodEnum<{
341
+ completed: "completed";
339
342
  pending: "pending";
340
343
  in_progress: "in_progress";
341
- completed: "completed";
342
344
  failed: "failed";
343
345
  }>;
344
346
  contentPosition: z.ZodOptional<z.ZodNumber>;
@@ -404,9 +406,9 @@ export declare const ChatSessionState: z.ZodObject<{
404
406
  prettyName: z.ZodOptional<z.ZodString>;
405
407
  icon: z.ZodOptional<z.ZodString>;
406
408
  status: z.ZodEnum<{
409
+ completed: "completed";
407
410
  pending: "pending";
408
411
  in_progress: "in_progress";
409
- completed: "completed";
410
412
  failed: "failed";
411
413
  }>;
412
414
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -449,9 +451,9 @@ export declare const ChatSessionState: z.ZodObject<{
449
451
  prettyName: z.ZodOptional<z.ZodString>;
450
452
  icon: z.ZodOptional<z.ZodString>;
451
453
  status: z.ZodEnum<{
454
+ completed: "completed";
452
455
  pending: "pending";
453
456
  in_progress: "in_progress";
454
- completed: "completed";
455
457
  failed: "failed";
456
458
  }>;
457
459
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -497,8 +499,8 @@ export declare const ChatSessionState: z.ZodObject<{
497
499
  callback: z.ZodString;
498
500
  status: z.ZodEnum<{
499
501
  error: "error";
500
- completed: "completed";
501
502
  triggered: "triggered";
503
+ completed: "completed";
502
504
  }>;
503
505
  threshold: z.ZodOptional<z.ZodNumber>;
504
506
  currentPercentage: z.ZodOptional<z.ZodNumber>;
@@ -516,6 +518,7 @@ export declare const ChatSessionState: z.ZodObject<{
516
518
  triggeredAt: z.ZodOptional<z.ZodNumber>;
517
519
  completedAt: z.ZodOptional<z.ZodNumber>;
518
520
  contentPosition: z.ZodOptional<z.ZodNumber>;
521
+ toolCallId: z.ZodOptional<z.ZodString>;
519
522
  }, z.core.$strip>>>;
520
523
  tokenUsage: z.ZodOptional<z.ZodObject<{
521
524
  inputTokens: z.ZodOptional<z.ZodNumber>;
@@ -546,8 +549,8 @@ export type ChatSessionState = z.infer<typeof ChatSessionState>;
546
549
  */
547
550
  export declare const ConnectionStatus: z.ZodEnum<{
548
551
  error: "error";
552
+ disconnected: "disconnected";
549
553
  connecting: "connecting";
550
554
  connected: "connected";
551
- disconnected: "disconnected";
552
555
  }>;
553
556
  export type ConnectionStatus = z.infer<typeof ConnectionStatus>;
@@ -45,6 +45,8 @@ export const HookNotificationDisplay = z.object({
45
45
  completedAt: z.number().optional(),
46
46
  // Position in content where the hook was triggered (for inline rendering)
47
47
  contentPosition: z.number().optional(),
48
+ // For tool_response hooks - associates notification with a specific tool call
49
+ toolCallId: z.string().optional(),
48
50
  });
49
51
  export { HookType, HookNotification };
50
52
  /**
@@ -13,16 +13,16 @@ export type ToolCallStatus = z.infer<typeof ToolCallStatusSchema>;
13
13
  * Tool call categories for UI presentation
14
14
  */
15
15
  export declare const ToolCallKindSchema: z.ZodEnum<{
16
+ search: "search";
17
+ execute: "execute";
18
+ move: "move";
19
+ other: "other";
16
20
  read: "read";
17
21
  edit: "edit";
18
22
  delete: "delete";
19
- move: "move";
20
- search: "search";
21
- execute: "execute";
22
23
  think: "think";
23
24
  fetch: "fetch";
24
25
  switch_mode: "switch_mode";
25
- other: "other";
26
26
  }>;
27
27
  export type ToolCallKind = z.infer<typeof ToolCallKindSchema>;
28
28
  /**
@@ -280,16 +280,16 @@ export declare const ToolCallSchema: z.ZodObject<{
280
280
  }, z.core.$strip>>;
281
281
  subline: z.ZodOptional<z.ZodString>;
282
282
  kind: z.ZodEnum<{
283
+ search: "search";
284
+ execute: "execute";
285
+ move: "move";
286
+ other: "other";
283
287
  read: "read";
284
288
  edit: "edit";
285
289
  delete: "delete";
286
- move: "move";
287
- search: "search";
288
- execute: "execute";
289
290
  think: "think";
290
291
  fetch: "fetch";
291
292
  switch_mode: "switch_mode";
292
- other: "other";
293
293
  }>;
294
294
  status: z.ZodEnum<{
295
295
  pending: "pending";
@@ -12,6 +12,8 @@ function createHookNotificationDisplay(notification) {
12
12
  id: `hook_${Date.now()}_${notification.hookType}_${notification.callback}`,
13
13
  hookType: notification.hookType,
14
14
  callback: notification.callback,
15
+ // Include toolCallId if present (for tool_response hooks)
16
+ ...(notification.toolCallId ? { toolCallId: notification.toolCallId } : {}),
15
17
  };
16
18
  switch (notification.type) {
17
19
  case "hook_triggered":
@@ -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> {
@@ -4,18 +4,25 @@ import React, { useEffect, useState } from "react";
4
4
  /**
5
5
  * Get display information for a hook type
6
6
  */
7
- function getHookDisplayInfo(hookType, _callback, status) {
7
+ function getHookDisplayInfo(hookType, _callback, status, action) {
8
8
  const isTriggered = status === "triggered";
9
+ const noActionNeeded = action === "no_action_needed";
9
10
  if (hookType === "context_size") {
11
+ if (isTriggered) {
12
+ return { icon: Archive, title: "Compacting Context..." };
13
+ }
10
14
  return {
11
15
  icon: Archive,
12
- title: isTriggered ? "Compacting Context..." : "Context Compacted",
16
+ title: noActionNeeded ? "Context Check" : "Context Compacted",
13
17
  };
14
18
  }
15
19
  if (hookType === "tool_response") {
20
+ if (isTriggered) {
21
+ return { icon: Scissors, title: "Compacting Response..." };
22
+ }
16
23
  return {
17
24
  icon: Scissors,
18
- title: isTriggered ? "Compacting Response..." : "Tool Response Compacted",
25
+ title: noActionNeeded ? "Response Check" : "Tool Response Compacted",
19
26
  };
20
27
  }
21
28
  // Fallback for unknown hook types
@@ -37,7 +44,7 @@ function formatNumber(num) {
37
44
  export function HookNotification({ notification }) {
38
45
  const [isExpanded, setIsExpanded] = useState(false);
39
46
  const [elapsedTime, setElapsedTime] = useState(0);
40
- const { icon: IconComponent, title } = getHookDisplayInfo(notification.hookType, notification.callback, notification.status);
47
+ const { icon: IconComponent, title } = getHookDisplayInfo(notification.hookType, notification.callback, notification.status, notification.metadata?.action);
41
48
  const isTriggered = notification.status === "triggered";
42
49
  const isCompleted = notification.status === "completed";
43
50
  const isError = notification.status === "error";
@@ -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
+ }
@@ -169,13 +169,17 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
169
169
  flushConsecutiveGroup();
170
170
  return result;
171
171
  };
172
+ // Get tool_response hook notifications to associate with tool calls
173
+ const toolResponseHooks = (message.hookNotifications || []).filter((n) => n.hookType === "tool_response");
172
174
  // Helper to render a tool call or group
173
175
  const renderToolCallOrGroup = (item, index) => {
174
176
  // Batch group (parallel operations)
175
177
  if (typeof item === "object" &&
176
178
  "type" in item &&
177
179
  item.type === "batch") {
178
- return (_jsx(ToolOperation, { toolCalls: item.toolCalls, isGrouped: true }, `batch-${item.toolCalls[0]?.batchId || index}`));
180
+ // Get hook notifications for all tool calls in this batch
181
+ const batchHookNotifications = toolResponseHooks.filter((n) => item.toolCalls.some((tc) => tc.id === n.toolCallId));
182
+ return (_jsx(ToolOperation, { toolCalls: item.toolCalls, isGrouped: true, hookNotifications: batchHookNotifications }, `batch-${item.toolCalls[0]?.batchId || index}`));
179
183
  }
180
184
  // Selecting group (consecutive preliminary tool calls)
181
185
  if (typeof item === "object" &&
@@ -184,10 +188,14 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
184
188
  return (_jsx(ToolOperation, { toolCalls: item.toolCalls, isGrouped: true }, `selecting-${item.toolCalls[0]?.id || index}`));
185
189
  }
186
190
  // Single tool call
187
- return (_jsx(ToolOperation, { toolCalls: [item], isGrouped: false }, item.id));
191
+ const singleToolCall = item;
192
+ const singleHookNotifications = toolResponseHooks.filter((n) => n.toolCallId === singleToolCall.id);
193
+ return (_jsx(ToolOperation, { toolCalls: [singleToolCall], isGrouped: false, hookNotifications: singleHookNotifications }, singleToolCall.id));
188
194
  };
189
- // Get hook notifications and sort by content position
195
+ // Get hook notifications, filter out tool_response hooks (they're shown as sublines on tool calls)
196
+ // and sort by content position
190
197
  const hookNotifications = (message.hookNotifications || [])
198
+ .filter((n) => n.hookType !== "tool_response")
191
199
  .slice()
192
200
  .sort((a, b) => (a.contentPosition ?? Infinity) -
193
201
  (b.contentPosition ?? Infinity));
@@ -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,11 +1,14 @@
1
+ import type { HookNotificationDisplay } from "../../core/schemas/chat.js";
1
2
  import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
2
3
  export interface ToolOperationProps {
3
4
  toolCalls: ToolCallType[];
4
5
  isGrouped?: boolean;
5
6
  autoMinimize?: boolean;
7
+ /** Hook notifications associated with these tool calls (for showing compaction status) */
8
+ hookNotifications?: HookNotificationDisplay[];
6
9
  }
7
10
  /**
8
11
  * ToolOperation component - unified display for tool calls
9
12
  * Handles both individual and grouped tool calls with smooth transitions
10
13
  */
11
- export declare function ToolOperation({ toolCalls, isGrouped, autoMinimize, }: ToolOperationProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function ToolOperation({ toolCalls, isGrouped, autoMinimize, hookNotifications, }: ToolOperationProps): import("react/jsx-runtime").JSX.Element;
@@ -31,7 +31,7 @@ const ICON_MAP = {
31
31
  * ToolOperation component - unified display for tool calls
32
32
  * Handles both individual and grouped tool calls with smooth transitions
33
33
  */
34
- export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = true, }) {
34
+ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = true, hookNotifications = [], }) {
35
35
  const [isExpanded, setIsExpanded] = useState(false);
36
36
  const [isMinimized, setIsMinimized] = useState(false);
37
37
  const [userInteracted, setUserInteracted] = useState(false);
@@ -40,6 +40,10 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
40
40
  // For single tool call, extract it
41
41
  const singleToolCall = toolCalls.length === 1 ? toolCalls[0] : null;
42
42
  const isTodoWrite = singleToolCall?.title === "todo_write";
43
+ // Find hook notification for this tool call (for compaction status)
44
+ const toolHookNotification = singleToolCall
45
+ ? hookNotifications.find((n) => n.toolCallId === singleToolCall.id)
46
+ : undefined;
43
47
  // Detect subagent calls
44
48
  const hasLiveSubagent = !!(singleToolCall?.subagentPort && singleToolCall?.subagentSessionId);
45
49
  const hasStoredSubagent = !!(singleToolCall?.subagentMessages &&
@@ -191,20 +195,48 @@ export function ToolOperation({ toolCalls, isGrouped = false, autoMinimize = tru
191
195
  ease: motionEasing.smooth,
192
196
  }), className: "h-3 w-3 text-text-secondary/70 group-hover:text-text-secondary transition-colors", children: _jsx(ChevronDown, { className: "h-3 w-3" }) }))] }), !isGrouped &&
193
197
  singleToolCall &&
194
- (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 && !isExpanded && (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: displayText }))] }), !isTodoWrite && isSubagentCall && singleToolCall && (_jsx("div", { className: "pl-4.5 mt-2", children: _jsx(SubAgentDetails, { port: singleToolCall.subagentPort, sessionId: singleToolCall.subagentSessionId, parentStatus: singleToolCall.status, agentName: singleToolCall.rawInput?.agentName, query: singleToolCall.rawInput?.query, isExpanded: isSubagentExpanded, onExpandChange: setIsSubagentExpanded, storedMessages: singleToolCall.subagentMessages, isReplay: isReplaySubagent }) })), _jsx(AnimatePresence, { initial: false, children: !isTodoWrite && isExpanded && (_jsx(motion.div, { initial: "collapsed", animate: "expanded", exit: "collapsed", variants: expandCollapseVariants, transition: getTransition(shouldReduceMotion ?? false, {
198
+ (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"
199
+ ? "Compacting response..."
200
+ : toolHookNotification.status === "completed"
201
+ ? toolHookNotification.metadata?.action === "no_action_needed"
202
+ ? `Response checked (${toolHookNotification.currentPercentage?.toFixed(0)}% context)`
203
+ : toolHookNotification.metadata?.tokensSaved
204
+ ? `Response compacted (${((toolHookNotification.metadata.tokensSaved / (toolHookNotification.metadata.originalTokens || 1)) * 100).toFixed(0)}% reduced)`
205
+ : "Response compacted"
206
+ : toolHookNotification.status === "error"
207
+ ? `Compaction error: ${toolHookNotification.error}`
208
+ : null })), isGrouped && !isExpanded && (_jsx("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: displayText })), isGrouped &&
209
+ hookNotifications.length > 0 &&
210
+ (() => {
211
+ const compactedCount = hookNotifications.filter((n) => n.status === "completed" &&
212
+ n.metadata?.action !== "no_action_needed").length;
213
+ const runningCount = hookNotifications.filter((n) => n.status === "triggered").length;
214
+ if (runningCount > 0) {
215
+ return (_jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: ["Compacting ", runningCount, " response", runningCount > 1 ? "s" : "", "..."] }));
216
+ }
217
+ if (compactedCount > 0) {
218
+ return (_jsxs("span", { className: "text-paragraph-sm text-text-secondary/70 pl-4.5", children: [compactedCount, " response", compactedCount > 1 ? "s" : "", " ", "compacted"] }));
219
+ }
220
+ return null;
221
+ })()] }), !isTodoWrite && isSubagentCall && singleToolCall && (_jsx("div", { className: "pl-4.5 mt-2", children: _jsx(SubAgentDetails, { port: singleToolCall.subagentPort, sessionId: singleToolCall.subagentSessionId, parentStatus: singleToolCall.status, agentName: singleToolCall.rawInput?.agentName, query: singleToolCall.rawInput?.query, isExpanded: isSubagentExpanded, onExpandChange: setIsSubagentExpanded, storedMessages: singleToolCall.subagentMessages, isReplay: isReplaySubagent }) })), _jsx(AnimatePresence, { initial: false, children: !isTodoWrite && isExpanded && (_jsx(motion.div, { initial: "collapsed", animate: "expanded", exit: "collapsed", variants: expandCollapseVariants, transition: getTransition(shouldReduceMotion ?? false, {
195
222
  duration: motionDuration.normal,
196
223
  ease: motionEasing.smooth,
197
224
  }), className: "mt-1", children: isGrouped ? (
198
225
  // Render individual tool calls in group
199
- _jsx("div", { className: "flex flex-col gap-4 mt-2", children: toolCalls.map((toolCall) => (_jsx(GroupedToolCallItem, { toolCall: toolCall }, toolCall.id))) })) : (
226
+ _jsx("div", { className: "flex flex-col gap-4 mt-2", children: toolCalls.map((toolCall) => {
227
+ const hookNotification = hookNotifications.find((n) => n.toolCallId === toolCall.id);
228
+ return (_jsx(GroupedToolCallItem, { toolCall: toolCall, ...(hookNotification ? { hookNotification } : {}) }, toolCall.id));
229
+ }) })) : (
200
230
  // Render single tool call details
201
- singleToolCall && (_jsx(ToolOperationDetails, { toolCall: singleToolCall }))) }, "expanded-content")) })] }));
231
+ singleToolCall && (_jsx(ToolOperationDetails, { toolCall: singleToolCall, ...(toolHookNotification
232
+ ? { hookNotification: toolHookNotification }
233
+ : {}) }))) }, "expanded-content")) })] }));
202
234
  }
203
235
  /**
204
236
  * Component to render a single tool call within a grouped parallel operation
205
237
  * Handles subagent calls with their own expansion state
206
238
  */
207
- function GroupedToolCallItem({ toolCall }) {
239
+ function GroupedToolCallItem({ toolCall, hookNotification, }) {
208
240
  const [isSubagentExpanded, setIsSubagentExpanded] = useState(false);
209
241
  // Detect subagent calls
210
242
  const hasLiveSubagent = !!(toolCall.subagentPort && toolCall.subagentSessionId);
@@ -216,12 +248,12 @@ function GroupedToolCallItem({ toolCall }) {
216
248
  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 }) })] }));
217
249
  }
218
250
  // Regular tool call - show details
219
- return (_jsx("div", { className: "flex items-start gap-1.5", children: _jsx("div", { className: "flex-1 ml-5", children: _jsx(ToolOperationDetails, { toolCall: toolCall }) }) }));
251
+ 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 } : {}) }) }) }));
220
252
  }
221
253
  /**
222
254
  * Component to display detailed tool call information
223
255
  */
224
- function ToolOperationDetails({ toolCall }) {
256
+ function ToolOperationDetails({ toolCall, hookNotification, }) {
225
257
  const { resolvedTheme } = useTheme();
226
258
  // Don't show details for preliminary tool calls
227
259
  if (isPreliminaryToolCall(toolCall)) {
@@ -328,5 +360,10 @@ function ToolOperationDetails({ toolCall }) {
328
360
  `:${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()));
329
361
  }
330
362
  return null;
331
- }), toolCall.error && (_jsxs("div", { className: "text-destructive font-mono text-[11px] mt-2", children: ["Error: ", toolCall.error] }))] })] })), 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-b border-border last:border-0 flex flex-wrap gap-4 text-[10px] text-text-secondary 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)"] }))] }))] }))] }));
363
+ }), toolCall.error && (_jsxs("div", { className: "text-destructive font-mono text-[11px] mt-2", children: ["Error: ", toolCall.error] }))] })] })), 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 })] })), hookNotification &&
364
+ hookNotification.status === "completed" &&
365
+ hookNotification.metadata?.action !== "no_action_needed" && (_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: "Response Compaction" }), _jsxs("div", { className: "space-y-1 text-[11px]", children: [hookNotification.metadata?.originalTokens !== undefined && (_jsxs("div", { className: "flex gap-2", children: [_jsx("span", { className: "text-text-secondary", children: "Original:" }), _jsxs("span", { className: "text-foreground", children: [hookNotification.metadata.originalTokens.toLocaleString(), " ", "tokens"] })] })), hookNotification.metadata?.finalTokens !== undefined && (_jsxs("div", { className: "flex gap-2", children: [_jsx("span", { className: "text-text-secondary", children: "Compacted:" }), _jsxs("span", { className: "text-foreground", children: [hookNotification.metadata.finalTokens.toLocaleString(), " ", "tokens"] })] })), hookNotification.metadata?.tokensSaved !== undefined && (_jsxs("div", { className: "flex gap-2", children: [_jsx("span", { className: "text-text-secondary", children: "Saved:" }), _jsxs("span", { className: "text-green-500 font-medium", children: [hookNotification.metadata.tokensSaved.toLocaleString(), " ", "tokens", hookNotification.metadata?.originalTokens && (_jsxs("span", { className: "text-text-secondary font-normal ml-1", children: ["(", ((hookNotification.metadata.tokensSaved /
366
+ hookNotification.metadata
367
+ .originalTokens) *
368
+ 100).toFixed(0), "% reduced)"] }))] })] }))] })] })), (toolCall.tokenUsage || toolCall.startedAt) && (_jsxs("div", { className: "p-2 bg-muted/50 border-b border-border last:border-0 flex flex-wrap gap-4 text-[10px] text-text-secondary 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)"] }))] }))] }))] }));
332
369
  }
@@ -284,6 +284,7 @@ export declare const HookTriggeredNotification: z.ZodObject<{
284
284
  currentPercentage: z.ZodNumber;
285
285
  callback: z.ZodString;
286
286
  triggeredAt: z.ZodOptional<z.ZodNumber>;
287
+ toolCallId: z.ZodOptional<z.ZodString>;
287
288
  }, z.core.$strip>;
288
289
  export type HookTriggeredNotification = z.infer<typeof HookTriggeredNotification>;
289
290
  /**
@@ -302,6 +303,7 @@ export declare const HookCompletedNotification: z.ZodObject<{
302
303
  tokensSaved: z.ZodOptional<z.ZodNumber>;
303
304
  }, z.core.$loose>>;
304
305
  completedAt: z.ZodOptional<z.ZodNumber>;
306
+ toolCallId: z.ZodOptional<z.ZodString>;
305
307
  }, z.core.$strip>;
306
308
  export type HookCompletedNotification = z.infer<typeof HookCompletedNotification>;
307
309
  /**
@@ -316,6 +318,7 @@ export declare const HookErrorNotification: z.ZodObject<{
316
318
  callback: z.ZodString;
317
319
  error: z.ZodString;
318
320
  completedAt: z.ZodOptional<z.ZodNumber>;
321
+ toolCallId: z.ZodOptional<z.ZodString>;
319
322
  }, z.core.$strip>;
320
323
  export type HookErrorNotification = z.infer<typeof HookErrorNotification>;
321
324
  /**
@@ -331,6 +334,7 @@ export declare const HookNotification: z.ZodDiscriminatedUnion<[z.ZodObject<{
331
334
  currentPercentage: z.ZodNumber;
332
335
  callback: z.ZodString;
333
336
  triggeredAt: z.ZodOptional<z.ZodNumber>;
337
+ toolCallId: z.ZodOptional<z.ZodString>;
334
338
  }, z.core.$strip>, z.ZodObject<{
335
339
  type: z.ZodLiteral<"hook_completed">;
336
340
  hookType: z.ZodEnum<{
@@ -344,6 +348,7 @@ export declare const HookNotification: z.ZodDiscriminatedUnion<[z.ZodObject<{
344
348
  tokensSaved: z.ZodOptional<z.ZodNumber>;
345
349
  }, z.core.$loose>>;
346
350
  completedAt: z.ZodOptional<z.ZodNumber>;
351
+ toolCallId: z.ZodOptional<z.ZodString>;
347
352
  }, z.core.$strip>, z.ZodObject<{
348
353
  type: z.ZodLiteral<"hook_error">;
349
354
  hookType: z.ZodEnum<{
@@ -353,6 +358,7 @@ export declare const HookNotification: z.ZodDiscriminatedUnion<[z.ZodObject<{
353
358
  callback: z.ZodString;
354
359
  error: z.ZodString;
355
360
  completedAt: z.ZodOptional<z.ZodNumber>;
361
+ toolCallId: z.ZodOptional<z.ZodString>;
356
362
  }, z.core.$strip>], "type">;
357
363
  export type HookNotification = z.infer<typeof HookNotification>;
358
364
  /**
@@ -371,6 +377,7 @@ export declare const HookNotificationChunk: z.ZodObject<{
371
377
  currentPercentage: z.ZodNumber;
372
378
  callback: z.ZodString;
373
379
  triggeredAt: z.ZodOptional<z.ZodNumber>;
380
+ toolCallId: z.ZodOptional<z.ZodString>;
374
381
  }, z.core.$strip>, z.ZodObject<{
375
382
  type: z.ZodLiteral<"hook_completed">;
376
383
  hookType: z.ZodEnum<{
@@ -384,6 +391,7 @@ export declare const HookNotificationChunk: z.ZodObject<{
384
391
  tokensSaved: z.ZodOptional<z.ZodNumber>;
385
392
  }, z.core.$loose>>;
386
393
  completedAt: z.ZodOptional<z.ZodNumber>;
394
+ toolCallId: z.ZodOptional<z.ZodString>;
387
395
  }, z.core.$strip>, z.ZodObject<{
388
396
  type: z.ZodLiteral<"hook_error">;
389
397
  hookType: z.ZodEnum<{
@@ -393,6 +401,7 @@ export declare const HookNotificationChunk: z.ZodObject<{
393
401
  callback: z.ZodString;
394
402
  error: z.ZodString;
395
403
  completedAt: z.ZodOptional<z.ZodNumber>;
404
+ toolCallId: z.ZodOptional<z.ZodString>;
396
405
  }, z.core.$strip>], "type">;
397
406
  messageId: z.ZodOptional<z.ZodString>;
398
407
  }, z.core.$strip>;
@@ -484,6 +493,7 @@ export declare const MessageChunk: z.ZodDiscriminatedUnion<[z.ZodObject<{
484
493
  currentPercentage: z.ZodNumber;
485
494
  callback: z.ZodString;
486
495
  triggeredAt: z.ZodOptional<z.ZodNumber>;
496
+ toolCallId: z.ZodOptional<z.ZodString>;
487
497
  }, z.core.$strip>, z.ZodObject<{
488
498
  type: z.ZodLiteral<"hook_completed">;
489
499
  hookType: z.ZodEnum<{
@@ -497,6 +507,7 @@ export declare const MessageChunk: z.ZodDiscriminatedUnion<[z.ZodObject<{
497
507
  tokensSaved: z.ZodOptional<z.ZodNumber>;
498
508
  }, z.core.$loose>>;
499
509
  completedAt: z.ZodOptional<z.ZodNumber>;
510
+ toolCallId: z.ZodOptional<z.ZodString>;
500
511
  }, z.core.$strip>, z.ZodObject<{
501
512
  type: z.ZodLiteral<"hook_error">;
502
513
  hookType: z.ZodEnum<{
@@ -506,6 +517,7 @@ export declare const MessageChunk: z.ZodDiscriminatedUnion<[z.ZodObject<{
506
517
  callback: z.ZodString;
507
518
  error: z.ZodString;
508
519
  completedAt: z.ZodOptional<z.ZodNumber>;
520
+ toolCallId: z.ZodOptional<z.ZodString>;
509
521
  }, z.core.$strip>], "type">;
510
522
  messageId: z.ZodOptional<z.ZodString>;
511
523
  }, z.core.$strip>], "type">;
@@ -161,6 +161,7 @@ export const HookTriggeredNotification = z.object({
161
161
  currentPercentage: z.number(),
162
162
  callback: z.string(),
163
163
  triggeredAt: z.number().optional(), // Unix timestamp (ms) from backend
164
+ toolCallId: z.string().optional(), // For tool_response hooks - associates notification with tool call
164
165
  });
165
166
  /**
166
167
  * Hook completed notification
@@ -178,6 +179,7 @@ export const HookCompletedNotification = z.object({
178
179
  .passthrough()
179
180
  .optional(),
180
181
  completedAt: z.number().optional(), // Unix timestamp (ms) from backend
182
+ toolCallId: z.string().optional(), // For tool_response hooks - associates notification with tool call
181
183
  });
182
184
  /**
183
185
  * Hook error notification
@@ -188,6 +190,7 @@ export const HookErrorNotification = z.object({
188
190
  callback: z.string(),
189
191
  error: z.string(),
190
192
  completedAt: z.number().optional(), // Unix timestamp (ms) from backend
193
+ toolCallId: z.string().optional(), // For tool_response hooks - associates notification with tool call
191
194
  });
192
195
  /**
193
196
  * Union of all hook notification types
@@ -4,12 +4,12 @@ import { z } from "zod";
4
4
  */
5
5
  export declare const SessionStatus: z.ZodEnum<{
6
6
  error: "error";
7
- idle: "idle";
7
+ active: "active";
8
+ disconnected: "disconnected";
8
9
  connecting: "connecting";
9
10
  connected: "connected";
10
- active: "active";
11
+ idle: "idle";
11
12
  streaming: "streaming";
12
- disconnected: "disconnected";
13
13
  }>;
14
14
  export type SessionStatus = z.infer<typeof SessionStatus>;
15
15
  /**
@@ -41,12 +41,12 @@ export declare const Session: z.ZodObject<{
41
41
  id: z.ZodString;
42
42
  status: z.ZodEnum<{
43
43
  error: "error";
44
- idle: "idle";
44
+ active: "active";
45
+ disconnected: "disconnected";
45
46
  connecting: "connecting";
46
47
  connected: "connected";
47
- active: "active";
48
+ idle: "idle";
48
49
  streaming: "streaming";
49
- disconnected: "disconnected";
50
50
  }>;
51
51
  config: z.ZodObject<{
52
52
  agentPath: z.ZodString;
@@ -117,12 +117,12 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
117
117
  sessionId: z.ZodString;
118
118
  status: z.ZodOptional<z.ZodEnum<{
119
119
  error: "error";
120
- idle: "idle";
120
+ active: "active";
121
+ disconnected: "disconnected";
121
122
  connecting: "connecting";
122
123
  connected: "connected";
123
- active: "active";
124
+ idle: "idle";
124
125
  streaming: "streaming";
125
- disconnected: "disconnected";
126
126
  }>>;
127
127
  message: z.ZodOptional<z.ZodObject<{
128
128
  id: z.ZodString;
@@ -197,9 +197,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
197
197
  other: "other";
198
198
  }>;
199
199
  status: z.ZodEnum<{
200
+ completed: "completed";
200
201
  pending: "pending";
201
202
  in_progress: "in_progress";
202
- completed: "completed";
203
203
  failed: "failed";
204
204
  }>;
205
205
  contentPosition: z.ZodOptional<z.ZodNumber>;
@@ -265,9 +265,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
265
265
  prettyName: z.ZodOptional<z.ZodString>;
266
266
  icon: z.ZodOptional<z.ZodString>;
267
267
  status: z.ZodEnum<{
268
+ completed: "completed";
268
269
  pending: "pending";
269
270
  in_progress: "in_progress";
270
- completed: "completed";
271
271
  failed: "failed";
272
272
  }>;
273
273
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -310,9 +310,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
310
310
  prettyName: z.ZodOptional<z.ZodString>;
311
311
  icon: z.ZodOptional<z.ZodString>;
312
312
  status: z.ZodEnum<{
313
+ completed: "completed";
313
314
  pending: "pending";
314
315
  in_progress: "in_progress";
315
- completed: "completed";
316
316
  failed: "failed";
317
317
  }>;
318
318
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -354,12 +354,12 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
354
354
  sessionId: z.ZodString;
355
355
  status: z.ZodOptional<z.ZodEnum<{
356
356
  error: "error";
357
- idle: "idle";
357
+ active: "active";
358
+ disconnected: "disconnected";
358
359
  connecting: "connecting";
359
360
  connected: "connected";
360
- active: "active";
361
+ idle: "idle";
361
362
  streaming: "streaming";
362
- disconnected: "disconnected";
363
363
  }>>;
364
364
  message: z.ZodOptional<z.ZodObject<{
365
365
  id: z.ZodString;
@@ -412,9 +412,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
412
412
  toolCallUpdate: z.ZodObject<{
413
413
  id: z.ZodString;
414
414
  status: z.ZodOptional<z.ZodEnum<{
415
+ completed: "completed";
415
416
  pending: "pending";
416
417
  in_progress: "in_progress";
417
- completed: "completed";
418
418
  failed: "failed";
419
419
  }>>;
420
420
  title: z.ZodOptional<z.ZodString>;
@@ -473,9 +473,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
473
473
  prettyName: z.ZodOptional<z.ZodString>;
474
474
  icon: z.ZodOptional<z.ZodString>;
475
475
  status: z.ZodEnum<{
476
+ completed: "completed";
476
477
  pending: "pending";
477
478
  in_progress: "in_progress";
478
- completed: "completed";
479
479
  failed: "failed";
480
480
  }>;
481
481
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -518,9 +518,9 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
518
518
  prettyName: z.ZodOptional<z.ZodString>;
519
519
  icon: z.ZodOptional<z.ZodString>;
520
520
  status: z.ZodEnum<{
521
+ completed: "completed";
521
522
  pending: "pending";
522
523
  in_progress: "in_progress";
523
- completed: "completed";
524
524
  failed: "failed";
525
525
  }>;
526
526
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -561,12 +561,12 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
561
561
  sessionId: z.ZodString;
562
562
  status: z.ZodOptional<z.ZodEnum<{
563
563
  error: "error";
564
- idle: "idle";
564
+ active: "active";
565
+ disconnected: "disconnected";
565
566
  connecting: "connecting";
566
567
  connected: "connected";
567
- active: "active";
568
+ idle: "idle";
568
569
  streaming: "streaming";
569
- disconnected: "disconnected";
570
570
  }>>;
571
571
  message: z.ZodOptional<z.ZodObject<{
572
572
  id: z.ZodString;
@@ -626,12 +626,12 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
626
626
  sessionId: z.ZodString;
627
627
  status: z.ZodOptional<z.ZodEnum<{
628
628
  error: "error";
629
- idle: "idle";
629
+ active: "active";
630
+ disconnected: "disconnected";
630
631
  connecting: "connecting";
631
632
  connected: "connected";
632
- active: "active";
633
+ idle: "idle";
633
634
  streaming: "streaming";
634
- disconnected: "disconnected";
635
635
  }>>;
636
636
  message: z.ZodOptional<z.ZodObject<{
637
637
  id: z.ZodString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/ui",
3
- "version": "0.1.75",
3
+ "version": "0.1.76",
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.53",
52
+ "@townco/core": "0.0.54",
53
53
  "@uiw/react-json-view": "^2.0.0-alpha.39",
54
54
  "bun": "^1.3.1",
55
55
  "class-variance-authority": "^0.7.1",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "devDependencies": {
69
69
  "@tailwindcss/postcss": "^4.1.17",
70
- "@townco/tsconfig": "0.1.72",
70
+ "@townco/tsconfig": "0.1.73",
71
71
  "@types/node": "^24.10.0",
72
72
  "@types/react": "^19.2.2",
73
73
  "ink": "^6.4.0",