wave-code 0.8.1 → 0.8.2

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.
@@ -0,0 +1,242 @@
1
+ export const initialState = {
2
+ inputText: "",
3
+ cursorPosition: 0,
4
+ showFileSelector: false,
5
+ atPosition: -1,
6
+ fileSearchQuery: "",
7
+ filteredFiles: [],
8
+ showCommandSelector: false,
9
+ slashPosition: -1,
10
+ commandSearchQuery: "",
11
+ showHistorySearch: false,
12
+ historySearchQuery: "",
13
+ longTextCounter: 0,
14
+ longTextMap: {},
15
+ attachedImages: [],
16
+ imageIdCounter: 1,
17
+ showBackgroundTaskManager: false,
18
+ showMcpManager: false,
19
+ showRewindManager: false,
20
+ showHelp: false,
21
+ showStatusCommand: false,
22
+ permissionMode: "default",
23
+ selectorJustUsed: false,
24
+ isPasting: false,
25
+ pasteBuffer: "",
26
+ initialPasteCursorPosition: 0,
27
+ };
28
+ export function inputReducer(state, action) {
29
+ switch (action.type) {
30
+ case "SET_INPUT_TEXT":
31
+ return { ...state, inputText: action.payload };
32
+ case "SET_CURSOR_POSITION":
33
+ return {
34
+ ...state,
35
+ cursorPosition: Math.max(0, Math.min(state.inputText.length, action.payload)),
36
+ };
37
+ case "INSERT_TEXT": {
38
+ const beforeCursor = state.inputText.substring(0, state.cursorPosition);
39
+ const afterCursor = state.inputText.substring(state.cursorPosition);
40
+ const newText = beforeCursor + action.payload + afterCursor;
41
+ const newCursorPosition = state.cursorPosition + action.payload.length;
42
+ return {
43
+ ...state,
44
+ inputText: newText,
45
+ cursorPosition: newCursorPosition,
46
+ };
47
+ }
48
+ case "DELETE_CHAR": {
49
+ if (state.cursorPosition > 0) {
50
+ const beforeCursor = state.inputText.substring(0, state.cursorPosition - 1);
51
+ const afterCursor = state.inputText.substring(state.cursorPosition);
52
+ const newText = beforeCursor + afterCursor;
53
+ const newCursorPosition = state.cursorPosition - 1;
54
+ return {
55
+ ...state,
56
+ inputText: newText,
57
+ cursorPosition: newCursorPosition,
58
+ };
59
+ }
60
+ return state;
61
+ }
62
+ case "MOVE_CURSOR": {
63
+ const newCursorPosition = Math.max(0, Math.min(state.inputText.length, state.cursorPosition + action.payload));
64
+ return { ...state, cursorPosition: newCursorPosition };
65
+ }
66
+ case "ACTIVATE_FILE_SELECTOR":
67
+ return {
68
+ ...state,
69
+ showFileSelector: true,
70
+ atPosition: action.payload,
71
+ fileSearchQuery: "",
72
+ filteredFiles: [],
73
+ };
74
+ case "SET_FILE_SEARCH_QUERY":
75
+ return { ...state, fileSearchQuery: action.payload };
76
+ case "SET_FILTERED_FILES":
77
+ return { ...state, filteredFiles: action.payload };
78
+ case "CANCEL_FILE_SELECTOR":
79
+ return {
80
+ ...state,
81
+ showFileSelector: false,
82
+ atPosition: -1,
83
+ fileSearchQuery: "",
84
+ filteredFiles: [],
85
+ selectorJustUsed: true,
86
+ };
87
+ case "ACTIVATE_COMMAND_SELECTOR":
88
+ return {
89
+ ...state,
90
+ showCommandSelector: true,
91
+ slashPosition: action.payload,
92
+ commandSearchQuery: "",
93
+ };
94
+ case "SET_COMMAND_SEARCH_QUERY":
95
+ return { ...state, commandSearchQuery: action.payload };
96
+ case "CANCEL_COMMAND_SELECTOR":
97
+ return {
98
+ ...state,
99
+ showCommandSelector: false,
100
+ slashPosition: -1,
101
+ commandSearchQuery: "",
102
+ selectorJustUsed: true,
103
+ };
104
+ case "ACTIVATE_HISTORY_SEARCH":
105
+ return {
106
+ ...state,
107
+ showHistorySearch: true,
108
+ historySearchQuery: "",
109
+ };
110
+ case "SET_HISTORY_SEARCH_QUERY":
111
+ return { ...state, historySearchQuery: action.payload };
112
+ case "CANCEL_HISTORY_SEARCH":
113
+ return {
114
+ ...state,
115
+ showHistorySearch: false,
116
+ historySearchQuery: "",
117
+ selectorJustUsed: true,
118
+ };
119
+ case "ADD_IMAGE": {
120
+ const newImage = {
121
+ id: state.imageIdCounter,
122
+ path: action.payload.path,
123
+ mimeType: action.payload.mimeType,
124
+ };
125
+ return {
126
+ ...state,
127
+ attachedImages: [...state.attachedImages, newImage],
128
+ imageIdCounter: state.imageIdCounter + 1,
129
+ };
130
+ }
131
+ case "REMOVE_IMAGE":
132
+ return {
133
+ ...state,
134
+ attachedImages: state.attachedImages.filter((img) => img.id !== action.payload),
135
+ };
136
+ case "CLEAR_IMAGES":
137
+ return { ...state, attachedImages: [] };
138
+ case "SET_SHOW_BACKGROUND_TASK_MANAGER":
139
+ return {
140
+ ...state,
141
+ showBackgroundTaskManager: action.payload,
142
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
143
+ };
144
+ case "SET_SHOW_MCP_MANAGER":
145
+ return {
146
+ ...state,
147
+ showMcpManager: action.payload,
148
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
149
+ };
150
+ case "SET_SHOW_REWIND_MANAGER":
151
+ return {
152
+ ...state,
153
+ showRewindManager: action.payload,
154
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
155
+ };
156
+ case "SET_SHOW_HELP":
157
+ return {
158
+ ...state,
159
+ showHelp: action.payload,
160
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
161
+ };
162
+ case "SET_SHOW_STATUS_COMMAND":
163
+ return {
164
+ ...state,
165
+ showStatusCommand: action.payload,
166
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
167
+ };
168
+ case "SET_PERMISSION_MODE":
169
+ return { ...state, permissionMode: action.payload };
170
+ case "SET_SELECTOR_JUST_USED":
171
+ return { ...state, selectorJustUsed: action.payload };
172
+ case "COMPRESS_AND_INSERT_TEXT": {
173
+ let textToInsert = action.payload;
174
+ let newLongTextCounter = state.longTextCounter;
175
+ const newLongTextMap = { ...state.longTextMap };
176
+ if (textToInsert.length > 200) {
177
+ newLongTextCounter += 1;
178
+ const compressedLabel = `[LongText#${newLongTextCounter}]`;
179
+ newLongTextMap[compressedLabel] = textToInsert;
180
+ textToInsert = compressedLabel;
181
+ }
182
+ const beforeCursor = state.inputText.substring(0, state.cursorPosition);
183
+ const afterCursor = state.inputText.substring(state.cursorPosition);
184
+ const newText = beforeCursor + textToInsert + afterCursor;
185
+ const newCursorPosition = state.cursorPosition + textToInsert.length;
186
+ return {
187
+ ...state,
188
+ inputText: newText,
189
+ cursorPosition: newCursorPosition,
190
+ longTextCounter: newLongTextCounter,
191
+ longTextMap: newLongTextMap,
192
+ };
193
+ }
194
+ case "CLEAR_LONG_TEXT_MAP":
195
+ return { ...state, longTextMap: {} };
196
+ case "CLEAR_INPUT":
197
+ return {
198
+ ...state,
199
+ inputText: "",
200
+ cursorPosition: 0,
201
+ };
202
+ case "START_PASTE":
203
+ return {
204
+ ...state,
205
+ isPasting: true,
206
+ pasteBuffer: action.payload.buffer,
207
+ initialPasteCursorPosition: action.payload.cursorPosition,
208
+ };
209
+ case "APPEND_PASTE_BUFFER":
210
+ return {
211
+ ...state,
212
+ pasteBuffer: state.pasteBuffer + action.payload,
213
+ };
214
+ case "END_PASTE":
215
+ return {
216
+ ...state,
217
+ isPasting: false,
218
+ pasteBuffer: "",
219
+ };
220
+ case "ADD_IMAGE_AND_INSERT_PLACEHOLDER": {
221
+ const newImage = {
222
+ id: state.imageIdCounter,
223
+ path: action.payload.path,
224
+ mimeType: action.payload.mimeType,
225
+ };
226
+ const placeholder = `[Image #${newImage.id}]`;
227
+ const beforeCursor = state.inputText.substring(0, state.cursorPosition);
228
+ const afterCursor = state.inputText.substring(state.cursorPosition);
229
+ const newText = beforeCursor + placeholder + afterCursor;
230
+ const newCursorPosition = state.cursorPosition + placeholder.length;
231
+ return {
232
+ ...state,
233
+ attachedImages: [...state.attachedImages, newImage],
234
+ imageIdCounter: state.imageIdCounter + 1,
235
+ inputText: newText,
236
+ cursorPosition: newCursorPosition,
237
+ };
238
+ }
239
+ default:
240
+ return state;
241
+ }
242
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,7 +39,7 @@
39
39
  "react": "^19.2.4",
40
40
  "react-dom": "19.2.4",
41
41
  "yargs": "^17.7.2",
42
- "wave-agent-sdk": "0.8.1"
42
+ "wave-agent-sdk": "0.8.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/react": "^19.1.8",
@@ -95,7 +95,7 @@ export const ChatInterface: React.FC = () => {
95
95
  <MessageList
96
96
  messages={messages}
97
97
  isExpanded={isExpanded}
98
- hideDynamicBlocks={isConfirmationVisible}
98
+ forceStatic={isConfirmationVisible && isConfirmationTooTall}
99
99
  version={version}
100
100
  workdir={workdir}
101
101
  model={model}
@@ -7,7 +7,7 @@ import { MessageBlockItem } from "./MessageBlockItem.js";
7
7
  export interface MessageListProps {
8
8
  messages: Message[];
9
9
  isExpanded?: boolean;
10
- hideDynamicBlocks?: boolean;
10
+ forceStatic?: boolean;
11
11
  version?: string;
12
12
  workdir?: string;
13
13
  model?: string;
@@ -17,7 +17,7 @@ export const MessageList = React.memo(
17
17
  ({
18
18
  messages,
19
19
  isExpanded = false,
20
- hideDynamicBlocks = false,
20
+ forceStatic = false,
21
21
  version,
22
22
  workdir,
23
23
  model,
@@ -54,7 +54,7 @@ export const MessageList = React.memo(
54
54
  message,
55
55
  isLastMessage: messageIndex === messages.length - 1,
56
56
  // Unique key for each block to help Static component
57
- key: `${message.id || messageIndex}-${blockIndex}`,
57
+ key: `${message.id}-${blockIndex}`,
58
58
  }));
59
59
  });
60
60
 
@@ -62,6 +62,7 @@ export const MessageList = React.memo(
62
62
  const blocksWithStatus = allBlocks.map((item) => {
63
63
  const { block, isLastMessage } = item;
64
64
  const isDynamic =
65
+ !forceStatic &&
65
66
  isLastMessage &&
66
67
  ((block.type === "tool" && block.stage !== "end") ||
67
68
  (block.type === "bang" && block.isRunning));
@@ -69,9 +70,7 @@ export const MessageList = React.memo(
69
70
  });
70
71
 
71
72
  const staticBlocks = blocksWithStatus.filter((b) => !b.isDynamic);
72
- const dynamicBlocks = hideDynamicBlocks
73
- ? []
74
- : blocksWithStatus.filter((b) => b.isDynamic);
73
+ const dynamicBlocks = blocksWithStatus.filter((b) => b.isDynamic);
75
74
 
76
75
  const staticItems = [
77
76
  { isWelcome: true, key: "welcome", block: undefined, message: undefined },
@@ -141,7 +141,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
141
141
 
142
142
  // AI State
143
143
  const [messages, setMessages] = useState<Message[]>([]);
144
- const messagesUpdateTimerRef = useRef<NodeJS.Timeout | null>(null);
145
144
 
146
145
  const [isLoading, setIsLoading] = useState(false);
147
146
  const [latestTotalTokens, setlatestTotalTokens] = useState(0);
@@ -243,15 +242,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
243
242
  const initializeAgent = async () => {
244
243
  const callbacks: AgentCallbacks = {
245
244
  onMessagesChange: () => {
246
- if (!isExpandedRef.current) {
247
- if (!messagesUpdateTimerRef.current) {
248
- messagesUpdateTimerRef.current = setTimeout(() => {
249
- if (agentRef.current) {
250
- setMessages([...agentRef.current.messages]);
251
- }
252
- messagesUpdateTimerRef.current = null;
253
- }, 100);
254
- }
245
+ if (!isExpandedRef.current && agentRef.current) {
246
+ setMessages([...agentRef.current.messages]);
255
247
  }
256
248
  },
257
249
  onServersChange: (servers) => {
@@ -374,9 +366,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
374
366
  // Cleanup on unmount
375
367
  useEffect(() => {
376
368
  return () => {
377
- if (messagesUpdateTimerRef.current) {
378
- clearTimeout(messagesUpdateTimerRef.current);
379
- }
380
369
  if (agentRef.current) {
381
370
  try {
382
371
  // Display usage summary before cleanup
@@ -580,10 +569,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
580
569
  setIsExpanded((prev) => {
581
570
  const newExpanded = !prev;
582
571
  if (newExpanded) {
583
- if (messagesUpdateTimerRef.current) {
584
- clearTimeout(messagesUpdateTimerRef.current);
585
- messagesUpdateTimerRef.current = null;
586
- }
587
572
  // Transitioning to EXPANDED: Freeze the current view
588
573
  // Deep copy the last message to ensure it doesn't update if the agent is still writing to it
589
574
  setMessages((currentMessages) => {