@townco/ui 0.1.48 → 0.1.50

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 (51) hide show
  1. package/dist/core/hooks/index.d.ts +1 -0
  2. package/dist/core/hooks/index.js +1 -0
  3. package/dist/core/hooks/use-chat-input.d.ts +2 -0
  4. package/dist/core/hooks/use-chat-input.js +11 -1
  5. package/dist/core/hooks/use-chat-messages.d.ts +22 -1
  6. package/dist/core/hooks/use-chat-messages.js +19 -4
  7. package/dist/core/hooks/use-chat-session.d.ts +1 -1
  8. package/dist/core/hooks/use-chat-session.js +22 -0
  9. package/dist/core/hooks/use-message-history.d.ts +12 -0
  10. package/dist/core/hooks/use-message-history.js +113 -0
  11. package/dist/core/hooks/use-tool-calls.d.ts +11 -0
  12. package/dist/core/schemas/chat.d.ts +40 -0
  13. package/dist/core/schemas/chat.js +9 -0
  14. package/dist/core/schemas/tool-call.d.ts +34 -0
  15. package/dist/core/schemas/tool-call.js +27 -0
  16. package/dist/core/store/chat-store.d.ts +5 -0
  17. package/dist/core/store/chat-store.js +46 -0
  18. package/dist/gui/components/ChatEmptyState.d.ts +4 -0
  19. package/dist/gui/components/ChatEmptyState.js +2 -2
  20. package/dist/gui/components/ChatInput.d.ts +21 -1
  21. package/dist/gui/components/ChatInput.js +184 -7
  22. package/dist/gui/components/ChatLayout.d.ts +3 -2
  23. package/dist/gui/components/ChatLayout.js +7 -7
  24. package/dist/gui/components/ChatPanelTabContent.d.ts +17 -0
  25. package/dist/gui/components/ChatPanelTabContent.js +6 -5
  26. package/dist/gui/components/ChatView.d.ts +3 -1
  27. package/dist/gui/components/ChatView.js +81 -49
  28. package/dist/gui/components/InvokingGroup.d.ts +9 -0
  29. package/dist/gui/components/InvokingGroup.js +16 -0
  30. package/dist/gui/components/MessageContent.js +122 -6
  31. package/dist/gui/components/PanelTabsHeader.d.ts +1 -1
  32. package/dist/gui/components/PanelTabsHeader.js +6 -1
  33. package/dist/gui/components/Response.js +2 -0
  34. package/dist/gui/components/Task.js +3 -3
  35. package/dist/gui/components/TodoListItem.js +1 -1
  36. package/dist/gui/components/ToolCall.js +57 -5
  37. package/dist/gui/components/ToolCallGroup.d.ts +8 -0
  38. package/dist/gui/components/ToolCallGroup.js +29 -0
  39. package/dist/gui/components/index.d.ts +1 -2
  40. package/dist/gui/components/index.js +1 -2
  41. package/dist/sdk/client/acp-client.d.ts +24 -1
  42. package/dist/sdk/client/acp-client.js +28 -7
  43. package/dist/sdk/schemas/message.d.ts +41 -9
  44. package/dist/sdk/schemas/message.js +15 -3
  45. package/dist/sdk/schemas/session.d.ts +81 -16
  46. package/dist/sdk/transports/http.d.ts +14 -0
  47. package/dist/sdk/transports/http.js +130 -36
  48. package/dist/sdk/transports/stdio.d.ts +14 -0
  49. package/dist/sdk/transports/stdio.js +27 -10
  50. package/dist/sdk/transports/types.d.ts +29 -0
  51. package/package.json +7 -3
@@ -6,4 +6,5 @@ export * from "./use-chat-input.js";
6
6
  export * from "./use-chat-messages.js";
7
7
  export * from "./use-chat-session.js";
8
8
  export * from "./use-media-query.js";
9
+ export * from "./use-message-history.js";
9
10
  export * from "./use-tool-calls.js";
@@ -6,4 +6,5 @@ export * from "./use-chat-input.js";
6
6
  export * from "./use-chat-messages.js";
7
7
  export * from "./use-chat-session.js";
8
8
  export * from "./use-media-query.js";
9
+ export * from "./use-message-history.js";
9
10
  export * from "./use-tool-calls.js";
@@ -10,6 +10,7 @@ export declare function useChatInput(client: AcpClient | null, startSession: ()
10
10
  path: string;
11
11
  size: number;
12
12
  mimeType: string;
13
+ data: string;
13
14
  }[];
14
15
  onChange: (value: string) => void;
15
16
  onSubmit: () => Promise<void>;
@@ -18,6 +19,7 @@ export declare function useChatInput(client: AcpClient | null, startSession: ()
18
19
  path: string;
19
20
  size: number;
20
21
  mimeType: string;
22
+ data: string;
21
23
  }) => void;
22
24
  onRemoveFile: (index: number) => void;
23
25
  };
@@ -27,11 +27,21 @@ export function useChatInput(client, startSession) {
27
27
  return;
28
28
  }
29
29
  const message = input.value;
30
+ const attachments = input.attachedFiles;
31
+ logger.debug("Submitting message with attachments", {
32
+ messageLength: message.length,
33
+ attachmentCount: attachments.length,
34
+ hasAttachments: attachments.length > 0,
35
+ });
30
36
  // Clear input immediately for better UX
31
37
  setInputValue("");
32
38
  setInputSubmitting(true);
33
39
  try {
34
- await sendMessage(message);
40
+ await sendMessage(message, attachments.length > 0 ? attachments : undefined);
41
+ // Clear attachments after successful send
42
+ useChatStore.setState((state) => ({
43
+ input: { ...state.input, attachedFiles: [] },
44
+ }));
35
45
  }
36
46
  catch (error) {
37
47
  // Error is handled in useChatMessages
@@ -16,8 +16,10 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
16
16
  title: string;
17
17
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
18
18
  status: "pending" | "in_progress" | "completed" | "failed";
19
+ batchId?: string | undefined;
19
20
  prettyName?: string | undefined;
20
21
  icon?: string | undefined;
22
+ subline?: string | undefined;
21
23
  contentPosition?: number | undefined;
22
24
  locations?: {
23
25
  path: string;
@@ -34,6 +36,15 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
34
36
  } | {
35
37
  type: "text";
36
38
  text: string;
39
+ } | {
40
+ type: "image";
41
+ data: string;
42
+ mimeType?: string | undefined;
43
+ alt?: string | undefined;
44
+ } | {
45
+ type: "image";
46
+ url: string;
47
+ alt?: string | undefined;
37
48
  } | {
38
49
  type: "diff";
39
50
  path: string;
@@ -64,7 +75,17 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
64
75
  outputTokens?: number | undefined;
65
76
  totalTokens?: number | undefined;
66
77
  } | undefined;
78
+ images?: {
79
+ mimeType: string;
80
+ data: string;
81
+ }[] | undefined;
67
82
  }[];
68
83
  isStreaming: boolean;
69
- sendMessage: (content: string) => Promise<void>;
84
+ sendMessage: (content: string, attachments?: Array<{
85
+ name: string;
86
+ path: string;
87
+ size: number;
88
+ mimeType: string;
89
+ data: string;
90
+ }>) => Promise<void>;
70
91
  };
@@ -18,7 +18,12 @@ export function useChatMessages(client, startSession) {
18
18
  /**
19
19
  * Send a message to the agent
20
20
  */
21
- const sendMessage = useCallback(async (content) => {
21
+ const sendMessage = useCallback(async (content, attachments) => {
22
+ logger.debug("[sendMessage] Called with", {
23
+ contentLength: content.length,
24
+ attachmentsCount: attachments?.length || 0,
25
+ hasAttachments: !!attachments && attachments.length > 0,
26
+ });
22
27
  if (!client) {
23
28
  logger.error("No client available");
24
29
  setError("No client available");
@@ -46,13 +51,21 @@ export function useChatMessages(client, startSession) {
46
51
  const startTime = Date.now();
47
52
  setIsStreaming(true);
48
53
  setStreamingStartTime(startTime);
49
- // Add user message to UI
54
+ // Add user message to UI with images
50
55
  const userMessage = {
51
56
  id: `msg_${Date.now()}_user`,
52
57
  role: "user",
53
- content,
58
+ content: content,
54
59
  timestamp: new Date().toISOString(),
55
60
  isStreaming: false,
61
+ // Include images for display
62
+ ...(attachments && attachments.length > 0
63
+ ? {
64
+ images: attachments
65
+ .filter((a) => a.mimeType.startsWith("image/"))
66
+ .map((a) => ({ mimeType: a.mimeType, data: a.data })),
67
+ }
68
+ : {}),
56
69
  };
57
70
  addMessage(userMessage);
58
71
  // Create placeholder for assistant message BEFORE sending
@@ -69,7 +82,9 @@ export function useChatMessages(client, startSession) {
69
82
  const messageStream = client.receiveMessages();
70
83
  // Send ONLY the new message (not full history)
71
84
  // The agent backend now manages conversation context
72
- client.sendMessage(content, activeSessionId).catch((error) => {
85
+ client
86
+ .sendMessage(content, activeSessionId, attachments)
87
+ .catch((error) => {
73
88
  const message = error instanceof Error ? error.message : String(error);
74
89
  setError(message);
75
90
  setIsStreaming(false);
@@ -3,7 +3,7 @@ import type { AcpClient } from "../../sdk/client/index.js";
3
3
  * Hook for managing chat session lifecycle
4
4
  */
5
5
  export declare function useChatSession(client: AcpClient | null, initialSessionId?: string | null): {
6
- connectionStatus: "disconnected" | "connecting" | "connected" | "error";
6
+ connectionStatus: "error" | "connecting" | "connected" | "disconnected";
7
7
  sessionId: string | null;
8
8
  connect: () => Promise<void>;
9
9
  loadSession: (sessionIdToLoad: string) => Promise<void>;
@@ -48,6 +48,26 @@ export function useChatSession(client, initialSessionId) {
48
48
  return "";
49
49
  })
50
50
  .join("");
51
+ // Extract image blocks for user messages
52
+ const imageBlocks = [];
53
+ for (const c of update.message.content) {
54
+ if (c.type === "image") {
55
+ const imgBlock = c;
56
+ // Handle both formats: direct data/mimeType or source object
57
+ if (imgBlock.source?.data) {
58
+ imageBlocks.push({
59
+ mimeType: imgBlock.source.media_type || "image/png",
60
+ data: imgBlock.source.data,
61
+ });
62
+ }
63
+ else if (imgBlock.data) {
64
+ imageBlocks.push({
65
+ mimeType: imgBlock.mimeType || "image/png",
66
+ data: imgBlock.data,
67
+ });
68
+ }
69
+ }
70
+ }
51
71
  // During session replay, chunks for the same message arrive separately
52
72
  // Check if we should append to the last assistant message or create a new one
53
73
  const messages = useChatStore.getState().messages;
@@ -70,6 +90,8 @@ export function useChatSession(client, initialSessionId) {
70
90
  content: textContent,
71
91
  timestamp: update.message.timestamp,
72
92
  isStreaming: false,
93
+ // Include images if present (for user messages)
94
+ ...(imageBlocks.length > 0 ? { images: imageBlocks } : {}),
73
95
  };
74
96
  addMessage(displayMessage);
75
97
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Hook for managing message history with localStorage persistence.
3
+ * Allows navigating through previously sent messages with up/down arrow keys.
4
+ */
5
+ export declare function useMessageHistory(): {
6
+ addToHistory: (message: string) => void;
7
+ navigatePrevious: (currentValue: string) => string | null;
8
+ navigateNext: () => string | null;
9
+ resetNavigation: () => void;
10
+ isBrowsingHistory: boolean;
11
+ historyIndex: number;
12
+ };
@@ -0,0 +1,113 @@
1
+ import { useCallback, useState } from "react";
2
+ const STORAGE_KEY = "town-message-history";
3
+ const MAX_HISTORY_SIZE = 100;
4
+ /**
5
+ * Hook for managing message history with localStorage persistence.
6
+ * Allows navigating through previously sent messages with up/down arrow keys.
7
+ */
8
+ export function useMessageHistory() {
9
+ // Index into history, -1 means "not browsing history"
10
+ const [historyIndex, setHistoryIndex] = useState(-1);
11
+ // Draft message saved when user starts browsing history
12
+ const [draftMessage, setDraftMessage] = useState("");
13
+ /**
14
+ * Get all messages from history
15
+ */
16
+ const getHistory = useCallback(() => {
17
+ try {
18
+ const stored = localStorage.getItem(STORAGE_KEY);
19
+ return stored ? JSON.parse(stored) : [];
20
+ }
21
+ catch {
22
+ return [];
23
+ }
24
+ }, []);
25
+ /**
26
+ * Add a message to history (called when message is sent)
27
+ */
28
+ const addToHistory = useCallback((message) => {
29
+ const trimmed = message.trim();
30
+ if (!trimmed)
31
+ return;
32
+ try {
33
+ const history = getHistory();
34
+ // Don't add duplicate of most recent message
35
+ if (history[0] === trimmed)
36
+ return;
37
+ // Add to beginning of array (most recent first)
38
+ const newHistory = [trimmed, ...history].slice(0, MAX_HISTORY_SIZE);
39
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(newHistory));
40
+ }
41
+ catch {
42
+ // Ignore localStorage errors
43
+ }
44
+ // Reset navigation state
45
+ setHistoryIndex(-1);
46
+ setDraftMessage("");
47
+ }, [getHistory]);
48
+ /**
49
+ * Navigate to previous message (up arrow)
50
+ * Returns the message to display, or null if no navigation happened
51
+ */
52
+ const navigatePrevious = useCallback((currentValue) => {
53
+ const history = getHistory();
54
+ if (history.length === 0)
55
+ return null;
56
+ // If starting to browse history, save current input as draft
57
+ if (historyIndex === -1) {
58
+ setDraftMessage(currentValue);
59
+ }
60
+ const newIndex = Math.min(historyIndex + 1, history.length - 1);
61
+ if (newIndex === historyIndex)
62
+ return null; // Already at oldest
63
+ const message = history[newIndex];
64
+ if (message === undefined)
65
+ return null;
66
+ setHistoryIndex(newIndex);
67
+ return message;
68
+ }, [historyIndex, getHistory]);
69
+ /**
70
+ * Navigate to next message (down arrow)
71
+ * Returns the message to display, or null if no navigation happened
72
+ */
73
+ const navigateNext = useCallback(() => {
74
+ // Not browsing history
75
+ if (historyIndex === -1)
76
+ return null;
77
+ const history = getHistory();
78
+ if (historyIndex === 0) {
79
+ // Return to draft
80
+ setHistoryIndex(-1);
81
+ const draft = draftMessage;
82
+ setDraftMessage("");
83
+ return draft;
84
+ }
85
+ const newIndex = historyIndex - 1;
86
+ const message = history[newIndex];
87
+ if (message === undefined)
88
+ return null;
89
+ setHistoryIndex(newIndex);
90
+ return message;
91
+ }, [historyIndex, draftMessage, getHistory]);
92
+ /**
93
+ * Reset history navigation (called when user types something new)
94
+ */
95
+ const resetNavigation = useCallback(() => {
96
+ if (historyIndex !== -1) {
97
+ setHistoryIndex(-1);
98
+ setDraftMessage("");
99
+ }
100
+ }, [historyIndex]);
101
+ /**
102
+ * Check if currently browsing history
103
+ */
104
+ const isBrowsingHistory = historyIndex !== -1;
105
+ return {
106
+ addToHistory,
107
+ navigatePrevious,
108
+ navigateNext,
109
+ resetNavigation,
110
+ isBrowsingHistory,
111
+ historyIndex,
112
+ };
113
+ }
@@ -14,8 +14,10 @@ export declare function useToolCalls(client: AcpClient | null): {
14
14
  title: string;
15
15
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
16
16
  status: "pending" | "in_progress" | "completed" | "failed";
17
+ batchId?: string | undefined;
17
18
  prettyName?: string | undefined;
18
19
  icon?: string | undefined;
20
+ subline?: string | undefined;
19
21
  contentPosition?: number | undefined;
20
22
  locations?: {
21
23
  path: string;
@@ -32,6 +34,15 @@ export declare function useToolCalls(client: AcpClient | null): {
32
34
  } | {
33
35
  type: "text";
34
36
  text: string;
37
+ } | {
38
+ type: "image";
39
+ data: string;
40
+ mimeType?: string | undefined;
41
+ alt?: string | undefined;
42
+ } | {
43
+ type: "image";
44
+ url: string;
45
+ alt?: string | undefined;
35
46
  } | {
36
47
  type: "diff";
37
48
  path: string;
@@ -2,6 +2,14 @@ import { z } from "zod";
2
2
  /**
3
3
  * Chat UI state schemas
4
4
  */
5
+ /**
6
+ * Image attachment for display
7
+ */
8
+ export declare const DisplayImageAttachment: z.ZodObject<{
9
+ mimeType: z.ZodString;
10
+ data: z.ZodString;
11
+ }, z.core.$strip>;
12
+ export type DisplayImageAttachment = z.infer<typeof DisplayImageAttachment>;
5
13
  /**
6
14
  * Display message schema (UI representation of messages)
7
15
  */
@@ -19,9 +27,11 @@ export declare const DisplayMessage: z.ZodObject<{
19
27
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
20
28
  toolCalls: z.ZodOptional<z.ZodArray<z.ZodObject<{
21
29
  id: z.ZodString;
30
+ batchId: z.ZodOptional<z.ZodString>;
22
31
  title: z.ZodString;
23
32
  prettyName: z.ZodOptional<z.ZodString>;
24
33
  icon: z.ZodOptional<z.ZodString>;
34
+ subline: z.ZodOptional<z.ZodString>;
25
35
  kind: z.ZodEnum<{
26
36
  read: "read";
27
37
  edit: "edit";
@@ -56,6 +66,15 @@ export declare const DisplayMessage: z.ZodObject<{
56
66
  }, z.core.$strip>, z.ZodObject<{
57
67
  type: z.ZodLiteral<"text">;
58
68
  text: z.ZodString;
69
+ }, z.core.$strip>, z.ZodObject<{
70
+ type: z.ZodLiteral<"image">;
71
+ data: z.ZodString;
72
+ mimeType: z.ZodOptional<z.ZodString>;
73
+ alt: z.ZodOptional<z.ZodString>;
74
+ }, z.core.$strip>, z.ZodObject<{
75
+ type: z.ZodLiteral<"image">;
76
+ url: z.ZodString;
77
+ alt: z.ZodOptional<z.ZodString>;
59
78
  }, z.core.$strip>, z.ZodObject<{
60
79
  type: z.ZodLiteral<"diff">;
61
80
  path: z.ZodString;
@@ -89,6 +108,10 @@ export declare const DisplayMessage: z.ZodObject<{
89
108
  outputTokens: z.ZodOptional<z.ZodNumber>;
90
109
  totalTokens: z.ZodOptional<z.ZodNumber>;
91
110
  }, z.core.$strip>>;
111
+ images: z.ZodOptional<z.ZodArray<z.ZodObject<{
112
+ mimeType: z.ZodString;
113
+ data: z.ZodString;
114
+ }, z.core.$strip>>>;
92
115
  }, z.core.$strip>;
93
116
  export type DisplayMessage = z.infer<typeof DisplayMessage>;
94
117
  /**
@@ -102,6 +125,7 @@ export declare const InputState: z.ZodObject<{
102
125
  path: z.ZodString;
103
126
  size: z.ZodNumber;
104
127
  mimeType: z.ZodString;
128
+ data: z.ZodString;
105
129
  }, z.core.$strip>>;
106
130
  }, z.core.$strip>;
107
131
  export type InputState = z.infer<typeof InputState>;
@@ -126,9 +150,11 @@ export declare const ChatSessionState: z.ZodObject<{
126
150
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
127
151
  toolCalls: z.ZodOptional<z.ZodArray<z.ZodObject<{
128
152
  id: z.ZodString;
153
+ batchId: z.ZodOptional<z.ZodString>;
129
154
  title: z.ZodString;
130
155
  prettyName: z.ZodOptional<z.ZodString>;
131
156
  icon: z.ZodOptional<z.ZodString>;
157
+ subline: z.ZodOptional<z.ZodString>;
132
158
  kind: z.ZodEnum<{
133
159
  read: "read";
134
160
  edit: "edit";
@@ -163,6 +189,15 @@ export declare const ChatSessionState: z.ZodObject<{
163
189
  }, z.core.$strip>, z.ZodObject<{
164
190
  type: z.ZodLiteral<"text">;
165
191
  text: z.ZodString;
192
+ }, z.core.$strip>, z.ZodObject<{
193
+ type: z.ZodLiteral<"image">;
194
+ data: z.ZodString;
195
+ mimeType: z.ZodOptional<z.ZodString>;
196
+ alt: z.ZodOptional<z.ZodString>;
197
+ }, z.core.$strip>, z.ZodObject<{
198
+ type: z.ZodLiteral<"image">;
199
+ url: z.ZodString;
200
+ alt: z.ZodOptional<z.ZodString>;
166
201
  }, z.core.$strip>, z.ZodObject<{
167
202
  type: z.ZodLiteral<"diff">;
168
203
  path: z.ZodString;
@@ -196,6 +231,10 @@ export declare const ChatSessionState: z.ZodObject<{
196
231
  outputTokens: z.ZodOptional<z.ZodNumber>;
197
232
  totalTokens: z.ZodOptional<z.ZodNumber>;
198
233
  }, z.core.$strip>>;
234
+ images: z.ZodOptional<z.ZodArray<z.ZodObject<{
235
+ mimeType: z.ZodString;
236
+ data: z.ZodString;
237
+ }, z.core.$strip>>>;
199
238
  }, z.core.$strip>>;
200
239
  input: z.ZodObject<{
201
240
  value: z.ZodString;
@@ -205,6 +244,7 @@ export declare const ChatSessionState: z.ZodObject<{
205
244
  path: z.ZodString;
206
245
  size: z.ZodNumber;
207
246
  mimeType: z.ZodString;
247
+ data: z.ZodString;
208
248
  }, z.core.$strip>>;
209
249
  }, z.core.$strip>;
210
250
  error: z.ZodNullable<z.ZodString>;
@@ -3,6 +3,13 @@ import { TokenUsageSchema, ToolCallSchema } from "./tool-call.js";
3
3
  /**
4
4
  * Chat UI state schemas
5
5
  */
6
+ /**
7
+ * Image attachment for display
8
+ */
9
+ export const DisplayImageAttachment = z.object({
10
+ mimeType: z.string(),
11
+ data: z.string(), // base64 encoded
12
+ });
6
13
  /**
7
14
  * Display message schema (UI representation of messages)
8
15
  */
@@ -16,6 +23,7 @@ export const DisplayMessage = z.object({
16
23
  metadata: z.record(z.string(), z.unknown()).optional(),
17
24
  toolCalls: z.array(ToolCallSchema).optional(),
18
25
  tokenUsage: TokenUsageSchema.optional(), // Token usage for this message
26
+ images: z.array(DisplayImageAttachment).optional(), // Image attachments for user messages
19
27
  });
20
28
  /**
21
29
  * Input state schema
@@ -28,6 +36,7 @@ export const InputState = z.object({
28
36
  path: z.string(),
29
37
  size: z.number(),
30
38
  mimeType: z.string(),
39
+ data: z.string(), // base64 encoded file data
31
40
  })),
32
41
  });
33
42
  /**
@@ -54,6 +54,15 @@ export declare const ToolCallContentBlockSchema: z.ZodDiscriminatedUnion<[z.ZodO
54
54
  }, z.core.$strip>, z.ZodObject<{
55
55
  type: z.ZodLiteral<"text">;
56
56
  text: z.ZodString;
57
+ }, z.core.$strip>, z.ZodObject<{
58
+ type: z.ZodLiteral<"image">;
59
+ data: z.ZodString;
60
+ mimeType: z.ZodOptional<z.ZodString>;
61
+ alt: z.ZodOptional<z.ZodString>;
62
+ }, z.core.$strip>, z.ZodObject<{
63
+ type: z.ZodLiteral<"image">;
64
+ url: z.ZodString;
65
+ alt: z.ZodOptional<z.ZodString>;
57
66
  }, z.core.$strip>, z.ZodObject<{
58
67
  type: z.ZodLiteral<"diff">;
59
68
  path: z.ZodString;
@@ -70,9 +79,11 @@ export type ToolCallContentBlock = z.infer<typeof ToolCallContentBlockSchema>;
70
79
  */
71
80
  export declare const ToolCallSchema: z.ZodObject<{
72
81
  id: z.ZodString;
82
+ batchId: z.ZodOptional<z.ZodString>;
73
83
  title: z.ZodString;
74
84
  prettyName: z.ZodOptional<z.ZodString>;
75
85
  icon: z.ZodOptional<z.ZodString>;
86
+ subline: z.ZodOptional<z.ZodString>;
76
87
  kind: z.ZodEnum<{
77
88
  read: "read";
78
89
  edit: "edit";
@@ -107,6 +118,15 @@ export declare const ToolCallSchema: z.ZodObject<{
107
118
  }, z.core.$strip>, z.ZodObject<{
108
119
  type: z.ZodLiteral<"text">;
109
120
  text: z.ZodString;
121
+ }, z.core.$strip>, z.ZodObject<{
122
+ type: z.ZodLiteral<"image">;
123
+ data: z.ZodString;
124
+ mimeType: z.ZodOptional<z.ZodString>;
125
+ alt: z.ZodOptional<z.ZodString>;
126
+ }, z.core.$strip>, z.ZodObject<{
127
+ type: z.ZodLiteral<"image">;
128
+ url: z.ZodString;
129
+ alt: z.ZodOptional<z.ZodString>;
110
130
  }, z.core.$strip>, z.ZodObject<{
111
131
  type: z.ZodLiteral<"diff">;
112
132
  path: z.ZodString;
@@ -147,6 +167,11 @@ export declare const ToolCallUpdateSchema: z.ZodObject<{
147
167
  completed: "completed";
148
168
  failed: "failed";
149
169
  }>>;
170
+ title: z.ZodOptional<z.ZodString>;
171
+ prettyName: z.ZodOptional<z.ZodString>;
172
+ icon: z.ZodOptional<z.ZodString>;
173
+ batchId: z.ZodOptional<z.ZodString>;
174
+ rawInput: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
150
175
  locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
151
176
  path: z.ZodString;
152
177
  line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
@@ -161,6 +186,15 @@ export declare const ToolCallUpdateSchema: z.ZodObject<{
161
186
  }, z.core.$strip>, z.ZodObject<{
162
187
  type: z.ZodLiteral<"text">;
163
188
  text: z.ZodString;
189
+ }, z.core.$strip>, z.ZodObject<{
190
+ type: z.ZodLiteral<"image">;
191
+ data: z.ZodString;
192
+ mimeType: z.ZodOptional<z.ZodString>;
193
+ alt: z.ZodOptional<z.ZodString>;
194
+ }, z.core.$strip>, z.ZodObject<{
195
+ type: z.ZodLiteral<"image">;
196
+ url: z.ZodString;
197
+ alt: z.ZodOptional<z.ZodString>;
164
198
  }, z.core.$strip>, z.ZodObject<{
165
199
  type: z.ZodLiteral<"diff">;
166
200
  path: z.ZodString;
@@ -55,6 +55,19 @@ export const ToolCallContentBlockSchema = z.discriminatedUnion("type", [
55
55
  type: z.literal("text"),
56
56
  text: z.string(),
57
57
  }),
58
+ // Image content block (base64)
59
+ z.object({
60
+ type: z.literal("image"),
61
+ data: z.string(), // Base64 encoded image data
62
+ mimeType: z.string().optional(),
63
+ alt: z.string().optional(),
64
+ }),
65
+ // Image URL content block
66
+ z.object({
67
+ type: z.literal("image"),
68
+ url: z.string(), // URL or file path to the image
69
+ alt: z.string().optional(),
70
+ }),
58
71
  z.object({
59
72
  type: z.literal("diff"),
60
73
  path: z.string(),
@@ -73,12 +86,16 @@ export const ToolCallContentBlockSchema = z.discriminatedUnion("type", [
73
86
  export const ToolCallSchema = z.object({
74
87
  /** Unique identifier within the session */
75
88
  id: z.string(),
89
+ /** Batch identifier for parallel tool calls - tool calls with the same batchId executed together */
90
+ batchId: z.string().optional(),
76
91
  /** Human-readable description of the operation */
77
92
  title: z.string(),
78
93
  /** Optional pretty name for the tool (e.g. "Web Search" instead of "web_search") */
79
94
  prettyName: z.string().optional(),
80
95
  /** Optional icon identifier for the tool (e.g. "Globe", "Search", "Edit") */
81
96
  icon: z.string().optional(),
97
+ /** Optional subline text to display below the tool title (e.g. current task status) */
98
+ subline: z.string().optional(),
82
99
  /** Category for UI presentation */
83
100
  kind: ToolCallKindSchema,
84
101
  /** Current execution status */
@@ -117,6 +134,11 @@ export const ToolCallSchema = z.object({
117
134
  export const ToolCallUpdateSchema = z.object({
118
135
  id: z.string(),
119
136
  status: ToolCallStatusSchema.optional(),
137
+ title: z.string().optional(),
138
+ prettyName: z.string().optional(),
139
+ icon: z.string().optional(),
140
+ batchId: z.string().optional(),
141
+ rawInput: z.record(z.string(), z.unknown()).optional(),
120
142
  locations: z.array(FileLocationSchema).optional(),
121
143
  rawOutput: z.record(z.string(), z.unknown()).optional(),
122
144
  content: z.array(ToolCallContentBlockSchema).optional(),
@@ -132,6 +154,11 @@ export function mergeToolCallUpdate(existing, update) {
132
154
  ...existing,
133
155
  // Only update fields that are defined in the update
134
156
  status: update.status ?? existing.status,
157
+ title: update.title ?? existing.title,
158
+ prettyName: update.prettyName ?? existing.prettyName,
159
+ icon: update.icon ?? existing.icon,
160
+ batchId: update.batchId ?? existing.batchId,
161
+ rawInput: update.rawInput ?? existing.rawInput,
135
162
  locations: update.locations ?? existing.locations,
136
163
  rawOutput: update.rawOutput ?? existing.rawOutput,
137
164
  content: update.content
@@ -1,6 +1,11 @@
1
1
  import { type LogEntry } from "@townco/core";
2
+ import type { TodoItem } from "../../gui/components/TodoListItem.js";
2
3
  import type { ConnectionStatus, DisplayMessage, InputState } from "../schemas/index.js";
3
4
  import type { ToolCall, ToolCallUpdate } from "../schemas/tool-call.js";
5
+ /**
6
+ * Selector to get todos for the current session (memoized to prevent infinite loops)
7
+ */
8
+ export declare const selectTodosForCurrentSession: (state: ChatStore) => TodoItem[];
4
9
  /**
5
10
  * Context size breakdown from agent
6
11
  */