wave-code 0.12.1 → 0.12.3

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 (44) hide show
  1. package/dist/components/App.d.ts +1 -0
  2. package/dist/components/App.d.ts.map +1 -1
  3. package/dist/components/App.js +9 -14
  4. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  5. package/dist/components/BackgroundTaskManager.js +2 -1
  6. package/dist/components/BangDisplay.d.ts.map +1 -1
  7. package/dist/components/BangDisplay.js +2 -14
  8. package/dist/components/ChatInterface.d.ts +3 -1
  9. package/dist/components/ChatInterface.d.ts.map +1 -1
  10. package/dist/components/ChatInterface.js +4 -65
  11. package/dist/components/ConfirmationDetails.d.ts +0 -2
  12. package/dist/components/ConfirmationDetails.d.ts.map +1 -1
  13. package/dist/components/ConfirmationDetails.js +3 -15
  14. package/dist/components/ConfirmationSelector.d.ts +0 -1
  15. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  16. package/dist/components/ConfirmationSelector.js +4 -12
  17. package/dist/components/MessageList.d.ts +1 -2
  18. package/dist/components/MessageList.d.ts.map +1 -1
  19. package/dist/components/MessageList.js +5 -16
  20. package/dist/components/RewindCommand.d.ts.map +1 -1
  21. package/dist/components/RewindCommand.js +2 -4
  22. package/dist/components/ToolDisplay.d.ts.map +1 -1
  23. package/dist/components/ToolDisplay.js +2 -5
  24. package/dist/contexts/useChat.d.ts +0 -2
  25. package/dist/contexts/useChat.d.ts.map +1 -1
  26. package/dist/contexts/useChat.js +26 -15
  27. package/dist/utils/logger.d.ts.map +1 -1
  28. package/dist/utils/logger.js +3 -6
  29. package/dist/utils/throttle.d.ts +15 -0
  30. package/dist/utils/throttle.d.ts.map +1 -0
  31. package/dist/utils/throttle.js +55 -0
  32. package/package.json +2 -2
  33. package/src/components/App.tsx +11 -17
  34. package/src/components/BackgroundTaskManager.tsx +3 -6
  35. package/src/components/BangDisplay.tsx +4 -22
  36. package/src/components/ChatInterface.tsx +8 -84
  37. package/src/components/ConfirmationDetails.tsx +2 -25
  38. package/src/components/ConfirmationSelector.tsx +3 -15
  39. package/src/components/MessageList.tsx +5 -25
  40. package/src/components/RewindCommand.tsx +3 -5
  41. package/src/components/ToolDisplay.tsx +2 -5
  42. package/src/contexts/useChat.tsx +34 -16
  43. package/src/utils/logger.ts +3 -7
  44. package/src/utils/throttle.ts +75 -0
@@ -5,6 +5,7 @@ import React, {
5
5
  useRef,
6
6
  useEffect,
7
7
  useState,
8
+ useMemo,
8
9
  } from "react";
9
10
  import { useInput } from "ink";
10
11
  import { useAppConfig } from "./useAppConfig.js";
@@ -22,8 +23,10 @@ import {
22
23
  AgentCallbacks,
23
24
  type ToolPermissionContext,
24
25
  OPERATION_CANCELLED_BY_USER,
26
+ cloneMessage,
25
27
  } from "wave-agent-sdk";
26
28
  import { logger } from "../utils/logger.js";
29
+ import { throttle } from "../utils/throttle.js";
27
30
  import { displayUsageSummary } from "../utils/usageSummary.js";
28
31
  import { expandLongTextPlaceholders } from "../managers/inputHandlers.js";
29
32
 
@@ -109,8 +112,6 @@ export interface ChatContextType {
109
112
  messages: Message[];
110
113
  sessionIds: string[];
111
114
  }>;
112
- wasLastDetailsTooTall: number;
113
- setWasLastDetailsTooTall: React.Dispatch<React.SetStateAction<number>>;
114
115
  // Status metadata
115
116
  getGatewayConfig: () => import("wave-agent-sdk").GatewayConfig;
116
117
  getModelConfig: () => import("wave-agent-sdk").ModelConfig;
@@ -156,10 +157,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
156
157
  const [isExpanded, setIsExpanded] = useState(false);
157
158
  const isExpandedRef = useRef(isExpanded);
158
159
 
159
- useEffect(() => {
160
- isExpandedRef.current = isExpanded;
161
- }, [isExpanded]);
162
-
163
160
  const [isTaskListVisible, setIsTaskListVisible] = useState(true);
164
161
 
165
162
  const [queuedMessages, setQueuedMessages] = useState<
@@ -170,9 +167,31 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
170
167
  }>
171
168
  >([]);
172
169
 
173
- // AI State
174
170
  const [messages, setMessages] = useState<Message[]>([]);
175
171
 
172
+ const throttledSetMessages = useMemo(
173
+ () =>
174
+ throttle(() => {
175
+ if (!isExpandedRef.current && agentRef.current) {
176
+ setMessages([...agentRef.current.messages]);
177
+ }
178
+ }, 300),
179
+ [],
180
+ );
181
+
182
+ useEffect(() => {
183
+ isExpandedRef.current = isExpanded;
184
+ if (isExpanded) {
185
+ throttledSetMessages.cancel();
186
+ }
187
+ }, [isExpanded, throttledSetMessages]);
188
+
189
+ useEffect(() => {
190
+ return () => {
191
+ throttledSetMessages.cancel();
192
+ };
193
+ }, [throttledSetMessages]);
194
+
176
195
  const [isLoading, setIsLoading] = useState(false);
177
196
  const [latestTotalTokens, setlatestTotalTokens] = useState(0);
178
197
  const [sessionId, setSessionId] = useState("");
@@ -259,8 +278,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
259
278
  [],
260
279
  );
261
280
 
262
- // Confirmation too tall state
263
- const [wasLastDetailsTooTall, setWasLastDetailsTooTall] = useState(0);
264
281
  const allowBypassInCycle =
265
282
  !!bypassPermissions || initialPermissionMode === "bypassPermissions";
266
283
 
@@ -298,9 +315,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
298
315
  const initializeAgent = async () => {
299
316
  const callbacks: AgentCallbacks = {
300
317
  onMessagesChange: () => {
301
- if (!isExpandedRef.current && agentRef.current) {
302
- setMessages([...agentRef.current.messages]);
303
- }
318
+ throttledSetMessages();
304
319
  },
305
320
  onServersChange: (servers) => {
306
321
  setMcpServers([...servers]);
@@ -415,6 +430,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
415
430
  worktreeSession,
416
431
  model,
417
432
  initialPermissionMode,
433
+ throttledSetMessages,
418
434
  ]);
419
435
 
420
436
  // Cleanup on unmount
@@ -471,7 +487,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
471
487
  if (command) {
472
488
  setIsCommandRunning(true);
473
489
  try {
474
- await agentRef.current?.executeBashCommand(command);
490
+ await agentRef.current?.bang(command);
475
491
  } finally {
476
492
  setIsCommandRunning(false);
477
493
  }
@@ -661,13 +677,16 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
661
677
  // Clear terminal screen when expanded state changes
662
678
  setIsExpanded((prev) => {
663
679
  const newExpanded = !prev;
680
+ isExpandedRef.current = newExpanded;
664
681
  if (newExpanded) {
665
682
  // Transitioning to EXPANDED: Freeze the current view
683
+ // Cancel any pending throttled updates to avoid overwriting the frozen state
684
+ throttledSetMessages.cancel();
666
685
  // Deep copy the last message to ensure it doesn't update if the agent is still writing to it
667
686
  setMessages((currentMessages) => {
668
687
  if (currentMessages.length === 0) return currentMessages;
669
688
  const lastMessage = currentMessages[currentMessages.length - 1];
670
- const frozenLastMessage = JSON.parse(JSON.stringify(lastMessage));
689
+ const frozenLastMessage = cloneMessage(lastMessage);
671
690
  return [...currentMessages.slice(0, -1), frozenLastMessage];
672
691
  });
673
692
  } else {
@@ -739,8 +758,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
739
758
  rewindId,
740
759
  handleRewindSelect,
741
760
  getFullMessageThread,
742
- wasLastDetailsTooTall,
743
- setWasLastDetailsTooTall,
761
+
744
762
  getGatewayConfig,
745
763
  getModelConfig,
746
764
  workingDirectory,
@@ -10,6 +10,7 @@
10
10
 
11
11
  import * as fs from "fs";
12
12
  import { Chalk } from "chalk";
13
+ import { getLastLines } from "wave-agent-sdk";
13
14
  import { LOG_FILE, DATA_DIRECTORY } from "./constants.js";
14
15
 
15
16
  const chalk = new Chalk({ level: 3 });
@@ -269,19 +270,14 @@ const truncateLogFileIfNeeded = (config: LogCleanupConfig): void => {
269
270
  // If file size exceeds limit, truncate file
270
271
  if (stats.size > config.maxFileSize) {
271
272
  const content = fs.readFileSync(logFile, "utf8");
272
- const lines = content.split("\n");
273
-
274
- // Keep the last specified number of lines
275
- const keepLines = Math.min(config.keepLines, lines.length);
276
- const truncatedContent = lines.slice(-keepLines).join("\n");
273
+ const truncatedContent = getLastLines(content, config.keepLines);
277
274
 
278
275
  // Write truncated content
279
276
  fs.writeFileSync(logFile, truncatedContent);
280
277
 
281
278
  // Record truncation operation
282
- const removedLines = lines.length - keepLines;
283
279
  logger.debug(
284
- `Log file truncated: removed ${removedLines} lines, kept last ${keepLines} lines`,
280
+ `Log file truncated: file size ${stats.size} exceeded limit ${config.maxFileSize}, kept last ${config.keepLines} lines`,
285
281
  );
286
282
  }
287
283
  } catch (error) {
@@ -0,0 +1,75 @@
1
+ export interface ThrottleOptions {
2
+ leading?: boolean;
3
+ trailing?: boolean;
4
+ }
5
+
6
+ export interface ThrottledFunction<T extends (...args: unknown[]) => void> {
7
+ (...args: Parameters<T>): void;
8
+ cancel: () => void;
9
+ flush: () => void;
10
+ }
11
+
12
+ /**
13
+ * Creates a throttled function that only invokes `func` at most once per
14
+ * every `wait` milliseconds.
15
+ */
16
+ export function throttle<T extends (...args: unknown[]) => void>(
17
+ func: T,
18
+ wait: number,
19
+ options: ThrottleOptions = { leading: true, trailing: true },
20
+ ): ThrottledFunction<T> {
21
+ let timeoutId: NodeJS.Timeout | null = null;
22
+ let lastArgs: Parameters<T> | null = null;
23
+ let lastCallTime = 0;
24
+
25
+ const invokeFunc = () => {
26
+ if (lastArgs) {
27
+ func(...lastArgs);
28
+ lastCallTime = Date.now();
29
+ lastArgs = null;
30
+ }
31
+ };
32
+
33
+ const throttled = (...args: Parameters<T>) => {
34
+ const now = Date.now();
35
+ const remaining = wait - (now - lastCallTime);
36
+
37
+ lastArgs = args;
38
+
39
+ if (remaining <= 0 || remaining > wait) {
40
+ if (timeoutId) {
41
+ clearTimeout(timeoutId);
42
+ timeoutId = null;
43
+ }
44
+ if (options.leading !== false || lastCallTime !== 0) {
45
+ invokeFunc();
46
+ } else {
47
+ lastCallTime = now;
48
+ }
49
+ } else if (!timeoutId && options.trailing !== false) {
50
+ timeoutId = setTimeout(() => {
51
+ timeoutId = null;
52
+ invokeFunc();
53
+ }, remaining);
54
+ }
55
+ };
56
+
57
+ throttled.cancel = () => {
58
+ if (timeoutId) {
59
+ clearTimeout(timeoutId);
60
+ timeoutId = null;
61
+ }
62
+ lastArgs = null;
63
+ lastCallTime = 0;
64
+ };
65
+
66
+ throttled.flush = () => {
67
+ if (timeoutId) {
68
+ clearTimeout(timeoutId);
69
+ timeoutId = null;
70
+ }
71
+ invokeFunc();
72
+ };
73
+
74
+ return throttled as ThrottledFunction<T>;
75
+ }