@townco/ui 0.1.67 → 0.1.69

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.
Files changed (56) hide show
  1. package/dist/core/hooks/use-chat-messages.d.ts +5 -0
  2. package/dist/core/hooks/use-tool-calls.d.ts +5 -0
  3. package/dist/core/hooks/use-tool-calls.js +2 -1
  4. package/dist/core/schemas/chat.d.ts +10 -0
  5. package/dist/core/schemas/tool-call.d.ts +96 -0
  6. package/dist/core/schemas/tool-call.js +12 -0
  7. package/dist/core/utils/tool-call-state.d.ts +30 -0
  8. package/dist/core/utils/tool-call-state.js +73 -0
  9. package/dist/core/utils/tool-summary.d.ts +13 -0
  10. package/dist/core/utils/tool-summary.js +172 -0
  11. package/dist/core/utils/tool-verbiage.d.ts +28 -0
  12. package/dist/core/utils/tool-verbiage.js +185 -0
  13. package/dist/gui/components/AppSidebar.d.ts +22 -0
  14. package/dist/gui/components/AppSidebar.js +22 -0
  15. package/dist/gui/components/ChatLayout.d.ts +5 -0
  16. package/dist/gui/components/ChatLayout.js +239 -132
  17. package/dist/gui/components/ChatView.js +42 -118
  18. package/dist/gui/components/MessageContent.js +199 -49
  19. package/dist/gui/components/SessionHistory.d.ts +10 -0
  20. package/dist/gui/components/SessionHistory.js +101 -0
  21. package/dist/gui/components/SessionHistoryItem.d.ts +11 -0
  22. package/dist/gui/components/SessionHistoryItem.js +24 -0
  23. package/dist/gui/components/Sheet.d.ts +25 -0
  24. package/dist/gui/components/Sheet.js +36 -0
  25. package/dist/gui/components/Sidebar.d.ts +65 -0
  26. package/dist/gui/components/Sidebar.js +231 -0
  27. package/dist/gui/components/SidebarToggle.d.ts +3 -0
  28. package/dist/gui/components/SidebarToggle.js +9 -0
  29. package/dist/gui/components/SubAgentDetails.d.ts +13 -6
  30. package/dist/gui/components/SubAgentDetails.js +29 -14
  31. package/dist/gui/components/ToolCallList.js +3 -3
  32. package/dist/gui/components/ToolOperation.d.ts +11 -0
  33. package/dist/gui/components/ToolOperation.js +289 -0
  34. package/dist/gui/components/WorkProgress.d.ts +20 -0
  35. package/dist/gui/components/WorkProgress.js +79 -0
  36. package/dist/gui/components/index.d.ts +8 -1
  37. package/dist/gui/components/index.js +9 -1
  38. package/dist/gui/hooks/index.d.ts +1 -0
  39. package/dist/gui/hooks/index.js +1 -0
  40. package/dist/gui/hooks/use-mobile.d.ts +1 -0
  41. package/dist/gui/hooks/use-mobile.js +15 -0
  42. package/dist/gui/index.d.ts +1 -0
  43. package/dist/gui/index.js +2 -0
  44. package/dist/gui/lib/motion.d.ts +55 -0
  45. package/dist/gui/lib/motion.js +217 -0
  46. package/dist/sdk/schemas/session.d.ts +102 -6
  47. package/dist/sdk/transports/http.js +105 -37
  48. package/dist/sdk/transports/types.d.ts +5 -0
  49. package/package.json +8 -7
  50. package/src/styles/global.css +128 -1
  51. package/dist/gui/components/InvokingGroup.d.ts +0 -9
  52. package/dist/gui/components/InvokingGroup.js +0 -16
  53. package/dist/gui/components/ToolCall.d.ts +0 -8
  54. package/dist/gui/components/ToolCall.js +0 -226
  55. package/dist/gui/components/ToolCallGroup.d.ts +0 -8
  56. package/dist/gui/components/ToolCallGroup.js +0 -29
@@ -1,12 +1,11 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createLogger } from "@townco/core";
3
- import { ArrowUp, Bug, ChevronDown, ChevronUp, Code, PanelRight, Plus, Settings, Sparkles, X, } from "lucide-react";
4
- import { useCallback, useEffect, useState } from "react";
3
+ import { ArrowUp, Bug, ChevronUp, PanelRight, Settings, X } from "lucide-react";
4
+ import { useEffect, useState } from "react";
5
5
  import { useChatMessages, useChatSession, useToolCalls, } from "../../core/hooks/index.js";
6
6
  import { selectTodosForCurrentSession, useChatStore, } from "../../core/store/chat-store.js";
7
- import { calculateTokenPercentage, formatTokenPercentage, } from "../../core/utils/model-context.js";
8
7
  import { cn } from "../lib/utils.js";
9
- import { ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, FilesTabContent, IconButton, Message, MessageContent, PanelTabsHeader, SettingsTabContent, SourcesTabContent, Tabs, TabsContent, ThemeToggle, TodoTabContent, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./index.js";
8
+ import { AppSidebar, ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, FilesTabContent, IconButton, Message, MessageContent, PanelTabsHeader, SettingsTabContent, SidebarInset, SidebarProvider, SidebarToggle, SourcesTabContent, Tabs, TabsContent, ThemeToggle, TodoTabContent, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./index.js";
10
9
  const logger = createLogger("gui");
11
10
  // Helper component to provide openFiles callback
12
11
  function OpenFilesButton({ children, }) {
@@ -31,85 +30,8 @@ function OpenFilesButton({ children, }) {
31
30
  };
32
31
  return _jsx(_Fragment, { children: children({ openFiles, openSettings }) });
33
32
  }
34
- // Hook to handle sidebar keyboard shortcut (Cmd+B / Ctrl+B)
35
- function SidebarHotkey() {
36
- const { panelSize, setPanelSize } = ChatLayout.useChatLayoutContext();
37
- useEffect(() => {
38
- const handleKeyDown = (e) => {
39
- // Cmd+B (Mac) or Ctrl+B (Windows/Linux)
40
- if ((e.metaKey || e.ctrlKey) && e.key === "b") {
41
- e.preventDefault();
42
- setPanelSize(panelSize === "hidden" ? "small" : "hidden");
43
- }
44
- };
45
- window.addEventListener("keydown", handleKeyDown);
46
- return () => window.removeEventListener("keydown", handleKeyDown);
47
- }, [panelSize, setPanelSize]);
48
- return null;
49
- }
50
- // Format relative time from date string
51
- function formatRelativeTime(dateString) {
52
- const date = new Date(dateString);
53
- const now = new Date();
54
- const diffMs = now.getTime() - date.getTime();
55
- const diffMins = Math.floor(diffMs / 60000);
56
- const diffHours = Math.floor(diffMs / 3600000);
57
- const diffDays = Math.floor(diffMs / 86400000);
58
- if (diffMins < 1)
59
- return "Just now";
60
- if (diffMins < 60)
61
- return `${diffMins}m ago`;
62
- if (diffHours < 24)
63
- return `${diffHours}h ago`;
64
- if (diffDays < 7)
65
- return `${diffDays}d ago`;
66
- return date.toLocaleDateString();
67
- }
68
- // Session switcher dropdown component
69
- function SessionSwitcher({ agentName, client, currentSessionId, }) {
70
- const [sessions, setSessions] = useState([]);
71
- const [isLoading, setIsLoading] = useState(false);
72
- const [isOpen, setIsOpen] = useState(false);
73
- const fetchSessions = useCallback(async () => {
74
- if (!client)
75
- return;
76
- setIsLoading(true);
77
- try {
78
- const sessionList = await client.listSessions();
79
- setSessions(sessionList);
80
- }
81
- catch (error) {
82
- logger.error("Failed to fetch sessions", { error });
83
- }
84
- finally {
85
- setIsLoading(false);
86
- }
87
- }, [client]);
88
- // Fetch sessions when dropdown opens
89
- useEffect(() => {
90
- if (isOpen) {
91
- fetchSessions();
92
- }
93
- }, [isOpen, fetchSessions]);
94
- const handleNewSession = () => {
95
- // Clear session from URL and reload to start fresh
96
- const url = new URL(window.location.href);
97
- url.searchParams.delete("session");
98
- window.location.href = url.toString();
99
- };
100
- const handleSessionSelect = (sessionId) => {
101
- if (sessionId === currentSessionId) {
102
- setIsOpen(false);
103
- return;
104
- }
105
- // Update URL with session ID and reload
106
- const url = new URL(window.location.href);
107
- url.searchParams.set("session", sessionId);
108
- window.location.href = url.toString();
109
- };
110
- return (_jsxs(DropdownMenu, { open: isOpen, onOpenChange: setIsOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex items-center gap-1 text-heading-4 text-foreground hover:text-foreground/80 transition-colors cursor-pointer", children: [agentName, _jsx(ChevronDown, { className: cn("size-4 text-muted-foreground transition-transform duration-200", isOpen && "rotate-180") })] }) }), _jsxs(DropdownMenuContent, { align: "start", className: "w-72", children: [_jsxs(DropdownMenuLabel, { className: "flex items-center justify-between", children: [_jsx("span", { children: "Sessions" }), isLoading && (_jsx("span", { className: "text-caption text-muted-foreground", children: "Loading..." }))] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: handleNewSession, className: "gap-2", children: [_jsx(Plus, { className: "size-4" }), _jsx("span", { children: "New Session" })] }), sessions.length > 0 && _jsx(DropdownMenuSeparator, {}), _jsx("div", { className: "max-h-64 overflow-y-auto", children: sessions.map((session) => (_jsxs(DropdownMenuItem, { onClick: () => handleSessionSelect(session.sessionId), className: cn("flex flex-col items-start gap-0.5 py-2", session.sessionId === currentSessionId &&
111
- "bg-muted/50 font-medium"), children: [_jsxs("div", { className: "flex w-full items-center justify-between gap-2", children: [_jsx("span", { className: "truncate text-paragraph-sm", children: session.firstUserMessage || "Empty session" }), session.sessionId === currentSessionId && (_jsx("span", { className: "shrink-0 text-caption text-primary", children: "Current" }))] }), _jsxs("span", { className: "text-caption text-muted-foreground", children: [formatRelativeTime(session.updatedAt), " \u2022 ", session.messageCount, " ", "messages"] })] }, session.sessionId))) }), sessions.length === 0 && !isLoading && (_jsx("div", { className: "px-2 py-4 text-center text-paragraph-sm text-muted-foreground", children: "No previous sessions" }))] })] }));
112
- }
33
+ // Note: Keyboard shortcut (Cmd+B / Ctrl+B) for toggling the right panel
34
+ // is now handled internally by ChatLayout.Root
113
35
  // Chat input with attachment handling
114
36
  function ChatInputWithAttachments({ client, startSession, placeholder, latestContextSize, currentModel, commandMenuItems, }) {
115
37
  const attachedFiles = useChatStore((state) => state.input.attachedFiles);
@@ -128,21 +50,19 @@ function AsideTabs({ todos, tools, mcps, subagents, }) {
128
50
  return (_jsxs(Tabs, { value: activeTab, onValueChange: (value) => setActiveTab(value), className: "flex flex-col h-full", children: [_jsx("div", { className: cn("border-b border-border bg-card", "px-4 py-2 h-16", "flex items-center", "[border-bottom-width:0.5px]"), children: _jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources", "settings"], variant: "compact" }) }), _jsx(TabsContent, { value: "todo", className: "flex-1 p-4 mt-0", children: _jsx(TodoTabContent, { todos: todos }) }), _jsx(TabsContent, { value: "files", className: "flex-1 p-4 mt-0", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "flex-1 p-4 mt-0", children: _jsx(SourcesTabContent, {}) }), _jsx(TabsContent, { value: "settings", className: "flex-1 p-4 mt-0 overflow-y-auto", children: _jsx(SettingsTabContent, { tools: tools ?? [], mcps: mcps ?? [], subagents: subagents ?? [] }) })] }));
129
51
  }
130
52
  // Mobile header component that uses ChatHeader context
131
- function MobileHeader({ agentName, showHeader, client, currentSessionId, }) {
53
+ function MobileHeader({ agentName, showHeader, }) {
132
54
  const { isExpanded, setIsExpanded } = ChatHeader.useChatHeaderContext();
133
- return (_jsxs("div", { className: "flex lg:hidden items-center flex-1", children: [showHeader && (_jsx("div", { className: "flex items-center gap-2 flex-1", children: _jsx(SessionSwitcher, { agentName: agentName, client: client, currentSessionId: currentSessionId }) })), !showHeader && _jsx("div", { className: "flex-1" }), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle menu", onClick: () => setIsExpanded(!isExpanded), children: _jsx(ChevronUp, { className: cn("size-4 text-muted-foreground transition-transform duration-200", isExpanded ? "" : "rotate-180") }) })] }));
55
+ return (_jsxs("div", { className: "flex lg:hidden items-center flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1", children: [_jsx(SidebarToggle, {}), showHeader && (_jsx("span", { className: "text-heading-4 text-foreground", children: agentName }))] }), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle menu", onClick: () => setIsExpanded(!isExpanded), children: _jsx(ChevronUp, { className: cn("size-4 text-muted-foreground transition-transform duration-200", isExpanded ? "" : "rotate-180") }) })] }));
134
56
  }
135
57
  // Header component that uses ChatLayout context (must be inside ChatLayout.Root)
136
- function AppChatHeader({ agentName, todos, sources, showHeader, sessionId, debuggerUrl, tools, mcps, subagents, client, }) {
137
- const { panelSize, setPanelSize } = ChatLayout.useChatLayoutContext();
58
+ function AppChatHeader({ agentName, todos, sources, showHeader, sessionId, debuggerUrl, tools, mcps, subagents, }) {
59
+ const { togglePanel, panelOpen } = ChatLayout.useChatLayoutContext();
138
60
  const debuggerLink = debuggerUrl
139
61
  ? sessionId
140
62
  ? `${debuggerUrl}/sessions/${sessionId}`
141
63
  : debuggerUrl
142
64
  : null;
143
- return (_jsxs(ChatHeader.Root, { className: cn("border-b border-border bg-card relative lg:p-0", "[border-bottom-width:0.5px]"), children: [_jsxs("div", { className: "hidden lg:flex items-center w-full h-16 py-5 pl-6 pr-4", children: [showHeader && (_jsx("div", { className: "flex items-center gap-2 flex-1", children: _jsx(SessionSwitcher, { agentName: agentName, client: client, currentSessionId: sessionId }) })), !showHeader && _jsx("div", { className: "flex-1" }), debuggerUrl && (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(IconButton, { "aria-label": "View session in debugger", disabled: !debuggerLink, asChild: !!debuggerLink, children: debuggerLink ? (_jsx("a", { href: debuggerLink, target: "_blank", rel: "noopener noreferrer", children: _jsx(Bug, { className: "size-4 text-muted-foreground" }) })) : (_jsx(Bug, { className: "size-4 text-muted-foreground" })) }) }), _jsx(TooltipContent, { children: _jsx("p", { children: sessionId ? "View session in debugger" : "Open debugger" }) })] }) })), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle sidebar", onClick: () => {
144
- setPanelSize(panelSize === "hidden" ? "small" : "hidden");
145
- }, children: _jsx(PanelRight, { className: "size-4 text-muted-foreground" }) })] }), _jsx(MobileHeader, { agentName: agentName, showHeader: showHeader, client: client, currentSessionId: sessionId }), _jsx(ChatHeader.ExpandablePanel, { className: cn("pt-6 pb-8 px-6", "border-b border-border bg-card", "shadow-[0_4px_16px_0_rgba(0,0,0,0.04)]", "[border-bottom-width:0.5px]"), children: _jsxs(Tabs, { defaultValue: "todo", className: "w-full", children: [_jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources", "settings"], variant: "default" }), _jsx(TabsContent, { value: "todo", className: "mt-4", children: _jsx(TodoTabContent, { todos: todos }) }), _jsx(TabsContent, { value: "files", className: "mt-4", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "mt-4", children: _jsx(SourcesTabContent, { sources: sources }) }), _jsx(TabsContent, { value: "settings", className: "mt-4", children: _jsx(SettingsTabContent, { tools: tools ?? [], mcps: mcps ?? [], subagents: subagents ?? [] }) })] }) })] }));
65
+ return (_jsxs(ChatHeader.Root, { className: cn("border-b border-border bg-card relative lg:p-0", "[border-bottom-width:0.5px]"), children: [_jsxs("div", { className: "hidden lg:flex items-center w-full h-16 py-5 pl-6 pr-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1", children: [_jsx(SidebarToggle, {}), showHeader && (_jsx("span", { className: "text-heading-4 text-foreground", children: agentName }))] }), debuggerUrl && (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(IconButton, { "aria-label": "View session in debugger", disabled: !debuggerLink, asChild: !!debuggerLink, children: debuggerLink ? (_jsx("a", { href: debuggerLink, target: "_blank", rel: "noopener noreferrer", children: _jsx(Bug, { className: "size-4 text-muted-foreground" }) })) : (_jsx(Bug, { className: "size-4 text-muted-foreground" })) }) }), _jsx(TooltipContent, { children: _jsx("p", { children: sessionId ? "View session in debugger" : "Open debugger" }) })] }) })), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle panel", onClick: togglePanel, "data-state": panelOpen ? "open" : "closed", children: _jsx(PanelRight, { className: "size-4 text-muted-foreground" }) })] }), _jsx(MobileHeader, { agentName: agentName, showHeader: showHeader }), _jsx(ChatHeader.ExpandablePanel, { className: cn("pt-6 pb-8 px-6", "border-b border-border bg-card", "shadow-[0_4px_16px_0_rgba(0,0,0,0.04)]", "[border-bottom-width:0.5px]"), children: _jsxs(Tabs, { defaultValue: "todo", className: "w-full", children: [_jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources", "settings"], variant: "default" }), _jsx(TabsContent, { value: "todo", className: "mt-4", children: _jsx(TodoTabContent, { todos: todos }) }), _jsx(TabsContent, { value: "files", className: "mt-4", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "mt-4", children: _jsx(SourcesTabContent, { sources: sources }) }), _jsx(TabsContent, { value: "settings", className: "mt-4", children: _jsx(SettingsTabContent, { tools: tools ?? [], mcps: mcps ?? [], subagents: subagents ?? [] }) })] }) })] }));
146
66
  }
147
67
  export function ChatView({ client, initialSessionId, error: initError, debuggerUrl, }) {
148
68
  // Use shared hooks from @townco/ui/core - MUST be called before any early returns
@@ -291,32 +211,36 @@ export function ChatView({ client, initialSessionId, error: initError, debuggerU
291
211
  },
292
212
  },
293
213
  ];
294
- return (_jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsx(SidebarHotkey, {}), _jsxs(ChatLayout.Main, { children: [!hideTopBar && (_jsx(AppChatHeader, { agentName: agentName, todos: todos, sources: sources, showHeader: messages.length > 0, sessionId: sessionId, tools: agentTools, mcps: agentMcps, subagents: agentSubagents, client: client, ...(debuggerUrl && { debuggerUrl }) })), connectionStatus === "error" && error && (_jsx("div", { className: "border-b border-destructive/20 bg-destructive/10 px-6 py-4", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "mb-1 text-paragraph-sm font-semibold text-destructive", children: "Connection Error" }), _jsx("p", { className: "whitespace-pre-line text-paragraph-sm text-foreground", children: error })] }), _jsx("button", { type: "button", onClick: connect, className: "rounded-lg bg-destructive px-4 py-2 text-paragraph-sm font-medium text-destructive-foreground transition-colors hover:bg-destructive-hover", children: "Retry" })] }) })), _jsxs(ChatLayout.Body, { children: [_jsx(ChatLayout.Messages, { children: messages.length === 0 ? (_jsx(OpenFilesButton, { children: ({ openFiles, openSettings }) => (_jsx("div", { className: "flex flex-1 items-center px-4", children: _jsx(ChatEmptyState, { title: agentName, titleElement: _jsx(SessionSwitcher, { agentName: agentName, client: client, currentSessionId: sessionId }), description: agentDescription, suggestedPrompts: suggestedPrompts, onPromptClick: (prompt) => {
295
- sendMessage(prompt);
296
- setPlaceholder("Type a message or / for commands...");
297
- logger.info("Prompt clicked", { prompt });
298
- }, onPromptHover: handlePromptHover, onPromptLeave: handlePromptLeave, onOpenFiles: openFiles, onOpenSettings: openSettings, toolsAndMcpsCount: agentTools.length +
299
- agentMcps.length +
300
- agentSubagents.length }) })) })) : (_jsx("div", { className: "flex flex-col px-4", children: messages.map((message, index) => {
301
- // Calculate dynamic spacing based on message sequence
302
- const isFirst = index === 0;
303
- const previousMessage = isFirst ? null : messages[index - 1];
304
- let spacingClass = "mt-2";
305
- if (isFirst) {
306
- // First message needs more top margin if it's an assistant initial message
307
- spacingClass =
308
- message.role === "assistant" ? "mt-8" : "mt-2";
309
- }
310
- else if (message.role === "user") {
311
- // User message usually starts a new turn
312
- spacingClass =
313
- previousMessage?.role === "user" ? "mt-4" : "mt-4";
314
- }
315
- else if (message.role === "assistant") {
316
- // Assistant message is usually a response
317
- spacingClass =
318
- previousMessage?.role === "assistant" ? "mt-2" : "mt-6";
319
- }
320
- return (_jsx(Message, { message: message, className: spacingClass, isLastMessage: index === messages.length - 1, children: _jsx(MessageContent, { message: message, thinkingDisplayStyle: "collapsible" }) }, message.id));
321
- }) })) }), _jsx(ChatLayout.Footer, { children: _jsx(ChatInputWithAttachments, { client: client, startSession: startSession, placeholder: placeholder, latestContextSize: latestContextSize, currentModel: currentModel, commandMenuItems: commandMenuItems }) })] })] }), isLargeScreen && (_jsx(ChatLayout.Aside, { breakpoint: "lg", children: _jsx(AsideTabs, { todos: todos, tools: agentTools, mcps: agentMcps, subagents: agentSubagents }) }))] }));
214
+ return (_jsxs(SidebarProvider, { defaultOpen: false, children: [_jsx(AppSidebar, { client: client, currentSessionId: sessionId }), _jsx(SidebarInset, { children: _jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsxs(ChatLayout.Main, { children: [!hideTopBar && (_jsx(AppChatHeader, { agentName: agentName, todos: todos, sources: sources, showHeader: messages.length > 0, sessionId: sessionId, tools: agentTools, mcps: agentMcps, subagents: agentSubagents, ...(debuggerUrl && { debuggerUrl }) })), connectionStatus === "error" && error && (_jsx("div", { className: "border-b border-destructive/20 bg-destructive/10 px-6 py-4", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "mb-1 text-paragraph-sm font-semibold text-destructive", children: "Connection Error" }), _jsx("p", { className: "whitespace-pre-line text-paragraph-sm text-foreground", children: error })] }), _jsx("button", { type: "button", onClick: connect, className: "rounded-lg bg-destructive px-4 py-2 text-paragraph-sm font-medium text-destructive-foreground transition-colors hover:bg-destructive-hover", children: "Retry" })] }) })), _jsxs(ChatLayout.Body, { children: [_jsx(ChatLayout.Messages, { children: messages.length === 0 ? (_jsx(OpenFilesButton, { children: ({ openFiles, openSettings }) => (_jsx("div", { className: "flex flex-1 items-center px-4", children: _jsx(ChatEmptyState, { title: agentName, description: agentDescription, suggestedPrompts: suggestedPrompts, onPromptClick: (prompt) => {
215
+ sendMessage(prompt);
216
+ setPlaceholder("Type a message or / for commands...");
217
+ logger.info("Prompt clicked", { prompt });
218
+ }, onPromptHover: handlePromptHover, onPromptLeave: handlePromptLeave, onOpenFiles: openFiles, onOpenSettings: openSettings, toolsAndMcpsCount: agentTools.length +
219
+ agentMcps.length +
220
+ agentSubagents.length }) })) })) : (_jsx("div", { className: "flex flex-col px-4", children: messages.map((message, index) => {
221
+ // Calculate dynamic spacing based on message sequence
222
+ const isFirst = index === 0;
223
+ const previousMessage = isFirst
224
+ ? null
225
+ : messages[index - 1];
226
+ let spacingClass = "mt-2";
227
+ if (isFirst) {
228
+ // First message needs more top margin if it's an assistant initial message
229
+ spacingClass =
230
+ message.role === "assistant" ? "mt-8" : "mt-2";
231
+ }
232
+ else if (message.role === "user") {
233
+ // User message usually starts a new turn
234
+ spacingClass =
235
+ previousMessage?.role === "user" ? "mt-4" : "mt-4";
236
+ }
237
+ else if (message.role === "assistant") {
238
+ // Assistant message is usually a response
239
+ spacingClass =
240
+ previousMessage?.role === "assistant"
241
+ ? "mt-2"
242
+ : "mt-6";
243
+ }
244
+ return (_jsx(Message, { message: message, className: spacingClass, isLastMessage: index === messages.length - 1, children: _jsx(MessageContent, { message: message, thinkingDisplayStyle: "collapsible" }) }, message.id));
245
+ }) })) }), _jsx(ChatLayout.Footer, { children: _jsx(ChatInputWithAttachments, { client: client, startSession: startSession, placeholder: placeholder, latestContextSize: latestContextSize, currentModel: currentModel, commandMenuItems: commandMenuItems }) })] })] }), isLargeScreen && (_jsx(ChatLayout.Aside, { breakpoint: "lg", children: _jsx(AsideTabs, { todos: todos, tools: agentTools, mcps: agentMcps, subagents: agentSubagents }) }))] }) })] }));
322
246
  }
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cva } from "class-variance-authority";
3
+ import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
3
4
  import * as React from "react";
4
5
  import { useChatStore } from "../../core/store/chat-store.js";
6
+ import { isPreliminaryToolCall } from "../../core/utils/tool-call-state.js";
7
+ import { getDuration, getTransition, motionEasing, shimmerTransition, } from "../lib/motion.js";
5
8
  import { cn } from "../lib/utils.js";
6
- import { InvokingGroup } from "./InvokingGroup.js";
7
9
  import { Reasoning } from "./Reasoning.js";
8
10
  import { Response } from "./Response.js";
9
- import { ToolCall } from "./ToolCall.js";
10
- import { ToolCallGroup } from "./ToolCallGroup.js";
11
+ import { ToolOperation } from "./ToolOperation.js";
11
12
  /**
12
13
  * MessageContent component inspired by shadcn.io/ai
13
14
  * Provides the content container with role-based styling
@@ -35,6 +36,7 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
35
36
  // Get streaming start time and current model from store
36
37
  const streamingStartTime = useChatStore((state) => state.streamingStartTime);
37
38
  const currentModel = useChatStore((state) => state.currentModel);
39
+ const shouldReduceMotion = useReducedMotion();
38
40
  // Use smart rendering if message is provided and no custom children
39
41
  const useSmartRendering = message && !children;
40
42
  // Derive props from message if using smart rendering
@@ -49,36 +51,88 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
49
51
  const hasThinking = !!thinking;
50
52
  // Check if waiting (streaming but no content yet)
51
53
  const isWaiting = message.isStreaming && !message.content && message.role === "assistant";
52
- content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true })), isWaiting && streamingStartTime && (_jsx("div", { className: "flex items-center gap-2", children: _jsx("div", { className: "size-2.5 rounded-full bg-foreground animate-pulse-scale" }) })), message.role === "assistant" ? ((() => {
54
+ content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(motion.div, { initial: {
55
+ filter: "blur(12px)",
56
+ opacity: 0,
57
+ y: 12,
58
+ }, animate: {
59
+ filter: "blur(0px)",
60
+ opacity: 1,
61
+ y: 0,
62
+ }, exit: {
63
+ filter: "blur(12px)",
64
+ opacity: 0,
65
+ y: -12,
66
+ }, transition: getTransition(shouldReduceMotion ?? false, {
67
+ duration: 0.5,
68
+ ease: motionEasing.smooth,
69
+ }), children: _jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true }) })), isWaiting && streamingStartTime && (_jsx(motion.div, { initial: {
70
+ filter: "blur(12px)",
71
+ opacity: 0,
72
+ y: 12,
73
+ }, animate: {
74
+ filter: "blur(0px)",
75
+ opacity: 1,
76
+ y: 0,
77
+ }, exit: {
78
+ filter: "blur(12px)",
79
+ opacity: 0,
80
+ y: -12,
81
+ }, transition: getTransition(shouldReduceMotion ?? false, {
82
+ duration: 0.4,
83
+ ease: motionEasing.smooth,
84
+ }), children: _jsx(motion.div, { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", animate: {
85
+ backgroundPosition: ["-200% 0", "200% 0"],
86
+ }, transition: {
87
+ ...shimmerTransition,
88
+ duration: getDuration(shouldReduceMotion ?? false, 1.5),
89
+ }, style: {
90
+ backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
91
+ backgroundSize: "200% 100%",
92
+ }, children: _jsx("div", { className: "flex items-center gap-1.5", children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Thinking..." }) }) }) })), message.role === "assistant" ? ((() => {
53
93
  // Sort tool calls by content position
54
94
  const sortedToolCalls = (message.toolCalls || [])
55
95
  .slice()
56
96
  .sort((a, b) => (a.contentPosition ?? Infinity) -
57
97
  (b.contentPosition ?? Infinity));
58
- // Helper to check if a tool call is preliminary (invoking)
59
- const isPreliminary = (tc) => tc.status === "pending" &&
60
- (!tc.rawInput || Object.keys(tc.rawInput).length === 0);
61
- // Helper to group tool calls by batchId and consecutive preliminary calls
98
+ // Helper to group tool calls by batchId, consecutive same-title calls, or consecutive preliminary calls
62
99
  const groupToolCalls = (toolCalls) => {
63
100
  const result = [];
64
101
  const batchGroups = new Map();
65
- let currentInvokingGroup = [];
66
- const flushInvokingGroup = () => {
67
- if (currentInvokingGroup.length > 1) {
102
+ let currentSelectingGroup = [];
103
+ let currentConsecutiveGroup = [];
104
+ let currentConsecutiveTitle = null;
105
+ const flushSelectingGroup = () => {
106
+ if (currentSelectingGroup.length > 1) {
68
107
  result.push({
69
- type: "invoking",
70
- toolCalls: currentInvokingGroup,
108
+ type: "selecting",
109
+ toolCalls: currentSelectingGroup,
71
110
  });
72
111
  }
73
- else if (currentInvokingGroup.length === 1) {
74
- result.push(currentInvokingGroup[0]);
112
+ else if (currentSelectingGroup.length === 1) {
113
+ result.push(currentSelectingGroup[0]);
75
114
  }
76
- currentInvokingGroup = [];
115
+ currentSelectingGroup = [];
116
+ };
117
+ const flushConsecutiveGroup = () => {
118
+ if (currentConsecutiveGroup.length > 1) {
119
+ // Multiple consecutive same-title calls - group them
120
+ result.push({
121
+ type: "batch",
122
+ toolCalls: currentConsecutiveGroup,
123
+ });
124
+ }
125
+ else if (currentConsecutiveGroup.length === 1) {
126
+ result.push(currentConsecutiveGroup[0]);
127
+ }
128
+ currentConsecutiveGroup = [];
129
+ currentConsecutiveTitle = null;
77
130
  };
78
131
  for (const tc of toolCalls) {
79
- // Handle batch groups
132
+ // Handle batch groups (explicit batchId)
80
133
  if (tc.batchId) {
81
- flushInvokingGroup();
134
+ flushSelectingGroup();
135
+ flushConsecutiveGroup();
82
136
  const existing = batchGroups.get(tc.batchId);
83
137
  if (existing) {
84
138
  existing.push(tc);
@@ -89,18 +143,29 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
89
143
  result.push({ type: "batch", toolCalls: group });
90
144
  }
91
145
  }
92
- // Handle consecutive preliminary (invoking) tool calls
93
- else if (isPreliminary(tc)) {
94
- currentInvokingGroup.push(tc);
146
+ // Handle consecutive preliminary (selecting) tool calls
147
+ else if (isPreliminaryToolCall(tc)) {
148
+ flushConsecutiveGroup();
149
+ currentSelectingGroup.push(tc);
95
150
  }
96
- // Regular tool call
151
+ // Regular tool call - group consecutive same-title calls (e.g., subagent)
97
152
  else {
98
- flushInvokingGroup();
99
- result.push(tc);
153
+ flushSelectingGroup();
154
+ // Check if this continues a consecutive group
155
+ if (currentConsecutiveTitle === tc.title) {
156
+ currentConsecutiveGroup.push(tc);
157
+ }
158
+ else {
159
+ // Different title - flush previous group and start new one
160
+ flushConsecutiveGroup();
161
+ currentConsecutiveGroup = [tc];
162
+ currentConsecutiveTitle = tc.title;
163
+ }
100
164
  }
101
165
  }
102
- // Flush any remaining invoking group
103
- flushInvokingGroup();
166
+ // Flush any remaining groups
167
+ flushSelectingGroup();
168
+ flushConsecutiveGroup();
104
169
  return result;
105
170
  };
106
171
  // Helper to render a tool call or group
@@ -109,42 +174,60 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
109
174
  if (typeof item === "object" &&
110
175
  "type" in item &&
111
176
  item.type === "batch") {
112
- return (_jsx(ToolCallGroup, { toolCalls: item.toolCalls }, `batch-${item.toolCalls[0]?.batchId || index}`));
177
+ return (_jsx(ToolOperation, { toolCalls: item.toolCalls, isGrouped: true }, `batch-${item.toolCalls[0]?.batchId || index}`));
113
178
  }
114
- // Invoking group (consecutive preliminary tool calls)
179
+ // Selecting group (consecutive preliminary tool calls)
115
180
  if (typeof item === "object" &&
116
181
  "type" in item &&
117
- item.type === "invoking") {
118
- return (_jsx(InvokingGroup, { toolCalls: item.toolCalls }, `invoking-${item.toolCalls[0]?.id || index}`));
182
+ item.type === "selecting") {
183
+ return (_jsx(ToolOperation, { toolCalls: item.toolCalls, isGrouped: true }, `selecting-${item.toolCalls[0]?.id || index}`));
119
184
  }
120
185
  // Single tool call
121
- return (_jsx(ToolCall, { toolCall: item }, item.id));
186
+ return (_jsx(ToolOperation, { toolCalls: [item], isGrouped: false }, item.id));
122
187
  };
123
- // If no tool calls or they don't have positions, render old way
188
+ // If no tool calls or they don't have positions, render simplified way
124
189
  if (sortedToolCalls.length === 0 ||
125
190
  !sortedToolCalls.some((tc) => tc.contentPosition !== undefined)) {
126
191
  const groupedToolCalls = groupToolCalls(sortedToolCalls);
127
- return (_jsxs(_Fragment, { children: [groupedToolCalls.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-1", children: groupedToolCalls.map((item, index) => renderToolCallOrGroup(item, index)) })), _jsx(Response, { content: message.content, isStreaming: message.isStreaming, showEmpty: false })] }));
192
+ return (_jsxs(_Fragment, { children: [groupedToolCalls.length > 0 && (_jsx(AnimatePresence, { mode: "popLayout", children: _jsx("div", { className: "flex flex-col gap-2 mb-1", children: groupedToolCalls.map((item, index) => renderToolCallOrGroup(item, index)) }) })), _jsx(motion.div, { initial: {
193
+ filter: "blur(12px)",
194
+ opacity: 0,
195
+ y: 12,
196
+ }, animate: {
197
+ filter: "blur(0px)",
198
+ opacity: 1,
199
+ y: 0,
200
+ }, exit: {
201
+ filter: "blur(12px)",
202
+ opacity: 0,
203
+ y: -12,
204
+ }, transition: getTransition(shouldReduceMotion ?? false, {
205
+ duration: 0.4,
206
+ ease: motionEasing.smooth,
207
+ }), children: _jsx(Response, { content: message.content, isStreaming: message.isStreaming, showEmpty: false }) })] }));
128
208
  }
129
209
  // Render content interleaved with tool calls
130
- // Group consecutive tool calls with the same batchId
210
+ // Group consecutive tool calls with the same batchId or same title
131
211
  const elements = [];
132
212
  let currentPosition = 0;
133
213
  let currentBatch = [];
134
214
  let currentBatchId;
215
+ let currentBatchTitle;
135
216
  const flushBatch = () => {
136
- if (currentBatch.length > 1 && currentBatchId) {
137
- elements.push(_jsx(ToolCallGroup, { toolCalls: currentBatch }, `group-${currentBatchId}`));
217
+ if (currentBatch.length > 1) {
218
+ // Group multiple consecutive calls (by batchId or same title)
219
+ elements.push(_jsx(ToolOperation, { toolCalls: currentBatch, isGrouped: true }, `group-${currentBatchId || currentBatchTitle}-${currentBatch[0].id}`));
138
220
  }
139
221
  else if (currentBatch.length === 1) {
140
- elements.push(_jsx("div", { children: _jsx(ToolCall, { toolCall: currentBatch[0] }) }, `tool-${currentBatch[0].id}`));
222
+ elements.push(_jsx("div", { children: _jsx(ToolOperation, { toolCalls: [currentBatch[0]], isGrouped: false }) }, `tool-${currentBatch[0].id}`));
141
223
  }
142
224
  currentBatch = [];
143
225
  currentBatchId = undefined;
226
+ currentBatchTitle = undefined;
144
227
  };
145
228
  // Separate preliminary tool calls - they should render at the end, not break text
146
- const preliminaryToolCalls = sortedToolCalls.filter(isPreliminary);
147
- const nonPreliminaryToolCalls = sortedToolCalls.filter((tc) => !isPreliminary(tc));
229
+ const preliminaryToolCalls = sortedToolCalls.filter(isPreliminaryToolCall);
230
+ const nonPreliminaryToolCalls = sortedToolCalls.filter((tc) => !isPreliminaryToolCall(tc));
148
231
  // Process non-preliminary tool calls inline with text
149
232
  nonPreliminaryToolCalls.forEach((toolCall, index) => {
150
233
  const position = toolCall.contentPosition ?? message.content.length;
@@ -154,10 +237,25 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
154
237
  flushBatch();
155
238
  const textChunk = message.content.slice(currentPosition, position);
156
239
  if (textChunk) {
157
- elements.push(_jsx(Response, { content: textChunk, isStreaming: false, showEmpty: false }, `text-before-${toolCall.id}`));
240
+ elements.push(_jsx(motion.div, { initial: {
241
+ filter: "blur(12px)",
242
+ opacity: 0,
243
+ y: 12,
244
+ }, animate: {
245
+ filter: "blur(0px)",
246
+ opacity: 1,
247
+ y: 0,
248
+ }, exit: {
249
+ filter: "blur(12px)",
250
+ opacity: 0,
251
+ y: -12,
252
+ }, transition: getTransition(shouldReduceMotion ?? false, {
253
+ duration: 0.4,
254
+ ease: motionEasing.smooth,
255
+ }), children: _jsx(Response, { content: textChunk, isStreaming: false, showEmpty: false }) }, `text-before-${toolCall.id}`));
158
256
  }
159
257
  }
160
- // Check if this tool call should be batched
258
+ // Check if this tool call should be batched (by batchId or consecutive same title)
161
259
  if (toolCall.batchId) {
162
260
  if (currentBatchId === toolCall.batchId) {
163
261
  // Same batch, add to current batch
@@ -167,13 +265,20 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
167
265
  // Different batch, flush previous and start new
168
266
  flushBatch();
169
267
  currentBatchId = toolCall.batchId;
268
+ currentBatchTitle = toolCall.title;
170
269
  currentBatch = [toolCall];
171
270
  }
172
271
  }
272
+ else if (currentBatchTitle === toolCall.title &&
273
+ !currentBatchId) {
274
+ // Same title as previous (no batchId), continue grouping
275
+ currentBatch.push(toolCall);
276
+ }
173
277
  else {
174
- // No batch ID, flush previous and render individually
278
+ // Different title or switching from batchId to title grouping
175
279
  flushBatch();
176
- elements.push(_jsx("div", { children: _jsx(ToolCall, { toolCall: toolCall }) }, `tool-${toolCall.id}`));
280
+ currentBatchTitle = toolCall.title;
281
+ currentBatch = [toolCall];
177
282
  }
178
283
  currentPosition = position;
179
284
  // Flush batch at the end
@@ -185,20 +290,65 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
185
290
  if (currentPosition < message.content.length) {
186
291
  const remainingText = message.content.slice(currentPosition);
187
292
  if (remainingText) {
188
- elements.push(_jsx(Response, { content: remainingText, isStreaming: message.isStreaming, showEmpty: false }, "text-end"));
293
+ elements.push(_jsx(motion.div, { initial: {
294
+ filter: "blur(12px)",
295
+ opacity: 0,
296
+ y: 12,
297
+ }, animate: {
298
+ filter: "blur(0px)",
299
+ opacity: 1,
300
+ y: 0,
301
+ }, exit: {
302
+ filter: "blur(12px)",
303
+ opacity: 0,
304
+ y: -12,
305
+ }, transition: getTransition(shouldReduceMotion ?? false, {
306
+ duration: 0.4,
307
+ ease: motionEasing.smooth,
308
+ }), children: _jsx(Response, { content: remainingText, isStreaming: message.isStreaming, showEmpty: false }) }, "text-end"));
189
309
  }
190
310
  }
191
- // Render preliminary (invoking) tool calls at the end, grouped
311
+ // Render preliminary (selecting) tool calls at the end, grouped
192
312
  if (preliminaryToolCalls.length > 0) {
193
313
  if (preliminaryToolCalls.length > 1) {
194
- elements.push(_jsx(InvokingGroup, { toolCalls: preliminaryToolCalls }, `invoking-group-${preliminaryToolCalls[0].id}`));
314
+ elements.push(_jsx(ToolOperation, { toolCalls: preliminaryToolCalls, isGrouped: true }, `selecting-group-${preliminaryToolCalls[0].id}`));
195
315
  }
196
316
  else {
197
- elements.push(_jsx("div", { children: _jsx(ToolCall, { toolCall: preliminaryToolCalls[0] }) }, `tool-${preliminaryToolCalls[0].id}`));
317
+ elements.push(_jsx("div", { children: _jsx(ToolOperation, { toolCalls: [preliminaryToolCalls[0]], isGrouped: false }) }, `tool-${preliminaryToolCalls[0].id}`));
198
318
  }
199
319
  }
200
- return _jsx(_Fragment, { children: elements });
201
- })()) : (_jsxs("div", { className: "flex flex-col gap-2", children: [message.images && message.images.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", children: message.images.map((image, index) => (_jsx("img", { src: `data:${image.mimeType};base64,${image.data}`, alt: `Attachment ${index + 1}`, className: "max-w-[200px] max-h-[200px] rounded-lg object-cover" }, index))) })), message.content && (_jsx("div", { className: "whitespace-pre-wrap", children: message.content }))] }))] }));
320
+ return (_jsx(AnimatePresence, { mode: "popLayout", children: elements }));
321
+ })()) : (_jsxs("div", { className: "flex flex-col gap-2", children: [message.images && message.images.length > 0 && (_jsx(motion.div, { className: "flex flex-wrap gap-2", initial: {
322
+ filter: "blur(12px)",
323
+ opacity: 0,
324
+ y: 12,
325
+ }, animate: {
326
+ filter: "blur(0px)",
327
+ opacity: 1,
328
+ y: 0,
329
+ }, exit: {
330
+ filter: "blur(12px)",
331
+ opacity: 0,
332
+ y: -12,
333
+ }, transition: getTransition(shouldReduceMotion ?? false, {
334
+ duration: 0.5,
335
+ ease: motionEasing.smooth,
336
+ }), children: message.images.map((image, index) => (_jsx("img", { src: `data:${image.mimeType};base64,${image.data}`, alt: `Attachment ${index + 1}`, className: "max-w-[200px] max-h-[200px] rounded-lg object-cover" }, index))) })), message.content && (_jsx(motion.div, { className: "whitespace-pre-wrap", initial: {
337
+ filter: "blur(12px)",
338
+ opacity: 0,
339
+ y: 12,
340
+ }, animate: {
341
+ filter: "blur(0px)",
342
+ opacity: 1,
343
+ y: 0,
344
+ }, exit: {
345
+ filter: "blur(12px)",
346
+ opacity: 0,
347
+ y: -12,
348
+ }, transition: getTransition(shouldReduceMotion ?? false, {
349
+ duration: 0.4,
350
+ ease: motionEasing.smooth,
351
+ }), children: message.content }))] }))] }));
202
352
  }
203
353
  return (_jsx("div", { ref: ref, className: cn(messageContentVariants({ role, variant }), isStreaming && "animate-pulse-subtle", className), ...props, children: content }));
204
354
  });
@@ -0,0 +1,10 @@
1
+ import type { AcpClient } from "../../sdk/client/index.js";
2
+ export interface SessionHistoryProps {
3
+ client: AcpClient | null;
4
+ currentSessionId: string | null;
5
+ onSessionSelect?: ((sessionId: string) => void) | undefined;
6
+ onRenameSession?: ((sessionId: string) => void) | undefined;
7
+ onArchiveSession?: ((sessionId: string) => void) | undefined;
8
+ onDeleteSession?: ((sessionId: string) => void) | undefined;
9
+ }
10
+ export declare function SessionHistory({ client, currentSessionId, onSessionSelect, onRenameSession, onArchiveSession, onDeleteSession, }: SessionHistoryProps): import("react/jsx-runtime").JSX.Element;