@townco/ui 0.1.127 → 0.1.128
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.
|
@@ -44,15 +44,7 @@ export function ToolCall({ toolCall }) {
|
|
|
44
44
|
const { resolvedTheme } = useTheme();
|
|
45
45
|
// Detect TodoWrite tool and subagent
|
|
46
46
|
const isTodoWrite = toolCall.title === "todo_write";
|
|
47
|
-
|
|
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;
|
|
47
|
+
const isSubagentCall = !!(toolCall.subagentPort && toolCall.subagentSessionId);
|
|
56
48
|
// Safely access ChatLayout context - will be undefined if not within ChatLayout
|
|
57
49
|
const layoutContext = React.useContext(ChatLayout.Context);
|
|
58
50
|
// Click handler: toggle sidepanel for TodoWrite, subagent details for subagents, expand for others
|
|
@@ -147,7 +139,7 @@ export function ToolCall({ toolCall }) {
|
|
|
147
139
|
if (isPreliminary) {
|
|
148
140
|
return (_jsx("div", { className: "flex flex-col my-4", children: _jsxs("span", { className: "text-paragraph-sm text-muted-foreground/50", children: ["Invoking ", displayName] }) }));
|
|
149
141
|
}
|
|
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
|
|
142
|
+
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 }) })), !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
143
|
Object.keys(toolCall.rawInput).length > 0 &&
|
|
152
144
|
!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
145
|
loc.line !== undefined &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.128",
|
|
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.106",
|
|
53
53
|
"@types/mdast": "^4.0.4",
|
|
54
54
|
"@uiw/react-json-view": "^2.0.0-alpha.39",
|
|
55
55
|
"class-variance-authority": "^0.7.1",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"zustand": "^5.0.8"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@townco/tsconfig": "0.1.
|
|
70
|
+
"@townco/tsconfig": "0.1.125",
|
|
71
71
|
"@types/node": "^24.10.0",
|
|
72
72
|
"@types/react": "^19.2.2",
|
|
73
73
|
"@types/unist": "^3.0.3",
|
|
@@ -1,23 +0,0 @@
|
|
|
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;
|
|
@@ -1,98 +0,0 @@
|
|
|
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
|
-
}
|