@townco/ui 0.1.51 → 0.1.53
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/index.d.ts +1 -0
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/use-chat-messages.d.ts +84 -0
- package/dist/core/hooks/use-chat-session.js +20 -3
- package/dist/core/hooks/use-subagent-stream.d.ts +28 -0
- package/dist/core/hooks/use-subagent-stream.js +254 -0
- package/dist/core/hooks/use-tool-calls.d.ts +84 -0
- package/dist/core/schemas/chat.d.ts +188 -0
- package/dist/core/schemas/tool-call.d.ts +286 -0
- package/dist/core/schemas/tool-call.js +53 -0
- package/dist/gui/components/ChatView.js +8 -2
- package/dist/gui/components/SubAgentDetails.d.ts +27 -0
- package/dist/gui/components/SubAgentDetails.js +121 -0
- package/dist/gui/components/ToolCall.js +39 -7
- package/dist/gui/components/index.d.ts +1 -0
- package/dist/gui/components/index.js +1 -0
- package/dist/sdk/client/acp-client.d.ts +4 -0
- package/dist/sdk/client/acp-client.js +1 -0
- package/dist/sdk/schemas/session.d.ts +96 -0
- package/dist/sdk/transports/http.d.ts +3 -0
- package/dist/sdk/transports/http.js +39 -0
- package/dist/sdk/transports/stdio.d.ts +3 -0
- package/dist/sdk/transports/types.d.ts +9 -0
- package/package.json +3 -3
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
* SubAgentDetails 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 SubAgentDetails({ port, sessionId, host, parentStatus, agentName, query, isExpanded: controlledIsExpanded, onExpandChange, }) {
|
|
15
|
+
const [internalIsExpanded, setInternalIsExpanded] = useState(false);
|
|
16
|
+
// Use controlled state if provided, otherwise use internal state
|
|
17
|
+
const isExpanded = controlledIsExpanded ?? internalIsExpanded;
|
|
18
|
+
const setIsExpanded = onExpandChange ?? setInternalIsExpanded;
|
|
19
|
+
const [isThinkingExpanded, setIsThinkingExpanded] = useState(false);
|
|
20
|
+
const [isNearBottom, setIsNearBottom] = useState(true);
|
|
21
|
+
const thinkingContainerRef = useRef(null);
|
|
22
|
+
const { messages, isStreaming: hookIsStreaming, error, } = useSubagentStream({
|
|
23
|
+
port,
|
|
24
|
+
sessionId,
|
|
25
|
+
...(host !== undefined ? { host } : {}),
|
|
26
|
+
});
|
|
27
|
+
// Use parent status as primary indicator, fall back to hook's streaming state
|
|
28
|
+
// Parent is "in_progress" means sub-agent is definitely still running
|
|
29
|
+
const isRunning = parentStatus === "in_progress" ||
|
|
30
|
+
parentStatus === "pending" ||
|
|
31
|
+
hookIsStreaming;
|
|
32
|
+
// Get the current/latest message
|
|
33
|
+
const currentMessage = messages[messages.length - 1];
|
|
34
|
+
const hasContent = currentMessage &&
|
|
35
|
+
(currentMessage.content ||
|
|
36
|
+
(currentMessage.toolCalls && currentMessage.toolCalls.length > 0) ||
|
|
37
|
+
(currentMessage.contentBlocks &&
|
|
38
|
+
currentMessage.contentBlocks.length > 0));
|
|
39
|
+
// Auto-collapse Thinking when completed (so Output is the primary view)
|
|
40
|
+
const prevIsRunningRef = useRef(isRunning);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (prevIsRunningRef.current && !isRunning) {
|
|
43
|
+
// Just completed - collapse thinking to show output
|
|
44
|
+
setIsThinkingExpanded(false);
|
|
45
|
+
}
|
|
46
|
+
prevIsRunningRef.current = isRunning;
|
|
47
|
+
}, [isRunning]);
|
|
48
|
+
// Check if user is near bottom of scroll area
|
|
49
|
+
const checkScrollPosition = useCallback(() => {
|
|
50
|
+
const container = thinkingContainerRef.current;
|
|
51
|
+
if (!container)
|
|
52
|
+
return;
|
|
53
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
54
|
+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
|
55
|
+
setIsNearBottom(distanceFromBottom < SCROLL_THRESHOLD);
|
|
56
|
+
}, []);
|
|
57
|
+
// Scroll to bottom
|
|
58
|
+
const scrollToBottom = useCallback(() => {
|
|
59
|
+
const container = thinkingContainerRef.current;
|
|
60
|
+
if (!container)
|
|
61
|
+
return;
|
|
62
|
+
container.scrollTop = container.scrollHeight;
|
|
63
|
+
}, []);
|
|
64
|
+
// Auto-scroll when content changes and user is near bottom
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (isNearBottom && (isRunning || hasContent)) {
|
|
67
|
+
scrollToBottom();
|
|
68
|
+
}
|
|
69
|
+
}, [
|
|
70
|
+
currentMessage?.content,
|
|
71
|
+
currentMessage?.toolCalls,
|
|
72
|
+
isNearBottom,
|
|
73
|
+
isRunning,
|
|
74
|
+
hasContent,
|
|
75
|
+
scrollToBottom,
|
|
76
|
+
]);
|
|
77
|
+
// Set up scroll listener
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const container = thinkingContainerRef.current;
|
|
80
|
+
if (!container)
|
|
81
|
+
return;
|
|
82
|
+
const handleScroll = () => checkScrollPosition();
|
|
83
|
+
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
84
|
+
checkScrollPosition(); // Check initial position
|
|
85
|
+
return () => container.removeEventListener("scroll", handleScroll);
|
|
86
|
+
}, [checkScrollPosition, isThinkingExpanded, isExpanded]);
|
|
87
|
+
// Get last line of streaming content for preview
|
|
88
|
+
const lastLine = currentMessage?.content
|
|
89
|
+
? currentMessage.content.split("\n").filter(Boolean).pop() || ""
|
|
90
|
+
: "";
|
|
91
|
+
const previewText = lastLine.length > 100 ? `${lastLine.slice(0, 100)}...` : lastLine;
|
|
92
|
+
// Get first line of query for fallback preview
|
|
93
|
+
const queryFirstLine = query
|
|
94
|
+
? (query.split("\n")[0] ?? "").slice(0, 100) +
|
|
95
|
+
(query.length > 100 ? "..." : "")
|
|
96
|
+
: "";
|
|
97
|
+
return (_jsxs("div", { children: [!isExpanded && (_jsx("div", { className: "w-full max-w-md", children: previewText ? (_jsx("p", { className: "text-paragraph-sm text-muted-foreground/70 truncate", children: previewText })) : queryFirstLine ? (_jsx("p", { className: "text-paragraph-sm text-muted-foreground/50 truncate", children: queryFirstLine })) : 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.contentBlocks &&
|
|
98
|
+
currentMessage.contentBlocks.length > 0
|
|
99
|
+
? // Render interleaved content blocks
|
|
100
|
+
currentMessage.contentBlocks.map((block, idx) => block.type === "text" ? (_jsx("div", { className: "text-[11px] text-foreground whitespace-pre-wrap font-mono", children: block.text }, `text-${idx}`)) : (_jsx(SubagentToolCallItem, { toolCall: block.toolCall }, block.toolCall.id)))
|
|
101
|
+
: // Fallback to legacy content
|
|
102
|
+
currentMessage.content && (_jsx("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 })] }))] }))] }));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Simple tool call display for sub-agent tool calls
|
|
106
|
+
*/
|
|
107
|
+
function SubagentToolCallItem({ toolCall }) {
|
|
108
|
+
const statusIcon = {
|
|
109
|
+
pending: "...",
|
|
110
|
+
in_progress: "",
|
|
111
|
+
completed: "",
|
|
112
|
+
failed: "",
|
|
113
|
+
}[toolCall.status];
|
|
114
|
+
const statusColor = {
|
|
115
|
+
pending: "text-muted-foreground",
|
|
116
|
+
in_progress: "text-blue-500",
|
|
117
|
+
completed: "text-green-500",
|
|
118
|
+
failed: "text-destructive",
|
|
119
|
+
}[toolCall.status];
|
|
120
|
+
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" }))] }));
|
|
121
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import JsonView from "@uiw/react-json-view";
|
|
3
|
-
import { AlertCircle,
|
|
3
|
+
import { AlertCircle, CheckSquare, ChevronDown, ChevronRight, CircleDot, Cloud, Edit, FileText, Globe, Image, Link, Search, Wrench, } from "lucide-react";
|
|
4
4
|
import React, { useState } from "react";
|
|
5
5
|
import { ChatLayout } from "./index.js";
|
|
6
|
+
import { SubAgentDetails } from "./SubAgentDetails.js";
|
|
6
7
|
import { useTheme } from "./ThemeProvider.js";
|
|
7
8
|
/**
|
|
8
9
|
* Map of icon names to Lucide components
|
|
@@ -17,7 +18,6 @@ const ICON_MAP = {
|
|
|
17
18
|
FileText: FileText,
|
|
18
19
|
Edit: Edit,
|
|
19
20
|
Wrench: Wrench,
|
|
20
|
-
BrainCircuit: BrainCircuit,
|
|
21
21
|
CircleDot: CircleDot,
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
@@ -40,12 +40,14 @@ const _kindIcons = {
|
|
|
40
40
|
*/
|
|
41
41
|
export function ToolCall({ toolCall }) {
|
|
42
42
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
43
|
+
const [isSubagentExpanded, setIsSubagentExpanded] = useState(false);
|
|
43
44
|
const { resolvedTheme } = useTheme();
|
|
44
|
-
// Detect TodoWrite tool
|
|
45
|
+
// Detect TodoWrite tool and subagent
|
|
45
46
|
const isTodoWrite = toolCall.title === "todo_write";
|
|
47
|
+
const isSubagentCall = !!(toolCall.subagentPort && toolCall.subagentSessionId);
|
|
46
48
|
// Safely access ChatLayout context - will be undefined if not within ChatLayout
|
|
47
49
|
const layoutContext = React.useContext(ChatLayout.Context);
|
|
48
|
-
// Click handler: toggle sidepanel for TodoWrite, expand for others
|
|
50
|
+
// Click handler: toggle sidepanel for TodoWrite, subagent details for subagents, expand for others
|
|
49
51
|
const handleHeaderClick = React.useCallback(() => {
|
|
50
52
|
if (isTodoWrite && layoutContext) {
|
|
51
53
|
// Toggle sidepanel - close if already open on todo tab, otherwise open
|
|
@@ -58,17 +60,45 @@ export function ToolCall({ toolCall }) {
|
|
|
58
60
|
layoutContext.setActiveTab("todo");
|
|
59
61
|
}
|
|
60
62
|
}
|
|
63
|
+
else if (isSubagentCall) {
|
|
64
|
+
// Toggle subagent details
|
|
65
|
+
setIsSubagentExpanded(!isSubagentExpanded);
|
|
66
|
+
}
|
|
61
67
|
else {
|
|
62
68
|
// Normal expand/collapse
|
|
63
69
|
setIsExpanded(!isExpanded);
|
|
64
70
|
}
|
|
65
|
-
}, [
|
|
71
|
+
}, [
|
|
72
|
+
isTodoWrite,
|
|
73
|
+
layoutContext,
|
|
74
|
+
isExpanded,
|
|
75
|
+
isSubagentCall,
|
|
76
|
+
isSubagentExpanded,
|
|
77
|
+
]);
|
|
66
78
|
// Determine which icon to show
|
|
67
79
|
const IconComponent = toolCall.icon && ICON_MAP[toolCall.icon]
|
|
68
80
|
? ICON_MAP[toolCall.icon]
|
|
69
81
|
: CircleDot;
|
|
70
82
|
// Determine display name
|
|
71
83
|
const displayName = toolCall.prettyName || toolCall.title;
|
|
84
|
+
// Determine icon color based on status (especially for subagents)
|
|
85
|
+
const isSubagentRunning = isSubagentCall &&
|
|
86
|
+
(toolCall.status === "in_progress" || toolCall.status === "pending");
|
|
87
|
+
const isSubagentFailed = isSubagentCall && toolCall.status === "failed";
|
|
88
|
+
const iconColorClass = isSubagentCall
|
|
89
|
+
? isSubagentFailed
|
|
90
|
+
? "text-destructive"
|
|
91
|
+
: isSubagentRunning
|
|
92
|
+
? "text-foreground animate-pulse"
|
|
93
|
+
: "text-green-500"
|
|
94
|
+
: "text-muted-foreground";
|
|
95
|
+
const statusTooltip = isSubagentCall
|
|
96
|
+
? isSubagentFailed
|
|
97
|
+
? "Sub-agent failed"
|
|
98
|
+
: isSubagentRunning
|
|
99
|
+
? "Sub-agent running"
|
|
100
|
+
: "Sub-agent completed"
|
|
101
|
+
: undefined;
|
|
72
102
|
// Check if there's an error
|
|
73
103
|
const hasError = toolCall.status === "failed" || !!toolCall.error;
|
|
74
104
|
// Check if this is a preliminary (pending) tool call without full details yet
|
|
@@ -109,9 +139,11 @@ export function ToolCall({ toolCall }) {
|
|
|
109
139
|
if (isPreliminary) {
|
|
110
140
|
return (_jsx("div", { className: "flex flex-col my-4", children: _jsxs("span", { className: "text-paragraph-sm text-muted-foreground/50", children: ["Invoking ", displayName] }) }));
|
|
111
141
|
}
|
|
112
|
-
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:
|
|
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 &&
|
|
143
|
+
Object.keys(toolCall.rawInput).length > 0 &&
|
|
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 &&
|
|
113
145
|
loc.line !== undefined &&
|
|
114
|
-
`:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })),
|
|
146
|
+
`:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), (toolCall.content && toolCall.content.length > 0) ||
|
|
115
147
|
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) => {
|
|
116
148
|
// Generate a stable key based on content
|
|
117
149
|
const getBlockKey = () => {
|
|
@@ -34,6 +34,7 @@ export { Response, type ResponseProps } from "./Response.js";
|
|
|
34
34
|
export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, } from "./Select.js";
|
|
35
35
|
export { Toaster } from "./Sonner.js";
|
|
36
36
|
export { type SourceItem, SourceListItem, type SourceListItemProps, } from "./SourceListItem.js";
|
|
37
|
+
export { SubAgentDetails, type SubAgentDetailsProps, } from "./SubAgentDetails.js";
|
|
37
38
|
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs.js";
|
|
38
39
|
export { Task, type TaskItem, TaskList, type TaskListProps, type TaskProps, } from "./Task.js";
|
|
39
40
|
export { Textarea, type TextareaProps, textareaVariants } from "./Textarea.js";
|
|
@@ -40,6 +40,7 @@ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScro
|
|
|
40
40
|
export { Toaster } from "./Sonner.js";
|
|
41
41
|
// Toast components
|
|
42
42
|
export { SourceListItem, } from "./SourceListItem.js";
|
|
43
|
+
export { SubAgentDetails, } from "./SubAgentDetails.js";
|
|
43
44
|
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs.js";
|
|
44
45
|
// Task/Todo components
|
|
45
46
|
export { Task, TaskList, } from "./Task.js";
|
|
@@ -93,6 +93,7 @@ export declare class AcpClient {
|
|
|
93
93
|
* - tools: List of tools available to the agent
|
|
94
94
|
* - mcps: List of MCP servers connected to the agent
|
|
95
95
|
* - subagents: List of subagents available via Task tool
|
|
96
|
+
* - uiConfig: UI configuration for interface appearance
|
|
96
97
|
*/
|
|
97
98
|
getAgentInfo(): {
|
|
98
99
|
name?: string;
|
|
@@ -100,6 +101,9 @@ export declare class AcpClient {
|
|
|
100
101
|
version?: string;
|
|
101
102
|
description?: string;
|
|
102
103
|
suggestedPrompts?: string[];
|
|
104
|
+
uiConfig?: {
|
|
105
|
+
hideTopBar?: boolean;
|
|
106
|
+
};
|
|
103
107
|
tools?: Array<{
|
|
104
108
|
name: string;
|
|
105
109
|
description?: string;
|
|
@@ -245,6 +245,7 @@ export class AcpClient {
|
|
|
245
245
|
* - tools: List of tools available to the agent
|
|
246
246
|
* - mcps: List of MCP servers connected to the agent
|
|
247
247
|
* - subagents: List of subagents available via Task tool
|
|
248
|
+
* - uiConfig: UI configuration for interface appearance
|
|
248
249
|
*/
|
|
249
250
|
getAgentInfo() {
|
|
250
251
|
return this.transport.getAgentInfo?.() || {};
|
|
@@ -249,6 +249,100 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
249
249
|
originalTokens: z.ZodOptional<z.ZodNumber>;
|
|
250
250
|
finalTokens: z.ZodOptional<z.ZodNumber>;
|
|
251
251
|
}, z.core.$strip>>;
|
|
252
|
+
subagentPort: z.ZodOptional<z.ZodNumber>;
|
|
253
|
+
subagentSessionId: z.ZodOptional<z.ZodString>;
|
|
254
|
+
subagentMessages: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
255
|
+
id: z.ZodString;
|
|
256
|
+
content: z.ZodString;
|
|
257
|
+
toolCalls: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
258
|
+
id: z.ZodString;
|
|
259
|
+
title: z.ZodString;
|
|
260
|
+
prettyName: z.ZodOptional<z.ZodString>;
|
|
261
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
262
|
+
status: z.ZodEnum<{
|
|
263
|
+
pending: "pending";
|
|
264
|
+
in_progress: "in_progress";
|
|
265
|
+
completed: "completed";
|
|
266
|
+
failed: "failed";
|
|
267
|
+
}>;
|
|
268
|
+
content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
269
|
+
type: z.ZodLiteral<"content">;
|
|
270
|
+
content: z.ZodObject<{
|
|
271
|
+
type: z.ZodLiteral<"text">;
|
|
272
|
+
text: z.ZodString;
|
|
273
|
+
}, z.core.$strip>;
|
|
274
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
275
|
+
type: z.ZodLiteral<"text">;
|
|
276
|
+
text: z.ZodString;
|
|
277
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
278
|
+
type: z.ZodLiteral<"image">;
|
|
279
|
+
data: z.ZodString;
|
|
280
|
+
mimeType: z.ZodOptional<z.ZodString>;
|
|
281
|
+
alt: z.ZodOptional<z.ZodString>;
|
|
282
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
283
|
+
type: z.ZodLiteral<"image">;
|
|
284
|
+
url: z.ZodString;
|
|
285
|
+
alt: z.ZodOptional<z.ZodString>;
|
|
286
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
287
|
+
type: z.ZodLiteral<"diff">;
|
|
288
|
+
path: z.ZodString;
|
|
289
|
+
oldText: z.ZodString;
|
|
290
|
+
newText: z.ZodString;
|
|
291
|
+
line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
292
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
293
|
+
type: z.ZodLiteral<"terminal">;
|
|
294
|
+
terminalId: z.ZodString;
|
|
295
|
+
}, z.core.$strip>], "type">>>;
|
|
296
|
+
}, z.core.$strip>>>;
|
|
297
|
+
contentBlocks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
298
|
+
type: z.ZodLiteral<"text">;
|
|
299
|
+
text: z.ZodString;
|
|
300
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
301
|
+
type: z.ZodLiteral<"tool_call">;
|
|
302
|
+
toolCall: z.ZodObject<{
|
|
303
|
+
id: z.ZodString;
|
|
304
|
+
title: z.ZodString;
|
|
305
|
+
prettyName: z.ZodOptional<z.ZodString>;
|
|
306
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
307
|
+
status: z.ZodEnum<{
|
|
308
|
+
pending: "pending";
|
|
309
|
+
in_progress: "in_progress";
|
|
310
|
+
completed: "completed";
|
|
311
|
+
failed: "failed";
|
|
312
|
+
}>;
|
|
313
|
+
content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
314
|
+
type: z.ZodLiteral<"content">;
|
|
315
|
+
content: z.ZodObject<{
|
|
316
|
+
type: z.ZodLiteral<"text">;
|
|
317
|
+
text: z.ZodString;
|
|
318
|
+
}, z.core.$strip>;
|
|
319
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
320
|
+
type: z.ZodLiteral<"text">;
|
|
321
|
+
text: z.ZodString;
|
|
322
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
323
|
+
type: z.ZodLiteral<"image">;
|
|
324
|
+
data: z.ZodString;
|
|
325
|
+
mimeType: z.ZodOptional<z.ZodString>;
|
|
326
|
+
alt: z.ZodOptional<z.ZodString>;
|
|
327
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
328
|
+
type: z.ZodLiteral<"image">;
|
|
329
|
+
url: z.ZodString;
|
|
330
|
+
alt: z.ZodOptional<z.ZodString>;
|
|
331
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
332
|
+
type: z.ZodLiteral<"diff">;
|
|
333
|
+
path: z.ZodString;
|
|
334
|
+
oldText: z.ZodString;
|
|
335
|
+
newText: z.ZodString;
|
|
336
|
+
line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
337
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
338
|
+
type: z.ZodLiteral<"terminal">;
|
|
339
|
+
terminalId: z.ZodString;
|
|
340
|
+
}, z.core.$strip>], "type">>>;
|
|
341
|
+
}, z.core.$strip>;
|
|
342
|
+
}, z.core.$strip>], "type">>>;
|
|
343
|
+
isStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
344
|
+
}, z.core.$strip>>>;
|
|
345
|
+
subagentStreaming: z.ZodOptional<z.ZodBoolean>;
|
|
252
346
|
}, z.core.$strip>;
|
|
253
347
|
messageId: z.ZodOptional<z.ZodString>;
|
|
254
348
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -363,6 +457,8 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
363
457
|
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
364
458
|
totalTokens: z.ZodOptional<z.ZodNumber>;
|
|
365
459
|
}, z.core.$strip>>;
|
|
460
|
+
subagentPort: z.ZodOptional<z.ZodNumber>;
|
|
461
|
+
subagentSessionId: z.ZodOptional<z.ZodString>;
|
|
366
462
|
}, z.core.$strip>;
|
|
367
463
|
messageId: z.ZodOptional<z.ZodString>;
|
|
368
464
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -71,6 +71,12 @@ export class HttpTransport {
|
|
|
71
71
|
typeof meta.initialMessage === "object"
|
|
72
72
|
? meta.initialMessage
|
|
73
73
|
: undefined;
|
|
74
|
+
const uiConfig = metaIsObject &&
|
|
75
|
+
"uiConfig" in meta &&
|
|
76
|
+
meta.uiConfig &&
|
|
77
|
+
typeof meta.uiConfig === "object"
|
|
78
|
+
? meta.uiConfig
|
|
79
|
+
: undefined;
|
|
74
80
|
this.agentInfo = {
|
|
75
81
|
name: initResponse.agentInfo.name,
|
|
76
82
|
// title is the ACP field for human-readable display name
|
|
@@ -81,6 +87,7 @@ export class HttpTransport {
|
|
|
81
87
|
...(description ? { description } : {}),
|
|
82
88
|
...(suggestedPrompts ? { suggestedPrompts } : {}),
|
|
83
89
|
...(initialMessage ? { initialMessage } : {}),
|
|
90
|
+
...(uiConfig ? { uiConfig } : {}),
|
|
84
91
|
...(tools ? { tools } : {}),
|
|
85
92
|
...(mcps ? { mcps } : {}),
|
|
86
93
|
...(subagents ? { subagents } : {}),
|
|
@@ -159,6 +166,12 @@ export class HttpTransport {
|
|
|
159
166
|
typeof meta.initialMessage === "object"
|
|
160
167
|
? meta.initialMessage
|
|
161
168
|
: undefined;
|
|
169
|
+
const uiConfig = metaIsObject &&
|
|
170
|
+
"uiConfig" in meta &&
|
|
171
|
+
meta.uiConfig &&
|
|
172
|
+
typeof meta.uiConfig === "object"
|
|
173
|
+
? meta.uiConfig
|
|
174
|
+
: undefined;
|
|
162
175
|
this.agentInfo = {
|
|
163
176
|
name: initResponse.agentInfo.name,
|
|
164
177
|
// title is the ACP field for human-readable display name
|
|
@@ -169,6 +182,7 @@ export class HttpTransport {
|
|
|
169
182
|
...(description ? { description } : {}),
|
|
170
183
|
...(suggestedPrompts ? { suggestedPrompts } : {}),
|
|
171
184
|
...(initialMessage ? { initialMessage } : {}),
|
|
185
|
+
...(uiConfig ? { uiConfig } : {}),
|
|
172
186
|
...(tools ? { tools } : {}),
|
|
173
187
|
...(mcps ? { mcps } : {}),
|
|
174
188
|
...(subagents ? { subagents } : {}),
|
|
@@ -773,6 +787,28 @@ export class HttpTransport {
|
|
|
773
787
|
typeof update._meta.batchId === "string"
|
|
774
788
|
? update._meta.batchId
|
|
775
789
|
: undefined;
|
|
790
|
+
// Extract subagent connection info from _meta
|
|
791
|
+
const subagentPort = update._meta &&
|
|
792
|
+
typeof update._meta === "object" &&
|
|
793
|
+
"subagentPort" in update._meta &&
|
|
794
|
+
typeof update._meta.subagentPort === "number"
|
|
795
|
+
? update._meta.subagentPort
|
|
796
|
+
: undefined;
|
|
797
|
+
const subagentSessionId = update._meta &&
|
|
798
|
+
typeof update._meta === "object" &&
|
|
799
|
+
"subagentSessionId" in update._meta &&
|
|
800
|
+
typeof update._meta.subagentSessionId === "string"
|
|
801
|
+
? update._meta.subagentSessionId
|
|
802
|
+
: undefined;
|
|
803
|
+
// Debug logging for subagent connection info
|
|
804
|
+
if (subagentPort || subagentSessionId) {
|
|
805
|
+
logger.info("Extracted subagent connection info from tool_call_update", {
|
|
806
|
+
toolCallId: update.toolCallId,
|
|
807
|
+
subagentPort,
|
|
808
|
+
subagentSessionId,
|
|
809
|
+
_meta: update._meta,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
776
812
|
// Tool call update notification
|
|
777
813
|
const toolCallUpdate = {
|
|
778
814
|
id: update.toolCallId ?? "",
|
|
@@ -835,6 +871,9 @@ export class HttpTransport {
|
|
|
835
871
|
completedAt: update.status === "completed" || update.status === "failed"
|
|
836
872
|
? Date.now()
|
|
837
873
|
: undefined,
|
|
874
|
+
// Sub-agent connection info for direct SSE streaming
|
|
875
|
+
subagentPort,
|
|
876
|
+
subagentSessionId,
|
|
838
877
|
};
|
|
839
878
|
const sessionUpdate = {
|
|
840
879
|
type: "tool_call_update",
|
|
@@ -31,6 +31,13 @@ export interface AgentInitialMessage {
|
|
|
31
31
|
/** The content of the initial message */
|
|
32
32
|
content: string;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* UI configuration for agents
|
|
36
|
+
*/
|
|
37
|
+
export interface AgentUiConfig {
|
|
38
|
+
/** Whether to hide the top bar (session switcher, debugger link, settings) */
|
|
39
|
+
hideTopBar?: boolean;
|
|
40
|
+
}
|
|
34
41
|
/**
|
|
35
42
|
* Session summary for listing
|
|
36
43
|
*/
|
|
@@ -90,6 +97,7 @@ export interface Transport {
|
|
|
90
97
|
* - mcps: List of MCP servers connected to the agent
|
|
91
98
|
* - subagents: List of subagents available via Task tool
|
|
92
99
|
* - initialMessage: Configuration for agent's initial message on session start
|
|
100
|
+
* - uiConfig: UI configuration for controlling interface appearance
|
|
93
101
|
*/
|
|
94
102
|
getAgentInfo?(): {
|
|
95
103
|
name?: string;
|
|
@@ -98,6 +106,7 @@ export interface Transport {
|
|
|
98
106
|
description?: string;
|
|
99
107
|
suggestedPrompts?: string[];
|
|
100
108
|
initialMessage?: AgentInitialMessage;
|
|
109
|
+
uiConfig?: AgentUiConfig;
|
|
101
110
|
tools?: AgentToolInfo[];
|
|
102
111
|
mcps?: AgentMcpInfo[];
|
|
103
112
|
subagents?: AgentSubagentInfo[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.53",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@agentclientprotocol/sdk": "^0.5.1",
|
|
47
|
-
"@townco/core": "0.0.
|
|
47
|
+
"@townco/core": "0.0.31",
|
|
48
48
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
49
49
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
50
50
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@tailwindcss/postcss": "^4.1.17",
|
|
70
|
-
"@townco/tsconfig": "0.1.
|
|
70
|
+
"@townco/tsconfig": "0.1.50",
|
|
71
71
|
"@types/node": "^24.10.0",
|
|
72
72
|
"@types/react": "^19.2.2",
|
|
73
73
|
"ink": "^6.4.0",
|