@townco/ui 0.1.78 → 0.1.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/hooks/use-chat-messages.d.ts +3 -1
- package/dist/core/hooks/use-tool-calls.d.ts +2 -0
- package/dist/core/schemas/chat.d.ts +7 -3
- package/dist/core/schemas/tool-call.d.ts +13 -0
- package/dist/core/schemas/tool-call.js +15 -0
- package/dist/gui/components/Button.d.ts +1 -1
- package/dist/gui/components/InvokingGroup.d.ts +9 -0
- package/dist/gui/components/InvokingGroup.js +16 -0
- package/dist/gui/components/SubagentStream.d.ts +23 -0
- package/dist/gui/components/SubagentStream.js +98 -0
- package/dist/gui/components/ToolCall.d.ts +8 -0
- package/dist/gui/components/ToolCall.js +234 -0
- package/dist/gui/components/ToolCallGroup.d.ts +8 -0
- package/dist/gui/components/ToolCallGroup.js +29 -0
- package/dist/gui/components/ToolOperation.js +104 -9
- package/dist/sdk/schemas/message.d.ts +2 -2
- package/dist/sdk/schemas/session.d.ts +19 -6
- package/dist/sdk/transports/http.js +90 -1
- package/package.json +3 -3
|
@@ -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: "
|
|
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?: "
|
|
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 &&
|
|
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
|
-
*
|
|
232
|
+
* Each tool call is individually expandable and collapsed by default
|
|
179
233
|
*/
|
|
180
234
|
function GroupedToolCallItem({ toolCall, hookNotification, }) {
|
|
181
|
-
const [
|
|
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: () =>
|
|
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 -
|
|
192
|
-
return (
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.80",
|
|
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.
|
|
52
|
+
"@townco/core": "0.0.58",
|
|
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.
|
|
68
|
+
"@townco/tsconfig": "0.1.77",
|
|
69
69
|
"@types/node": "^24.10.0",
|
|
70
70
|
"@types/react": "^19.2.2",
|
|
71
71
|
"ink": "^6.4.0",
|