@townco/ui 0.1.36 → 0.1.38

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 (53) hide show
  1. package/dist/core/hooks/use-chat-input.d.ts +1 -1
  2. package/dist/core/hooks/use-chat-input.js +2 -2
  3. package/dist/core/hooks/use-chat-messages.d.ts +3 -1
  4. package/dist/core/hooks/use-chat-messages.js +36 -7
  5. package/dist/core/hooks/use-chat-session.d.ts +1 -1
  6. package/dist/core/hooks/use-chat-session.js +4 -7
  7. package/dist/core/hooks/use-tool-calls.d.ts +2 -0
  8. package/dist/core/lib/logger.d.ts +24 -0
  9. package/dist/core/lib/logger.js +108 -0
  10. package/dist/core/schemas/chat.d.ts +4 -0
  11. package/dist/core/schemas/tool-call.d.ts +2 -0
  12. package/dist/core/schemas/tool-call.js +4 -0
  13. package/dist/gui/components/ChatEmptyState.d.ts +6 -0
  14. package/dist/gui/components/ChatEmptyState.js +2 -2
  15. package/dist/gui/components/ChatInput.d.ts +4 -0
  16. package/dist/gui/components/ChatInput.js +12 -7
  17. package/dist/gui/components/ChatLayout.js +102 -8
  18. package/dist/gui/components/ChatPanelTabContent.d.ts +1 -1
  19. package/dist/gui/components/ChatPanelTabContent.js +10 -3
  20. package/dist/gui/components/ChatView.js +45 -14
  21. package/dist/gui/components/ContextUsageButton.d.ts +7 -0
  22. package/dist/gui/components/ContextUsageButton.js +18 -0
  23. package/dist/gui/components/FileSystemItem.js +6 -7
  24. package/dist/gui/components/FileSystemView.js +3 -3
  25. package/dist/gui/components/InlineToolCallSummary.d.ts +14 -0
  26. package/dist/gui/components/InlineToolCallSummary.js +110 -0
  27. package/dist/gui/components/InlineToolCallSummaryACP.d.ts +15 -0
  28. package/dist/gui/components/InlineToolCallSummaryACP.js +90 -0
  29. package/dist/gui/components/MessageContent.js +1 -1
  30. package/dist/gui/components/Response.js +2 -2
  31. package/dist/gui/components/SourceListItem.js +9 -1
  32. package/dist/gui/components/TodoListItem.js +19 -2
  33. package/dist/gui/components/ToolCall.js +21 -2
  34. package/dist/gui/components/Tooltip.d.ts +7 -0
  35. package/dist/gui/components/Tooltip.js +10 -0
  36. package/dist/gui/components/index.d.ts +4 -0
  37. package/dist/gui/components/index.js +5 -0
  38. package/dist/gui/components/tool-call-summary.d.ts +44 -0
  39. package/dist/gui/components/tool-call-summary.js +67 -0
  40. package/dist/gui/data/mockSourceData.d.ts +10 -0
  41. package/dist/gui/data/mockSourceData.js +40 -0
  42. package/dist/gui/data/mockTodoData.d.ts +10 -0
  43. package/dist/gui/data/mockTodoData.js +35 -0
  44. package/dist/gui/examples/FileSystemDemo.d.ts +5 -0
  45. package/dist/gui/examples/FileSystemDemo.js +24 -0
  46. package/dist/gui/examples/FileSystemExample.d.ts +17 -0
  47. package/dist/gui/examples/FileSystemExample.js +94 -0
  48. package/dist/sdk/schemas/session.d.ts +2 -0
  49. package/dist/sdk/transports/http.d.ts +1 -0
  50. package/dist/sdk/transports/http.js +40 -8
  51. package/dist/sdk/transports/stdio.js +13 -0
  52. package/dist/tui/components/ChatView.js +5 -5
  53. package/package.json +3 -3
@@ -1,5 +1,7 @@
1
1
  export { toast } from "sonner";
2
2
  export { MockFileSystemProvider, mockFileSystemData, } from "../data/mockFileSystemData.js";
3
+ export { mockSourceData } from "../data/mockSourceData.js";
4
+ export { mockTodoData } from "../data/mockTodoData.js";
3
5
  export type { FileSystemData, FileSystemItem as FileSystemItemData, FileSystemItemType, FileSystemProvider, } from "../types/filesystem.js";
4
6
  export { Button, type ButtonProps, buttonVariants } from "./Button.js";
5
7
  export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./Card.js";
@@ -13,6 +15,7 @@ export { ChatSecondaryPanel, type ChatSecondaryPanelProps, } from "./ChatSeconda
13
15
  export * as ChatSidebar from "./ChatSidebar.js";
14
16
  export { ChatStatus, type ChatStatusProps } from "./ChatStatus.js";
15
17
  export { ChatView, type ChatViewProps } from "./ChatView.js";
18
+ export { ContextUsageButton, type ContextUsageButtonProps, } from "./ContextUsageButton.js";
16
19
  export { Conversation, type ConversationProps } from "./Conversation.js";
17
20
  export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, } from "./Dialog.js";
18
21
  export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "./DropdownMenu.js";
@@ -39,3 +42,4 @@ export { TodoList, type TodoListProps } from "./TodoList.js";
39
42
  export { type TodoItem, TodoListItem, type TodoListItemProps, } from "./TodoListItem.js";
40
43
  export { ToolCall, type ToolCallProps } from "./ToolCall.js";
41
44
  export { ToolCallList, type ToolCallListProps } from "./ToolCallList.js";
45
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
@@ -1,6 +1,8 @@
1
1
  // Base UI components
2
2
  export { toast } from "sonner";
3
3
  export { MockFileSystemProvider, mockFileSystemData, } from "../data/mockFileSystemData.js";
4
+ export { mockSourceData } from "../data/mockSourceData.js";
5
+ export { mockTodoData } from "../data/mockTodoData.js";
4
6
  export { Button, buttonVariants } from "./Button.js";
5
7
  export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./Card.js";
6
8
  export { ChatEmptyState } from "./ChatEmptyState.js";
@@ -14,6 +16,7 @@ export { ChatSecondaryPanel, } from "./ChatSecondaryPanel.js";
14
16
  export * as ChatSidebar from "./ChatSidebar.js";
15
17
  export { ChatStatus } from "./ChatStatus.js";
16
18
  export { ChatView } from "./ChatView.js";
19
+ export { ContextUsageButton, } from "./ContextUsageButton.js";
17
20
  // Chat components - shadcn.io/ai inspired primitives
18
21
  export { Conversation } from "./Conversation.js";
19
22
  // Dialog components
@@ -46,3 +49,5 @@ export { TodoList } from "./TodoList.js";
46
49
  export { TodoListItem, } from "./TodoListItem.js";
47
50
  export { ToolCall } from "./ToolCall.js";
48
51
  export { ToolCallList } from "./ToolCallList.js";
52
+ // Tooltip components
53
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./Tooltip.js";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Tool call summary data from the /summarize endpoint
3
+ */
4
+ export interface ToolCallSummary {
5
+ id: string;
6
+ name: string;
7
+ description: string;
8
+ input: Record<string, unknown>;
9
+ output?: string | undefined;
10
+ status: "pending" | "completed" | "error";
11
+ error?: string | undefined;
12
+ }
13
+ export interface SummaryResponse {
14
+ summary: {
15
+ total: number;
16
+ byStatus: Record<string, number>;
17
+ byTool: Record<string, number>;
18
+ };
19
+ toolCalls: ToolCallSummary[];
20
+ }
21
+ /**
22
+ * Props for ToolCallSummaryDisplay component
23
+ */
24
+ export interface ToolCallSummaryDisplayProps {
25
+ /** ACP server URL */
26
+ serverUrl: string;
27
+ /** Anthropic API request (messages with tool calls) */
28
+ request: {
29
+ model?: string;
30
+ messages: Array<{
31
+ role: "user" | "assistant";
32
+ content: string | Array<{
33
+ type: string;
34
+ [key: string]: unknown;
35
+ }>;
36
+ }>;
37
+ };
38
+ /** Custom className for styling */
39
+ className?: string;
40
+ }
41
+ /**
42
+ * Display a summary of tool calls from an Anthropic API request
43
+ */
44
+ export declare function ToolCallSummaryDisplay({ serverUrl, request, className, }: ToolCallSummaryDisplayProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createLogger } from "@townco/core";
3
+ import { AlertCircle, CheckCircle, Clock, Loader2, RefreshCw, } from "lucide-react";
4
+ import { useEffect, useState } from "react";
5
+ import { cn } from "../lib/utils";
6
+ const logger = createLogger("tool-call-summary");
7
+ /**
8
+ * Display a summary of tool calls from an Anthropic API request
9
+ */
10
+ export function ToolCallSummaryDisplay({ serverUrl, request, className, }) {
11
+ const [summary, setSummary] = useState(null);
12
+ const [loading, setLoading] = useState(false);
13
+ const [error, setError] = useState(null);
14
+ const fetchSummary = async () => {
15
+ setLoading(true);
16
+ setError(null);
17
+ try {
18
+ const response = await fetch(`${serverUrl}/summarize`, {
19
+ method: "POST",
20
+ headers: {
21
+ "Content-Type": "application/json",
22
+ },
23
+ body: JSON.stringify(request),
24
+ });
25
+ if (!response.ok) {
26
+ const errorData = await response.json();
27
+ throw new Error(errorData.error || "Failed to fetch summary");
28
+ }
29
+ const data = await response.json();
30
+ setSummary(data);
31
+ logger.info("Tool call summary fetched", {
32
+ total: data.summary.total,
33
+ });
34
+ }
35
+ catch (err) {
36
+ const message = err instanceof Error ? err.message : String(err);
37
+ setError(message);
38
+ logger.error("Failed to fetch tool call summary", { error: message });
39
+ }
40
+ finally {
41
+ setLoading(false);
42
+ }
43
+ };
44
+ // Fetch summary on mount and when request changes
45
+ useEffect(() => {
46
+ fetchSummary();
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, [serverUrl, request]);
49
+ if (loading && !summary) {
50
+ return (_jsxs("div", { className: cn("flex items-center justify-center p-4 text-muted-foreground", className), children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), _jsx("span", { className: "text-paragraph-sm", children: "Loading summary..." })] }));
51
+ }
52
+ if (error) {
53
+ return (_jsx("div", { className: cn("rounded-lg border border-destructive/20 p-4", className), children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive shrink-0 mt-0.5" }), _jsxs("div", { className: "flex-1", children: [_jsx("p", { className: "text-paragraph-sm font-medium text-destructive", children: "Error loading summary" }), _jsx("p", { className: "text-paragraph-sm text-muted-foreground mt-1", children: error })] }), _jsx("button", { type: "button", onClick: fetchSummary, className: "text-muted-foreground hover:text-foreground transition-colors", children: _jsx(RefreshCw, { className: "h-4 w-4" }) })] }) }));
54
+ }
55
+ if (!summary || summary.summary.total === 0) {
56
+ return (_jsx("div", { className: cn("flex items-center justify-center p-4 text-muted-foreground", className), children: _jsx("span", { className: "text-paragraph-sm", children: "No tool calls found" }) }));
57
+ }
58
+ return (_jsxs("div", { className: cn("space-y-4", className), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("h3", { className: "text-heading-5 font-semibold", children: ["Tool Calls (", summary.summary.total, ")"] }), _jsx("button", { type: "button", onClick: fetchSummary, disabled: loading, className: "text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50", children: _jsx(RefreshCw, { className: cn("h-4 w-4", loading && "animate-spin") }) })] }), _jsxs("div", { className: "flex gap-4", children: [(summary.summary.byStatus.completed ?? 0) > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(CheckCircle, { className: "h-4 w-4 text-success" }), _jsxs("span", { className: "text-paragraph-sm text-muted-foreground", children: [summary.summary.byStatus.completed, " completed"] })] })), (summary.summary.byStatus.pending ?? 0) > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Clock, { className: "h-4 w-4 text-warning" }), _jsxs("span", { className: "text-paragraph-sm text-muted-foreground", children: [summary.summary.byStatus.pending, " pending"] })] })), (summary.summary.byStatus.error ?? 0) > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" }), _jsxs("span", { className: "text-paragraph-sm text-muted-foreground", children: [summary.summary.byStatus.error, " errors"] })] }))] }), _jsx("div", { className: "space-y-2", children: summary.toolCalls.map((toolCall) => (_jsx(ToolCallSummaryItem, { toolCall: toolCall }, toolCall.id))) })] }));
59
+ }
60
+ /**
61
+ * Display a single tool call summary
62
+ */
63
+ function ToolCallSummaryItem({ toolCall }) {
64
+ const [expanded, setExpanded] = useState(false);
65
+ const statusIcon = toolCall.status === "completed" ? (_jsx(CheckCircle, { className: "h-4 w-4 text-success" })) : toolCall.status === "error" ? (_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" })) : (_jsx(Clock, { className: "h-4 w-4 text-warning" }));
66
+ return (_jsxs("div", { className: "rounded-lg border border-border bg-card p-3 space-y-2", children: [_jsxs("button", { type: "button", onClick: () => setExpanded(!expanded), className: "flex items-start gap-2 w-full text-left", children: [statusIcon, _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-paragraph-sm font-medium text-foreground truncate", children: toolCall.description }), _jsx("p", { className: "text-paragraph-xs text-muted-foreground", children: toolCall.name })] })] }), expanded && (_jsxs("div", { className: "pl-6 space-y-2", children: [Object.keys(toolCall.input).length > 0 && (_jsxs("div", { children: [_jsx("p", { className: "text-paragraph-xs font-medium text-muted-foreground mb-1", children: "Input:" }), _jsx("pre", { className: "text-paragraph-xs bg-muted p-2 rounded overflow-x-auto", children: JSON.stringify(toolCall.input, null, 2) })] })), toolCall.output && (_jsxs("div", { children: [_jsx("p", { className: "text-paragraph-xs font-medium text-muted-foreground mb-1", children: "Output:" }), _jsx("pre", { className: "text-paragraph-xs bg-muted p-2 rounded overflow-x-auto", children: toolCall.output })] })), toolCall.error && (_jsxs("div", { children: [_jsx("p", { className: "text-paragraph-xs font-medium text-destructive mb-1", children: "Error:" }), _jsx("p", { className: "text-paragraph-xs text-destructive bg-destructive/10 p-2 rounded", children: toolCall.error })] }))] }))] }));
67
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Mock source data - stub implementation
3
+ * Replace this with real source data from agent later
4
+ */
5
+ import type { SourceItem } from "../components/SourceListItem.js";
6
+ /**
7
+ * Stub source data
8
+ * This data can be easily replaced with real sources from the agent
9
+ */
10
+ export declare const mockSourceData: SourceItem[];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Mock source data - stub implementation
3
+ * Replace this with real source data from agent later
4
+ */
5
+ /**
6
+ * Stub source data
7
+ * This data can be easily replaced with real sources from the agent
8
+ */
9
+ export const mockSourceData = [
10
+ {
11
+ id: "1",
12
+ title: "Boeing Scores Early Wins at Dubai Airshow",
13
+ url: "https://www.reuters.com/business/aerospace-defense/boeing-scores-early-wins-dubai-airshow-2024-11-17",
14
+ snippet: "DUBAI, Nov 17 (Reuters) - Boeing (BA.N), opens new tab took centre stage at day one of the Dubai Airshow on Monday, booking a $38 billion order from host carrier Emirates and clinching more deals with African carriers, while China displayed its C919 in the Middle East for the first time.",
15
+ sourceName: "Reuters",
16
+ favicon: "https://www.reuters.com/favicon.ico",
17
+ },
18
+ {
19
+ id: "2",
20
+ title: "Boeing's Sustainable Aviation Goals Take Flight",
21
+ url: "https://www.forbes.com/boeing-sustainability",
22
+ snippet: "SEATTLE, Nov 18 (Reuters) - Boeing is making headway towards its sustainability targets, unveiling plans for a new eco-friendly aircraft design aimed at reducing emissions by 50% by 2030.",
23
+ sourceName: "Forbes",
24
+ },
25
+ {
26
+ id: "3",
27
+ title: "Boeing Faces Increased Competition in Global Aviation Market",
28
+ url: "https://www.reuters.com/business/aerospace-defense/boeing-competition-2024-11-19",
29
+ snippet: "CHICAGO, Nov 19 (Reuters) - As the global aviation industry rebounds post-pandemic, Boeing is grappling with intensified competition from rival manufacturers, particularly in the Asian market.",
30
+ sourceName: "Reuters",
31
+ favicon: "https://www.reuters.com/favicon.ico",
32
+ },
33
+ {
34
+ id: "4",
35
+ title: "Boeing's Starliner Successfully Completes Orbital Test Flight",
36
+ url: "https://www.theverge.com/boeing-starliner",
37
+ snippet: "NASA, Nov 20 (Reuters) - Boeing's CST-100 Starliner spacecraft achieves a significant milestone, successfully completing its orbital test flight, paving the way for future crewed missions to the International Space Station.",
38
+ sourceName: "The Verge",
39
+ },
40
+ ];
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Mock todo data - stub implementation
3
+ * Replace this with real todo data from agent/store later
4
+ */
5
+ import type { TodoItem } from "../components/TodoListItem.js";
6
+ /**
7
+ * Stub todo data
8
+ * This data can be easily replaced with real todo queries from the agent
9
+ */
10
+ export declare const mockTodoData: TodoItem[];
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Mock todo data - stub implementation
3
+ * Replace this with real todo data from agent/store later
4
+ */
5
+ /**
6
+ * Stub todo data
7
+ * This data can be easily replaced with real todo queries from the agent
8
+ */
9
+ export const mockTodoData = [
10
+ {
11
+ id: "todo-1",
12
+ text: "Review filesystem implementation",
13
+ status: "completed",
14
+ },
15
+ {
16
+ id: "todo-2",
17
+ text: "Add overflow menu actions",
18
+ status: "in_progress",
19
+ },
20
+ {
21
+ id: "todo-3",
22
+ text: "Connect to real filesystem API",
23
+ status: "pending",
24
+ },
25
+ {
26
+ id: "todo-4",
27
+ text: "Implement drag and drop support",
28
+ status: "pending",
29
+ },
30
+ {
31
+ id: "todo-5",
32
+ text: "Add file preview functionality",
33
+ status: "pending",
34
+ },
35
+ ];
@@ -0,0 +1,5 @@
1
+ /**
2
+ * FileSystemDemo - A demo page showcasing the FileSystemView component
3
+ * Can be integrated into the GUI app for testing and demonstration
4
+ */
5
+ export declare function FileSystemDemo(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * FileSystemDemo - A demo page showcasing the FileSystemView component
4
+ * Can be integrated into the GUI app for testing and demonstration
5
+ */
6
+ import * as React from "react";
7
+ import { FileSystemView } from "../components/FileSystemView.js";
8
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/Card.js";
9
+ export function FileSystemDemo() {
10
+ const [selectedItem, setSelectedItem] = React.useState(null);
11
+ const handleDownload = (item) => {
12
+ console.log("Download:", item.name);
13
+ // Implement download logic
14
+ };
15
+ const handleRename = (item) => {
16
+ console.log("Rename:", item.name);
17
+ // Implement rename logic
18
+ };
19
+ const handleDelete = (item) => {
20
+ console.log("Delete:", item.name);
21
+ // Implement delete logic
22
+ };
23
+ return (_jsx("div", { className: "w-full h-full p-8 bg-background", children: _jsxs("div", { className: "max-w-7xl mx-auto space-y-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold tracking-tight", children: "File Manager" }), _jsx("p", { className: "text-muted-foreground mt-2", children: "A hierarchical file system view with stub data. Replace the MockFileSystemProvider with a real implementation to connect to actual filesystem data." })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-6", children: [_jsxs(Card, { className: "lg:col-span-2", children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Files" }), _jsx(CardDescription, { children: "Browse files and folders. Click folders to expand/collapse." })] }), _jsx(CardContent, { className: "p-0", children: _jsx(FileSystemView, { onItemSelect: setSelectedItem, onDownload: handleDownload, onRename: handleRename, onDelete: handleDelete, className: "max-h-[600px] overflow-y-auto" }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Details" }), _jsx(CardDescription, { children: "Information about the selected item" })] }), _jsx(CardContent, { children: selectedItem ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Name" }), _jsx("p", { className: "text-base font-mono", children: selectedItem.name })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Type" }), _jsx("p", { className: "text-base capitalize", children: selectedItem.type })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "ID" }), _jsx("p", { className: "text-base font-mono text-xs", children: selectedItem.id })] }), selectedItem.extension && (_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Extension" }), _jsxs("p", { className: "text-base", children: [".", selectedItem.extension] })] })), selectedItem.children && (_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Children" }), _jsxs("p", { className: "text-base", children: [selectedItem.children.length, " item(s)"] })] })), selectedItem.path && (_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Path" }), _jsx("p", { className: "text-base font-mono text-xs", children: selectedItem.path })] }))] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Select a file or folder to view details" })) })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Implementation Notes" }) }), _jsxs(CardContent, { className: "space-y-2 text-sm", children: [_jsxs("p", { children: [_jsx("strong", { children: "Stub System:" }), " Currently using ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "MockFileSystemProvider" }), " with hardcoded data."] }), _jsxs("p", { children: [_jsx("strong", { children: "Easy Replacement:" }), " Implement the ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "FileSystemProvider" }), " ", "interface to connect to real data sources."] }), _jsxs("p", { children: [_jsx("strong", { children: "Design:" }), " Based on shadcn file manager components and Figma specifications."] })] })] })] }) }));
24
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Example usage of FileSystemView component
3
+ * This demonstrates how to use the stub filesystem and how to replace it with real data
4
+ */
5
+ /**
6
+ * Basic usage with default mock data
7
+ */
8
+ export declare function BasicFileSystemExample(): import("react/jsx-runtime").JSX.Element;
9
+ /**
10
+ * Custom mock data example
11
+ * Shows how to create custom stub data
12
+ */
13
+ export declare function CustomDataExample(): import("react/jsx-runtime").JSX.Element;
14
+ /**
15
+ * Example with real provider (commented out to avoid actual API calls)
16
+ */
17
+ export declare function RealDataExample(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Example usage of FileSystemView component
4
+ * This demonstrates how to use the stub filesystem and how to replace it with real data
5
+ */
6
+ import * as React from "react";
7
+ import { FileSystemView } from "../components/FileSystemView.js";
8
+ import { MockFileSystemProvider } from "../data/mockFileSystemData.js";
9
+ /**
10
+ * Basic usage with default mock data
11
+ */
12
+ export function BasicFileSystemExample() {
13
+ return (_jsx("div", { className: "w-full h-full bg-background", children: _jsx(FileSystemView, { onItemSelect: (item) => {
14
+ console.log("Selected:", item);
15
+ } }) }));
16
+ }
17
+ /**
18
+ * Custom mock data example
19
+ * Shows how to create custom stub data
20
+ */
21
+ export function CustomDataExample() {
22
+ const customData = [
23
+ {
24
+ id: "src",
25
+ name: "src",
26
+ type: "folder",
27
+ children: [
28
+ {
29
+ id: "components",
30
+ name: "components",
31
+ type: "folder",
32
+ children: [
33
+ { id: "button", name: "Button.tsx", type: "file", extension: "tsx" },
34
+ { id: "card", name: "Card.tsx", type: "file", extension: "tsx" },
35
+ ],
36
+ },
37
+ { id: "app", name: "App.tsx", type: "file", extension: "tsx" },
38
+ { id: "index", name: "index.ts", type: "file", extension: "ts" },
39
+ ],
40
+ },
41
+ {
42
+ id: "public",
43
+ name: "public",
44
+ type: "folder",
45
+ children: [
46
+ { id: "favicon", name: "favicon.ico", type: "file" },
47
+ ],
48
+ },
49
+ ];
50
+ const customProvider = new MockFileSystemProvider(customData);
51
+ return (_jsx("div", { className: "w-full h-full bg-background", children: _jsx(FileSystemView, { provider: customProvider, onItemSelect: (item) => {
52
+ console.log("Selected:", item);
53
+ } }) }));
54
+ }
55
+ /**
56
+ * Example of how to implement a real FileSystemProvider
57
+ * Replace MockFileSystemProvider with this when connecting to real data
58
+ */
59
+ class RealFileSystemProvider {
60
+ apiUrl;
61
+ constructor(apiUrl) {
62
+ this.apiUrl = apiUrl;
63
+ }
64
+ async getRootItems() {
65
+ // Replace with actual API call
66
+ const response = await fetch(`${this.apiUrl}/filesystem/root`);
67
+ return response.json();
68
+ }
69
+ async getItemChildren(itemId) {
70
+ // Replace with actual API call
71
+ const response = await fetch(`${this.apiUrl}/filesystem/items/${itemId}/children`);
72
+ return response.json();
73
+ }
74
+ async getItemDetails(itemId) {
75
+ // Replace with actual API call
76
+ const response = await fetch(`${this.apiUrl}/filesystem/items/${itemId}`);
77
+ return response.json();
78
+ }
79
+ }
80
+ /**
81
+ * Example with real provider (commented out to avoid actual API calls)
82
+ */
83
+ export function RealDataExample() {
84
+ // Uncomment and configure when ready to use real data:
85
+ // const realProvider = new RealFileSystemProvider('https://your-api.com');
86
+ return (_jsx("div", { className: "w-full h-full bg-background", children: _jsx(FileSystemView
87
+ // provider={realProvider}
88
+ , {
89
+ // provider={realProvider}
90
+ onItemSelect: (item) => {
91
+ console.log("Selected:", item);
92
+ // Handle file selection - open file, show details, etc.
93
+ } }) }));
94
+ }
@@ -158,6 +158,8 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
158
158
  toolCall: z.ZodObject<{
159
159
  id: z.ZodString;
160
160
  title: z.ZodString;
161
+ prettyName: z.ZodOptional<z.ZodString>;
162
+ icon: z.ZodOptional<z.ZodString>;
161
163
  kind: z.ZodEnum<{
162
164
  read: "read";
163
165
  edit: "edit";
@@ -20,6 +20,7 @@ export declare class HttpTransport implements Transport {
20
20
  private abortController;
21
21
  private options;
22
22
  private isReceivingMessages;
23
+ private isInReplayMode;
23
24
  constructor(options: HttpTransportOptions);
24
25
  connect(): Promise<void>;
25
26
  /**
@@ -21,6 +21,7 @@ export class HttpTransport {
21
21
  abortController = null;
22
22
  options;
23
23
  isReceivingMessages = false;
24
+ isInReplayMode = false; // True during session replay, ignores non-replay streaming
24
25
  constructor(options) {
25
26
  // Ensure baseUrl doesn't end with a slash
26
27
  this.options = { ...options, baseUrl: options.baseUrl.replace(/\/$/, "") };
@@ -100,6 +101,8 @@ export class HttpTransport {
100
101
  capabilities: initResponse.agentCapabilities,
101
102
  });
102
103
  // Step 2: Open SSE connection FIRST so we can receive replayed messages
104
+ // Enter replay mode - ignore non-replay streaming until user sends a message
105
+ this.isInReplayMode = true;
103
106
  this.currentSessionId = sessionId;
104
107
  await this.connectSSE();
105
108
  // Step 3: Load existing session (will trigger message replay)
@@ -116,6 +119,7 @@ export class HttpTransport {
116
119
  });
117
120
  this.connected = true;
118
121
  this.reconnectAttempts = 0;
122
+ // Note: isInReplayMode will be set to false when user sends their first message
119
123
  }
120
124
  catch (error) {
121
125
  this.connected = false;
@@ -163,6 +167,11 @@ export class HttpTransport {
163
167
  if (!this.connected || !this.currentSessionId) {
164
168
  throw new Error("Transport not connected");
165
169
  }
170
+ // Exit replay mode when user sends their first message
171
+ if (this.isInReplayMode) {
172
+ logger.info("Exiting replay mode - user sent a message");
173
+ this.isInReplayMode = false;
174
+ }
166
175
  try {
167
176
  // Reset stream state for new message
168
177
  this.streamComplete = false;
@@ -510,10 +519,22 @@ export class HttpTransport {
510
519
  "messageId" in update._meta
511
520
  ? String(update._meta.messageId)
512
521
  : undefined;
522
+ const prettyName = update._meta &&
523
+ typeof update._meta === "object" &&
524
+ "prettyName" in update._meta
525
+ ? String(update._meta.prettyName)
526
+ : undefined;
527
+ const icon = update._meta &&
528
+ typeof update._meta === "object" &&
529
+ "icon" in update._meta
530
+ ? String(update._meta.icon)
531
+ : undefined;
513
532
  // Initial tool call notification
514
533
  const toolCall = {
515
534
  id: update.toolCallId ?? "",
516
535
  title: update.title ?? "",
536
+ prettyName,
537
+ icon,
517
538
  kind: update.kind || "other",
518
539
  status: update.status || "pending",
519
540
  locations: update.locations,
@@ -729,6 +750,15 @@ export class HttpTransport {
729
750
  this.notifySessionUpdate(sessionUpdate);
730
751
  }
731
752
  else if (update?.sessionUpdate === "agent_message_chunk") {
753
+ // Check if this is a replay (not live streaming)
754
+ const isReplay = update._meta &&
755
+ typeof update._meta === "object" &&
756
+ "isReplay" in update._meta &&
757
+ update._meta.isReplay === true;
758
+ // If in replay mode, ignore any chunks that aren't marked as replay
759
+ if (this.isInReplayMode && !isReplay) {
760
+ return;
761
+ }
732
762
  // Handle agent message chunks
733
763
  const sessionUpdate = {
734
764
  type: "generic",
@@ -741,10 +771,7 @@ export class HttpTransport {
741
771
  "tokenUsage" in update._meta
742
772
  ? update._meta.tokenUsage
743
773
  : undefined;
744
- logger.debug("Agent message chunk", {
745
- tokenUsage,
746
- });
747
- // Queue message chunks if present
774
+ // Queue message chunks if present (but skip during replay)
748
775
  // For agent_message_chunk, content is an object, not an array
749
776
  const content = update.content;
750
777
  if (content && typeof content === "object") {
@@ -759,7 +786,8 @@ export class HttpTransport {
759
786
  isComplete: false,
760
787
  };
761
788
  }
762
- if (chunk) {
789
+ // Only queue chunks for live streaming, not replay
790
+ if (chunk && !isReplay) {
763
791
  // Resolve any waiting receive() calls immediately
764
792
  const resolver = this.chunkResolvers.shift();
765
793
  if (resolver) {
@@ -771,10 +799,11 @@ export class HttpTransport {
771
799
  }
772
800
  }
773
801
  // Also notify as a complete message for session replay
774
- // Only send session updates when NOT actively receiving messages (prevents duplication during normal streaming)
802
+ // During replay: always send session updates with complete messages
803
+ // During live streaming: only send when NOT actively receiving (prevents duplication)
775
804
  if (chunk &&
776
805
  typeof contentObj.text === "string" &&
777
- !this.isReceivingMessages) {
806
+ (isReplay || !this.isReceivingMessages)) {
778
807
  const messageSessionUpdate = {
779
808
  type: "generic",
780
809
  sessionId,
@@ -795,7 +824,10 @@ export class HttpTransport {
795
824
  this.notifySessionUpdate(messageSessionUpdate);
796
825
  }
797
826
  }
798
- this.notifySessionUpdate(sessionUpdate);
827
+ // Only send generic session update during live streaming (not replay)
828
+ if (!isReplay) {
829
+ this.notifySessionUpdate(sessionUpdate);
830
+ }
799
831
  }
800
832
  else if (update?.sessionUpdate === "user_message_chunk") {
801
833
  // Handle user message chunks (could be from replay or new messages)
@@ -60,10 +60,23 @@ export class StdioTransport {
60
60
  const sessionId = self.currentSessionId || params.sessionId;
61
61
  // Handle ACP tool call notifications
62
62
  if (update?.sessionUpdate === "tool_call") {
63
+ // Extract prettyName and icon from _meta
64
+ const prettyName = update._meta &&
65
+ typeof update._meta === "object" &&
66
+ "prettyName" in update._meta
67
+ ? String(update._meta.prettyName)
68
+ : undefined;
69
+ const icon = update._meta &&
70
+ typeof update._meta === "object" &&
71
+ "icon" in update._meta
72
+ ? String(update._meta.icon)
73
+ : undefined;
63
74
  // Initial tool call notification
64
75
  const toolCall = {
65
76
  id: update.toolCallId ?? "",
66
77
  title: update.title ?? "",
78
+ prettyName,
79
+ icon,
67
80
  kind: update.kind || "other",
68
81
  status: update.status || "pending",
69
82
  locations: update.locations,
@@ -12,10 +12,10 @@ export function ChatView({ client }) {
12
12
  const _currentModel = useChatStore((state) => state.currentModel);
13
13
  const _tokenDisplayMode = useChatStore((state) => state.tokenDisplayMode);
14
14
  // Use headless hooks for business logic
15
- useChatSession(client); // Subscribe to session changes
16
- const { messages, isStreaming } = useChatMessages(client);
15
+ const { startSession } = useChatSession(client); // Subscribe to session changes
16
+ const { messages, isStreaming } = useChatMessages(client, startSession);
17
17
  useToolCalls(client); // Still need to subscribe to tool call events
18
- const { value, isSubmitting, attachedFiles, onChange, onSubmit } = useChatInput(client);
18
+ const { value, isSubmitting, attachedFiles, onChange, onSubmit } = useChatInput(client, startSession);
19
19
  // Check if we're actively receiving content (hide waiting indicator)
20
20
  const _hasStreamingContent = messages.some((msg) => msg.isStreaming && msg.content.length > 0);
21
21
  // Callbacks for keyboard shortcuts
@@ -34,8 +34,8 @@ export function ChatViewStatus({ client }) {
34
34
  const currentContext = useChatStore((state) => state.currentContext);
35
35
  const currentModel = useChatStore((state) => state.currentModel);
36
36
  const tokenDisplayMode = useChatStore((state) => state.tokenDisplayMode);
37
- const { connectionStatus, sessionId } = useChatSession(client);
38
- const { messages, isStreaming } = useChatMessages(client);
37
+ const { connectionStatus, sessionId, startSession } = useChatSession(client);
38
+ const { messages, isStreaming } = useChatMessages(client, startSession);
39
39
  const hasStreamingContent = messages.some((msg) => msg.isStreaming && msg.content.length > 0);
40
40
  return (_jsx(StatusBar, { connectionStatus: connectionStatus, sessionId: sessionId, isStreaming: isStreaming, streamingStartTime: streamingStartTime, hasStreamingContent: hasStreamingContent, totalBilled: totalBilled, currentContext: currentContext, currentModel: currentModel, tokenDisplayMode: tokenDisplayMode }));
41
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/ui",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@agentclientprotocol/sdk": "^0.5.1",
43
- "@townco/core": "0.0.14",
43
+ "@townco/core": "0.0.16",
44
44
  "@radix-ui/react-dialog": "^1.1.15",
45
45
  "@radix-ui/react-dropdown-menu": "^2.1.16",
46
46
  "@radix-ui/react-label": "^2.1.8",
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "devDependencies": {
64
64
  "@tailwindcss/postcss": "^4.1.17",
65
- "@townco/tsconfig": "0.1.33",
65
+ "@townco/tsconfig": "0.1.35",
66
66
  "@types/node": "^24.10.0",
67
67
  "@types/react": "^19.2.2",
68
68
  "ink": "^6.4.0",