@townco/ui 0.1.77 → 0.1.79

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 (49) hide show
  1. package/dist/core/hooks/use-chat-messages.d.ts +6 -4
  2. package/dist/core/hooks/use-chat-messages.js +4 -1
  3. package/dist/core/hooks/use-chat-session.d.ts +1 -1
  4. package/dist/core/hooks/use-chat-session.js +5 -1
  5. package/dist/core/hooks/use-subagent-stream.js +6 -6
  6. package/dist/core/hooks/use-tool-calls.d.ts +5 -3
  7. package/dist/core/hooks/use-tool-calls.js +1 -1
  8. package/dist/core/schemas/chat.d.ts +14 -10
  9. package/dist/core/schemas/tool-call.d.ts +21 -8
  10. package/dist/core/schemas/tool-call.js +15 -0
  11. package/dist/core/store/chat-store.js +1 -0
  12. package/dist/core/utils/tool-summary.js +8 -3
  13. package/dist/core/utils/tool-verbiage.js +1 -1
  14. package/dist/gui/components/AppSidebar.d.ts +1 -1
  15. package/dist/gui/components/AppSidebar.js +4 -3
  16. package/dist/gui/components/ChatEmptyState.js +1 -1
  17. package/dist/gui/components/ChatHeader.d.ts +1 -28
  18. package/dist/gui/components/ChatHeader.js +4 -71
  19. package/dist/gui/components/ChatLayout.d.ts +6 -2
  20. package/dist/gui/components/ChatLayout.js +82 -33
  21. package/dist/gui/components/ChatView.js +28 -45
  22. package/dist/gui/components/ContextUsageButton.d.ts +0 -1
  23. package/dist/gui/components/ContextUsageButton.js +10 -3
  24. package/dist/gui/components/HookNotification.js +2 -1
  25. package/dist/gui/components/MessageContent.js +24 -160
  26. package/dist/gui/components/SessionHistory.js +1 -2
  27. package/dist/gui/components/SessionHistoryItem.js +1 -1
  28. package/dist/gui/components/Sidebar.js +27 -42
  29. package/dist/gui/components/SubAgentDetails.js +10 -14
  30. package/dist/gui/components/TodoSubline.js +1 -0
  31. package/dist/gui/components/ToolOperation.js +117 -81
  32. package/dist/gui/components/WorkProgress.js +5 -3
  33. package/dist/gui/components/index.d.ts +0 -1
  34. package/dist/gui/components/resizable.d.ts +1 -1
  35. package/dist/gui/constants.d.ts +6 -0
  36. package/dist/gui/constants.js +8 -0
  37. package/dist/gui/hooks/index.d.ts +1 -0
  38. package/dist/gui/hooks/index.js +1 -0
  39. package/dist/gui/hooks/use-lock-body-scroll.d.ts +7 -0
  40. package/dist/gui/hooks/use-lock-body-scroll.js +29 -0
  41. package/dist/gui/lib/motion.d.ts +12 -0
  42. package/dist/gui/lib/motion.js +69 -0
  43. package/dist/sdk/schemas/session.d.ts +37 -24
  44. package/dist/sdk/transports/http.d.ts +1 -1
  45. package/dist/sdk/transports/http.js +99 -1
  46. package/dist/sdk/transports/stdio.js +2 -2
  47. package/dist/sdk/transports/types.d.ts +11 -0
  48. package/dist/sdk/transports/types.js +28 -1
  49. package/package.json +3 -5
@@ -15,7 +15,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
15
15
  id: string;
16
16
  title: string;
17
17
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
18
- status: "completed" | "pending" | "in_progress" | "failed";
18
+ status: "pending" | "in_progress" | "completed" | "failed";
19
19
  batchId?: string | undefined;
20
20
  prettyName?: string | undefined;
21
21
  icon?: string | undefined;
@@ -73,6 +73,8 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
73
73
  compactionAction?: "compacted" | "truncated" | undefined;
74
74
  originalTokens?: number | undefined;
75
75
  finalTokens?: number | undefined;
76
+ originalContentPreview?: string | undefined;
77
+ originalContentPath?: string | undefined;
76
78
  } | undefined;
77
79
  subagentPort?: number | undefined;
78
80
  subagentSessionId?: string | undefined;
@@ -82,7 +84,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
82
84
  toolCalls?: {
83
85
  id: string;
84
86
  title: string;
85
- status: "completed" | "pending" | "in_progress" | "failed";
87
+ status: "pending" | "in_progress" | "completed" | "failed";
86
88
  prettyName?: string | undefined;
87
89
  icon?: string | undefined;
88
90
  content?: ({
@@ -122,7 +124,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
122
124
  toolCall: {
123
125
  id: string;
124
126
  title: string;
125
- status: "completed" | "pending" | "in_progress" | "failed";
127
+ status: "pending" | "in_progress" | "completed" | "failed";
126
128
  prettyName?: string | undefined;
127
129
  icon?: string | undefined;
128
130
  content?: ({
@@ -163,7 +165,7 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
163
165
  id: string;
164
166
  hookType: "context_size" | "tool_response";
165
167
  callback: string;
166
- status: "error" | "triggered" | "completed";
168
+ status: "completed" | "error" | "triggered";
167
169
  threshold?: number | undefined;
168
170
  currentPercentage?: number | undefined;
169
171
  metadata?: {
@@ -103,8 +103,11 @@ export function useChatMessages(client, startSession) {
103
103
  if (chunk.type === "content") {
104
104
  // Content chunk - text streaming
105
105
  // Update context size if provided (check both _meta.context_size and direct context_size)
106
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing dynamic properties from streaming chunks
106
107
  const chunkMeta = chunk._meta;
107
- const contextSizeData = chunkMeta?.context_size || chunk.context_size;
108
+ const contextSizeData =
109
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing dynamic properties from streaming chunks
110
+ chunkMeta?.context_size || chunk.context_size;
108
111
  if (contextSizeData != null) {
109
112
  const contextSize = contextSizeData;
110
113
  logger.info("✅ Received context_size from backend", {
@@ -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: "error" | "disconnected" | "connecting" | "connected";
6
+ connectionStatus: "error" | "connecting" | "connected" | "disconnected";
7
7
  sessionId: string | null;
8
8
  connect: () => Promise<void>;
9
9
  loadSession: (sessionIdToLoad: string) => Promise<void>;
@@ -21,8 +21,11 @@ export function useChatSession(client, initialSessionId) {
21
21
  return;
22
22
  const unsubscribe = client.onSessionUpdate((update) => {
23
23
  // Extract context size from update metadata if available
24
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing dynamic metadata properties from session updates
24
25
  const updateMeta = update._meta;
25
- const contextSizeData = updateMeta?.context_size || update.context_size;
26
+ const contextSizeData =
27
+ // biome-ignore lint/suspicious/noExplicitAny: Accessing dynamic metadata properties from session updates
28
+ updateMeta?.context_size || update.context_size;
26
29
  if (contextSizeData != null) {
27
30
  const contextSize = contextSizeData;
28
31
  logger.info("✅ Received context_size from session update", {
@@ -52,6 +55,7 @@ export function useChatSession(client, initialSessionId) {
52
55
  const imageBlocks = [];
53
56
  for (const c of update.message.content) {
54
57
  if (c.type === "image") {
58
+ // biome-ignore lint/suspicious/noExplicitAny: Image blocks can have various formats (source object or direct properties)
55
59
  const imgBlock = c;
56
60
  // Handle both formats: direct data/mimeType or source object
57
61
  if (imgBlock.source?.data) {
@@ -14,7 +14,7 @@ const logger = createLogger("subagent-stream");
14
14
  export function useSubagentStream(options) {
15
15
  const [messages, setMessages] = useState([]);
16
16
  // Start as streaming=true if options provided, since we're about to connect
17
- const [isStreaming, setIsStreaming] = useState(!!options);
17
+ const [_isStreaming, setIsStreaming] = useState(!!options);
18
18
  const [hasCompleted, setHasCompleted] = useState(false);
19
19
  const [error, setError] = useState(null);
20
20
  const abortControllerRef = useRef(null);
@@ -221,12 +221,12 @@ export function useSubagentStream(options) {
221
221
  logger.debug("Sub-agent stream completed");
222
222
  }
223
223
  }, [processSSEMessage]);
224
+ // Extract values from options (memoized to avoid dependency issues)
225
+ const port = options?.port;
226
+ const sessionId = options?.sessionId;
227
+ const host = options?.host ?? "localhost";
224
228
  // Connect when options change
225
229
  useEffect(() => {
226
- if (!options) {
227
- return;
228
- }
229
- const { port, sessionId, host = "localhost" } = options;
230
230
  if (!port || !sessionId) {
231
231
  return;
232
232
  }
@@ -247,7 +247,7 @@ export function useSubagentStream(options) {
247
247
  updateTimeoutRef.current = null;
248
248
  }
249
249
  };
250
- }, [options?.port, options?.sessionId, options?.host, connectToSubagent]);
250
+ }, [port, sessionId, host, connectToSubagent]);
251
251
  // Derive streaming status: streaming if we haven't completed yet
252
252
  const effectiveIsStreaming = !hasCompleted;
253
253
  return { messages, isStreaming: effectiveIsStreaming, error };
@@ -13,7 +13,7 @@ export declare function useToolCalls(client: AcpClient | null): {
13
13
  id: string;
14
14
  title: string;
15
15
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
16
- status: "completed" | "pending" | "in_progress" | "failed";
16
+ status: "pending" | "in_progress" | "completed" | "failed";
17
17
  batchId?: string | undefined;
18
18
  prettyName?: string | undefined;
19
19
  icon?: string | undefined;
@@ -71,6 +71,8 @@ export declare function useToolCalls(client: AcpClient | null): {
71
71
  compactionAction?: "compacted" | "truncated" | undefined;
72
72
  originalTokens?: number | undefined;
73
73
  finalTokens?: number | undefined;
74
+ originalContentPreview?: string | undefined;
75
+ originalContentPath?: string | undefined;
74
76
  } | undefined;
75
77
  subagentPort?: number | undefined;
76
78
  subagentSessionId?: string | undefined;
@@ -80,7 +82,7 @@ export declare function useToolCalls(client: AcpClient | null): {
80
82
  toolCalls?: {
81
83
  id: string;
82
84
  title: string;
83
- status: "completed" | "pending" | "in_progress" | "failed";
85
+ status: "pending" | "in_progress" | "completed" | "failed";
84
86
  prettyName?: string | undefined;
85
87
  icon?: string | undefined;
86
88
  content?: ({
@@ -120,7 +122,7 @@ export declare function useToolCalls(client: AcpClient | null): {
120
122
  toolCall: {
121
123
  id: string;
122
124
  title: string;
123
- status: "completed" | "pending" | "in_progress" | "failed";
125
+ status: "pending" | "in_progress" | "completed" | "failed";
124
126
  prettyName?: string | undefined;
125
127
  icon?: string | undefined;
126
128
  content?: ({
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "@townco/core";
2
2
  import { useEffect } from "react";
3
3
  import { useChatStore } from "../store/chat-store.js";
4
- const logger = createLogger("use-tool-calls", "debug");
4
+ const _logger = createLogger("use-tool-calls", "debug");
5
5
  /**
6
6
  * Hook to track and manage tool calls from ACP sessions
7
7
  *
@@ -22,9 +22,9 @@ export declare const HookNotificationDisplay: z.ZodObject<{
22
22
  }>;
23
23
  callback: z.ZodString;
24
24
  status: z.ZodEnum<{
25
+ completed: "completed";
25
26
  error: "error";
26
27
  triggered: "triggered";
27
- completed: "completed";
28
28
  }>;
29
29
  threshold: z.ZodOptional<z.ZodNumber>;
30
30
  currentPercentage: z.ZodOptional<z.ZodNumber>;
@@ -86,9 +86,9 @@ export declare const DisplayMessage: z.ZodObject<{
86
86
  other: "other";
87
87
  }>;
88
88
  status: z.ZodEnum<{
89
- completed: "completed";
90
89
  pending: "pending";
91
90
  in_progress: "in_progress";
91
+ completed: "completed";
92
92
  failed: "failed";
93
93
  }>;
94
94
  contentPosition: z.ZodOptional<z.ZodNumber>;
@@ -142,6 +142,8 @@ export declare const DisplayMessage: z.ZodObject<{
142
142
  }>>;
143
143
  originalTokens: z.ZodOptional<z.ZodNumber>;
144
144
  finalTokens: z.ZodOptional<z.ZodNumber>;
145
+ originalContentPreview: z.ZodOptional<z.ZodString>;
146
+ originalContentPath: z.ZodOptional<z.ZodString>;
145
147
  }, z.core.$strip>>;
146
148
  subagentPort: z.ZodOptional<z.ZodNumber>;
147
149
  subagentSessionId: z.ZodOptional<z.ZodString>;
@@ -154,9 +156,9 @@ export declare const DisplayMessage: z.ZodObject<{
154
156
  prettyName: z.ZodOptional<z.ZodString>;
155
157
  icon: z.ZodOptional<z.ZodString>;
156
158
  status: z.ZodEnum<{
157
- completed: "completed";
158
159
  pending: "pending";
159
160
  in_progress: "in_progress";
161
+ completed: "completed";
160
162
  failed: "failed";
161
163
  }>;
162
164
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -199,9 +201,9 @@ export declare const DisplayMessage: z.ZodObject<{
199
201
  prettyName: z.ZodOptional<z.ZodString>;
200
202
  icon: z.ZodOptional<z.ZodString>;
201
203
  status: z.ZodEnum<{
202
- completed: "completed";
203
204
  pending: "pending";
204
205
  in_progress: "in_progress";
206
+ completed: "completed";
205
207
  failed: "failed";
206
208
  }>;
207
209
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -246,9 +248,9 @@ export declare const DisplayMessage: z.ZodObject<{
246
248
  }>;
247
249
  callback: z.ZodString;
248
250
  status: z.ZodEnum<{
251
+ completed: "completed";
249
252
  error: "error";
250
253
  triggered: "triggered";
251
- completed: "completed";
252
254
  }>;
253
255
  threshold: z.ZodOptional<z.ZodNumber>;
254
256
  currentPercentage: z.ZodOptional<z.ZodNumber>;
@@ -338,9 +340,9 @@ export declare const ChatSessionState: z.ZodObject<{
338
340
  other: "other";
339
341
  }>;
340
342
  status: z.ZodEnum<{
341
- completed: "completed";
342
343
  pending: "pending";
343
344
  in_progress: "in_progress";
345
+ completed: "completed";
344
346
  failed: "failed";
345
347
  }>;
346
348
  contentPosition: z.ZodOptional<z.ZodNumber>;
@@ -394,6 +396,8 @@ export declare const ChatSessionState: z.ZodObject<{
394
396
  }>>;
395
397
  originalTokens: z.ZodOptional<z.ZodNumber>;
396
398
  finalTokens: z.ZodOptional<z.ZodNumber>;
399
+ originalContentPreview: z.ZodOptional<z.ZodString>;
400
+ originalContentPath: z.ZodOptional<z.ZodString>;
397
401
  }, z.core.$strip>>;
398
402
  subagentPort: z.ZodOptional<z.ZodNumber>;
399
403
  subagentSessionId: z.ZodOptional<z.ZodString>;
@@ -406,9 +410,9 @@ export declare const ChatSessionState: z.ZodObject<{
406
410
  prettyName: z.ZodOptional<z.ZodString>;
407
411
  icon: z.ZodOptional<z.ZodString>;
408
412
  status: z.ZodEnum<{
409
- completed: "completed";
410
413
  pending: "pending";
411
414
  in_progress: "in_progress";
415
+ completed: "completed";
412
416
  failed: "failed";
413
417
  }>;
414
418
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -451,9 +455,9 @@ export declare const ChatSessionState: z.ZodObject<{
451
455
  prettyName: z.ZodOptional<z.ZodString>;
452
456
  icon: z.ZodOptional<z.ZodString>;
453
457
  status: z.ZodEnum<{
454
- completed: "completed";
455
458
  pending: "pending";
456
459
  in_progress: "in_progress";
460
+ completed: "completed";
457
461
  failed: "failed";
458
462
  }>;
459
463
  content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -498,9 +502,9 @@ export declare const ChatSessionState: z.ZodObject<{
498
502
  }>;
499
503
  callback: z.ZodString;
500
504
  status: z.ZodEnum<{
505
+ completed: "completed";
501
506
  error: "error";
502
507
  triggered: "triggered";
503
- completed: "completed";
504
508
  }>;
505
509
  threshold: z.ZodOptional<z.ZodNumber>;
506
510
  currentPercentage: z.ZodOptional<z.ZodNumber>;
@@ -549,8 +553,8 @@ export type ChatSessionState = z.infer<typeof ChatSessionState>;
549
553
  */
550
554
  export declare const ConnectionStatus: z.ZodEnum<{
551
555
  error: "error";
552
- disconnected: "disconnected";
553
556
  connecting: "connecting";
554
557
  connected: "connected";
558
+ disconnected: "disconnected";
555
559
  }>;
556
560
  export type ConnectionStatus = z.infer<typeof ConnectionStatus>;
@@ -13,16 +13,16 @@ export type ToolCallStatus = z.infer<typeof ToolCallStatusSchema>;
13
13
  * Tool call categories for UI presentation
14
14
  */
15
15
  export declare const ToolCallKindSchema: z.ZodEnum<{
16
- search: "search";
17
- execute: "execute";
18
- move: "move";
19
- other: "other";
20
16
  read: "read";
21
17
  edit: "edit";
22
18
  delete: "delete";
19
+ move: "move";
20
+ search: "search";
21
+ execute: "execute";
23
22
  think: "think";
24
23
  fetch: "fetch";
25
24
  switch_mode: "switch_mode";
25
+ other: "other";
26
26
  }>;
27
27
  export type ToolCallKind = z.infer<typeof ToolCallKindSchema>;
28
28
  /**
@@ -280,16 +280,16 @@ export declare const ToolCallSchema: z.ZodObject<{
280
280
  }, z.core.$strip>>;
281
281
  subline: z.ZodOptional<z.ZodString>;
282
282
  kind: z.ZodEnum<{
283
- search: "search";
284
- execute: "execute";
285
- move: "move";
286
- other: "other";
287
283
  read: "read";
288
284
  edit: "edit";
289
285
  delete: "delete";
286
+ move: "move";
287
+ search: "search";
288
+ execute: "execute";
290
289
  think: "think";
291
290
  fetch: "fetch";
292
291
  switch_mode: "switch_mode";
292
+ other: "other";
293
293
  }>;
294
294
  status: z.ZodEnum<{
295
295
  pending: "pending";
@@ -348,6 +348,8 @@ export declare const ToolCallSchema: z.ZodObject<{
348
348
  }>>;
349
349
  originalTokens: z.ZodOptional<z.ZodNumber>;
350
350
  finalTokens: z.ZodOptional<z.ZodNumber>;
351
+ originalContentPreview: z.ZodOptional<z.ZodString>;
352
+ originalContentPath: z.ZodOptional<z.ZodString>;
351
353
  }, z.core.$strip>>;
352
354
  subagentPort: z.ZodOptional<z.ZodNumber>;
353
355
  subagentSessionId: z.ZodOptional<z.ZodString>;
@@ -594,6 +596,17 @@ export declare const ToolCallUpdateSchema: z.ZodObject<{
594
596
  }, z.core.$strip>], "type">>>;
595
597
  isStreaming: z.ZodOptional<z.ZodBoolean>;
596
598
  }, z.core.$strip>>>;
599
+ _meta: z.ZodOptional<z.ZodObject<{
600
+ truncationWarning: z.ZodOptional<z.ZodString>;
601
+ compactionAction: z.ZodOptional<z.ZodEnum<{
602
+ compacted: "compacted";
603
+ truncated: "truncated";
604
+ }>>;
605
+ originalTokens: z.ZodOptional<z.ZodNumber>;
606
+ finalTokens: z.ZodOptional<z.ZodNumber>;
607
+ originalContentPreview: z.ZodOptional<z.ZodString>;
608
+ originalContentPath: z.ZodOptional<z.ZodString>;
609
+ }, z.core.$strip>>;
597
610
  }, z.core.$strip>;
598
611
  export type ToolCallUpdate = z.infer<typeof ToolCallUpdateSchema>;
599
612
  /**
@@ -171,6 +171,8 @@ export const ToolCallSchema = z.object({
171
171
  compactionAction: z.enum(["compacted", "truncated"]).optional(),
172
172
  originalTokens: z.number().optional(),
173
173
  finalTokens: z.number().optional(),
174
+ originalContentPreview: z.string().optional(),
175
+ originalContentPath: z.string().optional(),
174
176
  })
175
177
  .optional(),
176
178
  /** Sub-agent HTTP port for direct SSE connection (Task tool only) */
@@ -205,6 +207,17 @@ export const ToolCallUpdateSchema = z.object({
205
207
  subagentSessionId: z.string().optional(),
206
208
  /** Sub-agent messages for replay */
207
209
  subagentMessages: z.array(SubagentMessageSchema).optional(),
210
+ /** Internal metadata (e.g., compaction info) */
211
+ _meta: z
212
+ .object({
213
+ truncationWarning: z.string().optional(),
214
+ compactionAction: z.enum(["compacted", "truncated"]).optional(),
215
+ originalTokens: z.number().optional(),
216
+ finalTokens: z.number().optional(),
217
+ originalContentPreview: z.string().optional(),
218
+ originalContentPath: z.string().optional(),
219
+ })
220
+ .optional(),
208
221
  });
209
222
  /**
210
223
  * Helper to merge a tool call update into an existing tool call
@@ -232,6 +245,8 @@ export function mergeToolCallUpdate(existing, update) {
232
245
  subagentSessionId: update.subagentSessionId ?? existing.subagentSessionId,
233
246
  // Sub-agent messages for replay
234
247
  subagentMessages: update.subagentMessages ?? existing.subagentMessages,
248
+ // Internal metadata (compaction info)
249
+ _meta: update._meta ?? existing._meta,
235
250
  };
236
251
  return merged;
237
252
  }
@@ -359,6 +359,7 @@ export const useChatStore = create((set) => ({
359
359
  if (existingIndex !== -1) {
360
360
  // Merge: preserve triggered data (threshold, currentPercentage, triggeredAt),
361
361
  // overlay completion data
362
+ // biome-ignore lint/style/noNonNullAssertion: existingIndex !== -1 ensures element exists
362
363
  const existing = existingNotifications[existingIndex];
363
364
  updatedNotifications = [...existingNotifications];
364
365
  // Use backend timestamp if available, fallback to Date.now()
@@ -33,14 +33,15 @@ function extractKeyParameter(toolCall) {
33
33
  function formatItemList(items, maxItems = 3) {
34
34
  if (items.length === 0)
35
35
  return "";
36
+ // biome-ignore lint/style/noNonNullAssertion: Length check ensures element exists
36
37
  if (items.length === 1)
37
38
  return items[0];
38
39
  if (items.length <= maxItems) {
39
- return items.slice(0, -1).join(", ") + " and " + items[items.length - 1];
40
+ return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
40
41
  }
41
42
  const shown = items.slice(0, maxItems);
42
43
  const remaining = items.length - maxItems;
43
- return shown.join(", ") + ` and ${remaining} more`;
44
+ return `${shown.join(", ")} and ${remaining} more`;
44
45
  }
45
46
  /**
46
47
  * Detect common patterns in file paths
@@ -49,13 +50,14 @@ function detectFilePattern(paths) {
49
50
  if (paths.length === 0)
50
51
  return null;
51
52
  // Extract directory from first path
53
+ // biome-ignore lint/style/noNonNullAssertion: Length check ensures element exists
52
54
  const firstPath = paths[0];
53
55
  const lastSlash = firstPath.lastIndexOf("/");
54
56
  if (lastSlash === -1)
55
57
  return null;
56
58
  const directory = firstPath.substring(0, lastSlash);
57
59
  // Check if all paths are in the same directory
58
- const allInSameDir = paths.every((p) => p.startsWith(directory + "/"));
60
+ const allInSameDir = paths.every((p) => p.startsWith(`${directory}/`));
59
61
  if (allInSameDir) {
60
62
  return directory;
61
63
  }
@@ -68,8 +70,10 @@ function generateSameToolSummary(toolCalls, tense) {
68
70
  if (toolCalls.length === 0)
69
71
  return "";
70
72
  if (toolCalls.length === 1) {
73
+ // biome-ignore lint/style/noNonNullAssertion: Length check ensures element exists
71
74
  return getToolCallVerbiage(toolCalls[0], tense);
72
75
  }
76
+ // biome-ignore lint/style/noNonNullAssertion: Length check ensures element exists
73
77
  const firstTool = toolCalls[0];
74
78
  const toolName = firstTool.title;
75
79
  // Extract parameters from all tool calls
@@ -144,6 +148,7 @@ export function generateSmartSummary(toolCalls, tense) {
144
148
  if (toolCalls.length === 0)
145
149
  return "";
146
150
  if (toolCalls.length === 1) {
151
+ // biome-ignore lint/style/noNonNullAssertion: Length check ensures element exists
147
152
  return getToolCallVerbiage(toolCalls[0], tense);
148
153
  }
149
154
  // Check if all tool calls are of the same type
@@ -128,7 +128,7 @@ function formatVerbiage(template, params) {
128
128
  function truncate(text, maxLength) {
129
129
  if (text.length <= maxLength)
130
130
  return text;
131
- return text.substring(0, maxLength - 1) + "…";
131
+ return `${text.substring(0, maxLength - 1)}…`;
132
132
  }
133
133
  /**
134
134
  * Get display verbiage for a tool call
@@ -19,4 +19,4 @@ export interface AppSidebarProps {
19
19
  /** Additional className for the sidebar */
20
20
  className?: string;
21
21
  }
22
- export declare function AppSidebar({ client, currentSessionId, onSessionSelect, onNewSession, onRenameSession, onArchiveSession, onDeleteSession, footer, className, }: AppSidebarProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function AppSidebar({ client, currentSessionId, onSessionSelect, onNewSession, onRenameSession, onArchiveSession, onDeleteSession, className, }: AppSidebarProps): import("react/jsx-runtime").JSX.Element;
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Plus } from "lucide-react";
2
+ import { Plus, Settings } from "lucide-react";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { IconButton } from "./IconButton.js";
5
5
  import { SessionHistory } from "./SessionHistory.js";
6
6
  import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, useSidebar, } from "./Sidebar.js";
7
- export function AppSidebar({ client, currentSessionId, onSessionSelect, onNewSession, onRenameSession, onArchiveSession, onDeleteSession, footer, className, }) {
7
+ import { ThemeToggle } from "./ThemeToggle.js";
8
+ export function AppSidebar({ client, currentSessionId, onSessionSelect, onNewSession, onRenameSession, onArchiveSession, onDeleteSession, className, }) {
8
9
  const { setOpenMobile } = useSidebar();
9
10
  const handleNewSession = () => {
10
11
  if (onNewSession) {
@@ -18,5 +19,5 @@ export function AppSidebar({ client, currentSessionId, onSessionSelect, onNewSes
18
19
  }
19
20
  setOpenMobile(false);
20
21
  };
21
- return (_jsxs(Sidebar, { className: cn("group-data-[side=left]:border-r-0", className), children: [_jsx(SidebarHeader, { className: "h-16 py-5 pl-6 pr-4", children: _jsxs("div", { className: "flex flex-row items-center justify-between w-full", children: [_jsx("span", { className: "font-semibold text-lg", children: "Sessions" }), _jsx(IconButton, { onClick: handleNewSession, "aria-label": "New Chat", children: _jsx(Plus, { className: "size-4 text-muted-foreground" }) })] }) }), _jsx(SidebarContent, { children: _jsx(SessionHistory, { client: client, currentSessionId: currentSessionId, onSessionSelect: onSessionSelect, onRenameSession: onRenameSession, onArchiveSession: onArchiveSession, onDeleteSession: onDeleteSession }) }), footer && _jsx(SidebarFooter, { children: footer })] }));
22
+ return (_jsxs(Sidebar, { className: cn("group-data-[side=left]:border-r-0", className), children: [_jsx(SidebarHeader, { className: "h-16 py-5 px-4 justify-center gap-0", children: _jsxs("div", { className: "flex flex-row items-center justify-between w-full pl-2", children: [_jsx("span", { className: "font-semibold text-xl tracking-tight", children: "Sessions" }), _jsx(IconButton, { onClick: handleNewSession, "aria-label": "New Chat", variant: "default", children: _jsx(Plus, { className: "size-4" }) })] }) }), _jsx(SidebarContent, { className: "gap-6", children: _jsx(SessionHistory, { client: client, currentSessionId: currentSessionId, onSessionSelect: onSessionSelect, onRenameSession: onRenameSession, onArchiveSession: onArchiveSession, onDeleteSession: onDeleteSession }) }), _jsx(SidebarFooter, { className: "p-0", children: _jsxs("div", { className: "border-t border-border pl-6 pr-4 py-5 flex justify-end gap-2", children: [_jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Settings", children: _jsx(Settings, { className: "size-4 text-muted-foreground" }) })] }) })] }));
22
23
  }
@@ -17,6 +17,6 @@ export const ChatEmptyState = React.forwardRef(({ title, titleElement, descripti
17
17
  for (let i = 0; i < suggestedPrompts.length; i += 2) {
18
18
  promptRows.push(suggestedPrompts.slice(i, i + 2));
19
19
  }
20
- return (_jsxs("div", { ref: ref, className: cn("flex flex-col items-start", className), ...props, children: [titleElement ? (_jsx("div", { className: "text-heading-4 text-text-primary mb-6", children: titleElement })) : (_jsx("h3", { className: "text-heading-4 text-text-primary mb-6", children: title })), _jsx("p", { className: "text-subheading text-text-secondary max-w-prose mb-6", children: description }), (onOpenFiles || onOpenSettings) && (_jsxs("div", { className: "flex items-center gap-1 -ml-3 mb-6", children: [onOpenFiles && (_jsxs("button", { type: "button", onClick: onOpenFiles, className: "inline-flex items-center gap-1 py-1.5 pr-3 pl-3 rounded-lg hover:bg-accent transition-colors", children: [_jsx("span", { className: "text-paragraph-sm-medium text-foreground tracking-wide leading-none", children: "View Files" }), _jsx(ChevronRight, { className: "size-4 text-foreground shrink-0" })] })), onOpenSettings && (_jsxs("button", { type: "button", onClick: onOpenSettings, className: "inline-flex items-center gap-1 py-1.5 pr-3 pl-3 rounded-lg hover:bg-accent transition-colors", children: [_jsxs("span", { className: "text-paragraph-sm-medium text-foreground tracking-wide leading-none", children: ["View Tools & MCPs", toolsAndMcpsCount !== undefined && toolsAndMcpsCount > 0 && (_jsxs("span", { className: "ml-1", children: ["(", toolsAndMcpsCount, ")"] }))] }), _jsx(ChevronRight, { className: "size-4 text-foreground shrink-0" })] }))] })), (guideUrl || onGuideClick) && (_jsxs("button", { type: "button", onClick: handleGuideClick, className: "inline-flex items-center gap-1 py-1.5 pr-3 -ml-3 pl-3 rounded-lg hover:bg-accent transition-colors", children: [_jsx("span", { className: "text-paragraph-sm-medium text-foreground tracking-wide leading-none", children: guideText }), _jsx(ChevronRight, { className: "size-4 text-foreground shrink-0" })] })), suggestedPrompts.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3 w-full max-w-prompt-container", children: [_jsx("p", { className: "text-label text-text-tertiary", children: "Suggested Prompts" }), _jsx("div", { className: "flex flex-col gap-2.5", children: promptRows.map((row) => (_jsx("div", { className: "flex gap-2.5 items-center", children: row.map((prompt) => (_jsx("button", { type: "button", onClick: () => handlePromptClick(prompt), onMouseEnter: () => onPromptHover?.(prompt), onMouseLeave: () => onPromptLeave?.(), className: "flex-1 flex items-start gap-2 p-3 bg-secondary hover:bg-secondary/80 rounded-2xl transition-colors min-w-0", children: _jsx("span", { className: "text-paragraph font-normal leading-normal text-text-tertiary truncate", children: prompt }) }, prompt))) }, row.join("-")))) })] }))] }));
20
+ return (_jsxs("div", { ref: ref, className: cn("flex flex-col items-start", className), ...props, children: [titleElement ? (_jsx("div", { className: "text-heading-4 text-text-primary mb-6", children: titleElement })) : (_jsx("h3", { className: "text-heading-4 text-text-primary mb-6", children: title })), _jsx("p", { className: "text-subheading text-text-secondary max-w-prose mb-6", children: description }), (onOpenFiles || onOpenSettings) && (_jsxs("div", { className: "flex items-center gap-1 -ml-3 mb-6", children: [onOpenFiles && (_jsxs("button", { type: "button", onClick: onOpenFiles, className: "inline-flex items-center gap-1 py-1.5 pr-3 pl-3 rounded-lg hover:bg-accent transition-colors", children: [_jsx("span", { className: "text-paragraph-sm-medium text-foreground tracking-wide leading-none", children: "View Files" }), _jsx(ChevronRight, { className: "size-4 text-foreground shrink-0" })] })), onOpenSettings && (_jsxs("button", { type: "button", onClick: onOpenSettings, className: "inline-flex items-center gap-1 py-1.5 pr-3 pl-3 rounded-lg hover:bg-accent transition-colors", children: [_jsxs("span", { className: "text-paragraph-sm-medium text-foreground tracking-wide leading-none", children: ["View Tools & MCPs", toolsAndMcpsCount !== undefined && toolsAndMcpsCount > 0 && (_jsxs("span", { className: "ml-1", children: ["(", toolsAndMcpsCount, ")"] }))] }), _jsx(ChevronRight, { className: "size-4 text-foreground shrink-0" })] }))] })), (guideUrl || onGuideClick) && (_jsxs("button", { type: "button", onClick: handleGuideClick, className: "inline-flex items-center gap-1 py-1.5 pr-3 -ml-3 pl-3 rounded-lg hover:bg-accent transition-colors", children: [_jsx("span", { className: "text-paragraph-sm-medium text-foreground tracking-wide leading-none", children: guideText }), _jsx(ChevronRight, { className: "size-4 text-foreground shrink-0" })] })), suggestedPrompts.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3 w-full max-w-prompt-container", children: [_jsx("p", { className: "text-text-tertiary", children: "Suggested Prompts" }), _jsx("div", { className: "flex flex-col gap-2.5", children: promptRows.map((row) => (_jsx("div", { className: "flex gap-2.5 items-center", children: row.map((prompt) => (_jsx("button", { type: "button", onClick: () => handlePromptClick(prompt), onMouseEnter: () => onPromptHover?.(prompt), onMouseLeave: () => onPromptLeave?.(), className: "flex-1 flex items-start gap-2 p-3 bg-secondary hover:bg-secondary/80 rounded-2xl transition-colors min-w-0", children: _jsx("span", { className: "text-paragraph font-normal leading-normal text-text-tertiary truncate", children: prompt }) }, prompt))) }, row.join("-")))) })] }))] }));
21
21
  });
22
22
  ChatEmptyState.displayName = "ChatEmptyState";
@@ -1,16 +1,5 @@
1
1
  import * as React from "react";
2
- interface ChatHeaderContextValue {
3
- isExpanded: boolean;
4
- setIsExpanded: (expanded: boolean) => void;
5
- }
6
- declare const useChatHeaderContext: () => ChatHeaderContextValue;
7
2
  export interface ChatHeaderRootProps extends React.HTMLAttributes<HTMLDivElement> {
8
- /** Initial expanded state */
9
- defaultExpanded?: boolean;
10
- /** Controlled expanded state */
11
- expanded?: boolean;
12
- /** Callback when expanded state changes */
13
- onExpandedChange?: (expanded: boolean) => void;
14
3
  }
15
4
  declare const ChatHeaderRoot: React.ForwardRefExoticComponent<ChatHeaderRootProps & React.RefAttributes<HTMLDivElement>>;
16
5
  export interface ChatHeaderTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
@@ -19,20 +8,4 @@ declare const ChatHeaderTitle: React.ForwardRefExoticComponent<ChatHeaderTitlePr
19
8
  export interface ChatHeaderActionsProps extends React.HTMLAttributes<HTMLDivElement> {
20
9
  }
21
10
  declare const ChatHeaderActions: React.ForwardRefExoticComponent<ChatHeaderActionsProps & React.RefAttributes<HTMLDivElement>>;
22
- export type ConnectionStatus = "connected" | "connecting" | "error" | "disconnected";
23
- export interface ChatHeaderStatusIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
24
- /** Connection status */
25
- status: ConnectionStatus;
26
- /** Optional status text override */
27
- statusText?: string;
28
- }
29
- declare const ChatHeaderStatusIndicator: React.ForwardRefExoticComponent<ChatHeaderStatusIndicatorProps & React.RefAttributes<HTMLDivElement>>;
30
- export interface ChatHeaderToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
31
- /** Icon to display (should rotate based on expanded state) */
32
- icon?: React.ReactNode;
33
- }
34
- declare const ChatHeaderToggle: React.ForwardRefExoticComponent<ChatHeaderToggleProps & React.RefAttributes<HTMLButtonElement>>;
35
- export interface ChatHeaderExpandablePanelProps extends React.HTMLAttributes<HTMLDivElement> {
36
- }
37
- declare const ChatHeaderExpandablePanel: React.ForwardRefExoticComponent<ChatHeaderExpandablePanelProps & React.RefAttributes<HTMLDivElement>>;
38
- export { ChatHeaderRoot as Root, ChatHeaderTitle as Title, ChatHeaderActions as Actions, ChatHeaderStatusIndicator as StatusIndicator, ChatHeaderToggle as Toggle, ChatHeaderExpandablePanel as ExpandablePanel, useChatHeaderContext, };
11
+ export { ChatHeaderRoot as Root, ChatHeaderTitle as Title, ChatHeaderActions as Actions, };