wave-code 0.7.0 → 0.7.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.
Files changed (57) hide show
  1. package/dist/components/ChatInterface.d.ts.map +1 -1
  2. package/dist/components/ChatInterface.js +28 -15
  3. package/dist/components/ConfirmationDetails.d.ts +1 -0
  4. package/dist/components/ConfirmationDetails.d.ts.map +1 -1
  5. package/dist/components/ConfirmationDetails.js +6 -9
  6. package/dist/components/ConfirmationSelector.d.ts +1 -0
  7. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  8. package/dist/components/ConfirmationSelector.js +164 -117
  9. package/dist/components/DiffDisplay.d.ts +1 -0
  10. package/dist/components/DiffDisplay.d.ts.map +1 -1
  11. package/dist/components/DiffDisplay.js +92 -29
  12. package/dist/components/HistorySearch.d.ts.map +1 -1
  13. package/dist/components/HistorySearch.js +1 -1
  14. package/dist/components/Markdown.d.ts.map +1 -1
  15. package/dist/components/Markdown.js +3 -1
  16. package/dist/components/McpManager.d.ts.map +1 -1
  17. package/dist/components/McpManager.js +49 -52
  18. package/dist/components/MessageBlockItem.d.ts +9 -0
  19. package/dist/components/MessageBlockItem.d.ts.map +1 -0
  20. package/dist/components/MessageBlockItem.js +11 -0
  21. package/dist/components/MessageList.d.ts +2 -4
  22. package/dist/components/MessageList.d.ts.map +1 -1
  23. package/dist/components/MessageList.js +28 -23
  24. package/dist/components/PluginDetail.d.ts.map +1 -1
  25. package/dist/components/PluginDetail.js +19 -22
  26. package/dist/components/TaskList.d.ts.map +1 -1
  27. package/dist/components/TaskList.js +2 -5
  28. package/dist/components/ToolDisplay.d.ts.map +1 -1
  29. package/dist/components/ToolDisplay.js +1 -1
  30. package/dist/contexts/useChat.d.ts.map +1 -1
  31. package/dist/contexts/useChat.js +17 -2
  32. package/dist/utils/highlightUtils.d.ts +2 -0
  33. package/dist/utils/highlightUtils.d.ts.map +1 -0
  34. package/dist/utils/highlightUtils.js +69 -0
  35. package/dist/utils/toolParameterTransforms.d.ts +1 -1
  36. package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
  37. package/dist/utils/toolParameterTransforms.js +10 -3
  38. package/package.json +4 -2
  39. package/src/components/ChatInterface.tsx +35 -23
  40. package/src/components/ConfirmationDetails.tsx +13 -9
  41. package/src/components/ConfirmationSelector.tsx +207 -128
  42. package/src/components/DiffDisplay.tsx +162 -59
  43. package/src/components/HistorySearch.tsx +1 -0
  44. package/src/components/Markdown.tsx +3 -1
  45. package/src/components/McpManager.tsx +51 -59
  46. package/src/components/MessageBlockItem.tsx +83 -0
  47. package/src/components/MessageList.tsx +55 -52
  48. package/src/components/PluginDetail.tsx +30 -31
  49. package/src/components/TaskList.tsx +2 -5
  50. package/src/components/ToolDisplay.tsx +5 -1
  51. package/src/contexts/useChat.tsx +18 -2
  52. package/src/utils/highlightUtils.ts +76 -0
  53. package/src/utils/toolParameterTransforms.ts +11 -2
  54. package/dist/components/MessageItem.d.ts +0 -8
  55. package/dist/components/MessageItem.d.ts.map +0 -1
  56. package/dist/components/MessageItem.js +0 -13
  57. package/src/components/MessageItem.tsx +0 -81
@@ -1,5 +1,5 @@
1
- import React, { useState } from "react";
2
- import { Box, Text, useInput } from "ink";
1
+ import React, { useLayoutEffect, useRef, useState } from "react";
2
+ import { Box, Text, useInput, useStdout, measureElement } from "ink";
3
3
  import type { PermissionDecision, AskUserQuestionInput } from "wave-agent-sdk";
4
4
  import {
5
5
  BASH_TOOL_NAME,
@@ -25,10 +25,11 @@ export interface ConfirmationSelectorProps {
25
25
  onDecision: (decision: PermissionDecision) => void;
26
26
  onCancel: () => void;
27
27
  onAbort: () => void;
28
+ onHeightMeasured?: (height: number) => void;
28
29
  }
29
30
 
30
31
  interface ConfirmationState {
31
- selectedOption: "allow" | "auto" | "alternative";
32
+ selectedOption: "clear" | "auto" | "allow" | "alternative";
32
33
  alternativeText: string;
33
34
  alternativeCursorPosition: number;
34
35
  hasUserInput: boolean;
@@ -43,30 +44,41 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
43
44
  onDecision,
44
45
  onCancel,
45
46
  onAbort,
47
+ onHeightMeasured,
46
48
  }) => {
49
+ const { stdout } = useStdout();
50
+ const boxRef = useRef(null);
51
+
52
+ useLayoutEffect(() => {
53
+ if (boxRef.current) {
54
+ const { height } = measureElement(boxRef.current);
55
+ onHeightMeasured?.(height);
56
+ }
57
+ }, [stdout?.rows, onHeightMeasured, toolName, toolInput, isExpanded]);
58
+
47
59
  const [state, setState] = useState<ConfirmationState>({
48
- selectedOption: "allow",
60
+ selectedOption: toolName === EXIT_PLAN_MODE_TOOL_NAME ? "clear" : "allow",
49
61
  alternativeText: "",
50
62
  alternativeCursorPosition: 0,
51
63
  hasUserInput: false,
52
64
  });
53
65
 
54
- const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
55
- const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
56
- const [selectedOptionIndices, setSelectedOptionIndices] = useState<
57
- Set<number>
58
- >(new Set());
59
- const [userAnswers, setUserAnswers] = useState<Record<string, string>>({});
60
- const [otherText, setOtherText] = useState("");
61
- const [otherCursorPosition, setOtherCursorPosition] = useState(0);
66
+ const [questionState, setQuestionState] = useState({
67
+ currentQuestionIndex: 0,
68
+ selectedOptionIndex: 0,
69
+ selectedOptionIndices: new Set<number>(),
70
+ userAnswers: {} as Record<string, string>,
71
+ otherText: "",
72
+ otherCursorPosition: 0,
73
+ });
62
74
 
63
75
  const questions =
64
76
  (toolInput as unknown as AskUserQuestionInput)?.questions || [];
65
- const currentQuestion = questions[currentQuestionIndex];
77
+ const currentQuestion = questions[questionState.currentQuestionIndex];
66
78
 
67
79
  const getAutoOptionText = () => {
68
80
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
69
- return "Yes, and auto-accept edits";
81
+ return "Yes, auto-accept edits";
70
82
  }
71
83
  if (toolName === BASH_TOOL_NAME) {
72
84
  if (suggestedPrefix) {
@@ -88,112 +100,156 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
88
100
  if (!currentQuestion) return;
89
101
  const options = [...currentQuestion.options, { label: "Other" }];
90
102
  const isMultiSelect = currentQuestion.multiSelect;
91
- const isOtherFocused = selectedOptionIndex === options.length - 1;
92
103
 
93
104
  if (key.return) {
94
- let answer = "";
95
- if (isMultiSelect) {
96
- const selectedLabels = Array.from(selectedOptionIndices)
97
- .filter((i) => i < currentQuestion.options.length)
98
- .map((i) => currentQuestion.options[i].label);
99
- const isOtherChecked = selectedOptionIndices.has(options.length - 1);
100
- if (isOtherChecked && otherText.trim()) {
101
- selectedLabels.push(otherText.trim());
105
+ setQuestionState((prev) => {
106
+ const isOtherFocused =
107
+ prev.selectedOptionIndex === options.length - 1;
108
+ let answer = "";
109
+ if (isMultiSelect) {
110
+ const selectedLabels = Array.from(prev.selectedOptionIndices)
111
+ .filter((i) => i < currentQuestion.options.length)
112
+ .map((i) => currentQuestion.options[i].label);
113
+ const isOtherChecked = prev.selectedOptionIndices.has(
114
+ options.length - 1,
115
+ );
116
+ if (isOtherChecked && prev.otherText.trim()) {
117
+ selectedLabels.push(prev.otherText.trim());
118
+ }
119
+ answer = selectedLabels.join(", ");
120
+ } else {
121
+ if (isOtherFocused) {
122
+ answer = prev.otherText.trim();
123
+ } else {
124
+ answer = options[prev.selectedOptionIndex].label;
125
+ }
102
126
  }
103
- answer = selectedLabels.join(", ");
104
- } else {
105
- if (isOtherFocused) {
106
- answer = otherText.trim();
127
+
128
+ if (!answer) return prev;
129
+
130
+ const newAnswers = {
131
+ ...prev.userAnswers,
132
+ [currentQuestion.question]: answer,
133
+ };
134
+
135
+ if (prev.currentQuestionIndex < questions.length - 1) {
136
+ return {
137
+ ...prev,
138
+ currentQuestionIndex: prev.currentQuestionIndex + 1,
139
+ selectedOptionIndex: 0,
140
+ selectedOptionIndices: new Set(),
141
+ userAnswers: newAnswers,
142
+ otherText: "",
143
+ otherCursorPosition: 0,
144
+ };
107
145
  } else {
108
- answer = options[selectedOptionIndex].label;
146
+ onDecision({
147
+ behavior: "allow",
148
+ message: JSON.stringify(newAnswers),
149
+ });
150
+ return {
151
+ ...prev,
152
+ userAnswers: newAnswers,
153
+ };
109
154
  }
110
- }
111
- if (!answer) return;
112
- const newAnswers = {
113
- ...userAnswers,
114
- [currentQuestion.question]: answer,
115
- };
116
- setUserAnswers(newAnswers);
117
- if (currentQuestionIndex < questions.length - 1) {
118
- setCurrentQuestionIndex(currentQuestionIndex + 1);
119
- setSelectedOptionIndex(0);
120
- setSelectedOptionIndices(new Set());
121
- setOtherText("");
122
- setOtherCursorPosition(0);
123
- } else {
124
- onDecision({
125
- behavior: "allow",
126
- message: JSON.stringify(newAnswers),
127
- });
128
- }
155
+ });
129
156
  return;
130
157
  }
131
158
 
132
159
  if (input === " ") {
133
- if (
134
- isMultiSelect &&
135
- (!isOtherFocused || !selectedOptionIndices.has(selectedOptionIndex))
136
- ) {
137
- setSelectedOptionIndices((prev) => {
138
- const next = new Set(prev);
139
- if (next.has(selectedOptionIndex)) next.delete(selectedOptionIndex);
140
- else next.add(selectedOptionIndex);
141
- return next;
142
- });
143
- return;
144
- }
145
- if (!isOtherFocused) return;
160
+ setQuestionState((prev) => {
161
+ const isOtherFocused =
162
+ prev.selectedOptionIndex === options.length - 1;
163
+ if (
164
+ isMultiSelect &&
165
+ (!isOtherFocused ||
166
+ !prev.selectedOptionIndices.has(prev.selectedOptionIndex))
167
+ ) {
168
+ const nextIndices = new Set(prev.selectedOptionIndices);
169
+ if (nextIndices.has(prev.selectedOptionIndex))
170
+ nextIndices.delete(prev.selectedOptionIndex);
171
+ else nextIndices.add(prev.selectedOptionIndex);
172
+ return {
173
+ ...prev,
174
+ selectedOptionIndices: nextIndices,
175
+ };
176
+ }
177
+ return prev;
178
+ });
179
+ // If it's other and focused, we don't return here, allowing the input handler below to handle it
146
180
  }
147
181
 
148
182
  if (key.upArrow) {
149
- if (selectedOptionIndex > 0)
150
- setSelectedOptionIndex(selectedOptionIndex - 1);
183
+ setQuestionState((prev) => ({
184
+ ...prev,
185
+ selectedOptionIndex: Math.max(0, prev.selectedOptionIndex - 1),
186
+ }));
151
187
  return;
152
188
  }
153
189
  if (key.downArrow) {
154
- if (selectedOptionIndex < options.length - 1)
155
- setSelectedOptionIndex(selectedOptionIndex + 1);
190
+ setQuestionState((prev) => ({
191
+ ...prev,
192
+ selectedOptionIndex: Math.min(
193
+ options.length - 1,
194
+ prev.selectedOptionIndex + 1,
195
+ ),
196
+ }));
156
197
  return;
157
198
  }
158
199
 
159
- if (isOtherFocused) {
160
- if (key.leftArrow) {
161
- setOtherCursorPosition((prev) => Math.max(0, prev - 1));
162
- return;
163
- }
164
- if (key.rightArrow) {
165
- setOtherCursorPosition((prev) =>
166
- Math.min(otherText.length, prev + 1),
167
- );
168
- return;
169
- }
170
- if (key.backspace || key.delete) {
171
- if (otherCursorPosition > 0) {
172
- setOtherText(
173
- (prev) =>
174
- prev.slice(0, otherCursorPosition - 1) +
175
- prev.slice(otherCursorPosition),
176
- );
177
- setOtherCursorPosition((prev) => prev - 1);
200
+ setQuestionState((prev) => {
201
+ const isOtherFocused = prev.selectedOptionIndex === options.length - 1;
202
+ if (isOtherFocused) {
203
+ if (key.leftArrow) {
204
+ return {
205
+ ...prev,
206
+ otherCursorPosition: Math.max(0, prev.otherCursorPosition - 1),
207
+ };
208
+ }
209
+ if (key.rightArrow) {
210
+ return {
211
+ ...prev,
212
+ otherCursorPosition: Math.min(
213
+ prev.otherText.length,
214
+ prev.otherCursorPosition + 1,
215
+ ),
216
+ };
217
+ }
218
+ if (key.backspace || key.delete) {
219
+ if (prev.otherCursorPosition > 0) {
220
+ return {
221
+ ...prev,
222
+ otherText:
223
+ prev.otherText.slice(0, prev.otherCursorPosition - 1) +
224
+ prev.otherText.slice(prev.otherCursorPosition),
225
+ otherCursorPosition: prev.otherCursorPosition - 1,
226
+ };
227
+ }
228
+ }
229
+ if (input && !key.ctrl && !key.meta) {
230
+ return {
231
+ ...prev,
232
+ otherText:
233
+ prev.otherText.slice(0, prev.otherCursorPosition) +
234
+ input +
235
+ prev.otherText.slice(prev.otherCursorPosition),
236
+ otherCursorPosition: prev.otherCursorPosition + input.length,
237
+ };
178
238
  }
179
- return;
180
- }
181
- if (input && !key.ctrl && !key.meta) {
182
- setOtherText(
183
- (prev) =>
184
- prev.slice(0, otherCursorPosition) +
185
- input +
186
- prev.slice(otherCursorPosition),
187
- );
188
- setOtherCursorPosition((prev) => prev + input.length);
189
- return;
190
239
  }
191
- }
240
+ return prev;
241
+ });
192
242
  return;
193
243
  }
194
244
 
195
245
  if (key.return) {
196
- if (state.selectedOption === "allow") {
246
+ if (state.selectedOption === "clear") {
247
+ onDecision({
248
+ behavior: "allow",
249
+ newPermissionMode: "acceptEdits",
250
+ clearContext: true,
251
+ });
252
+ } else if (state.selectedOption === "allow") {
197
253
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
198
254
  onDecision({ behavior: "allow", newPermissionMode: "default" });
199
255
  } else {
@@ -237,31 +293,31 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
237
293
  }
238
294
  }
239
295
 
296
+ const availableOptions: ConfirmationState["selectedOption"][] = [];
297
+ if (toolName === EXIT_PLAN_MODE_TOOL_NAME) availableOptions.push("clear");
298
+ availableOptions.push("allow");
299
+ if (!hidePersistentOption) availableOptions.push("auto");
300
+ availableOptions.push("alternative");
301
+
240
302
  if (key.upArrow) {
241
- setState((prev) => {
242
- if (prev.selectedOption === "alternative")
243
- return {
244
- ...prev,
245
- selectedOption: hidePersistentOption ? "allow" : "auto",
246
- };
247
- if (prev.selectedOption === "auto")
248
- return { ...prev, selectedOption: "allow" };
249
- return prev;
250
- });
303
+ const currentIndex = availableOptions.indexOf(state.selectedOption);
304
+ if (currentIndex > 0) {
305
+ setState((prev) => ({
306
+ ...prev,
307
+ selectedOption: availableOptions[currentIndex - 1],
308
+ }));
309
+ }
251
310
  return;
252
311
  }
253
312
 
254
313
  if (key.downArrow) {
255
- setState((prev) => {
256
- if (prev.selectedOption === "allow")
257
- return {
258
- ...prev,
259
- selectedOption: hidePersistentOption ? "alternative" : "auto",
260
- };
261
- if (prev.selectedOption === "auto")
262
- return { ...prev, selectedOption: "alternative" };
263
- return prev;
264
- });
314
+ const currentIndex = availableOptions.indexOf(state.selectedOption);
315
+ if (currentIndex < availableOptions.length - 1) {
316
+ setState((prev) => ({
317
+ ...prev,
318
+ selectedOption: availableOptions[currentIndex + 1],
319
+ }));
320
+ }
265
321
  return;
266
322
  }
267
323
 
@@ -303,12 +359,12 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
303
359
  }
304
360
  });
305
361
 
306
- const placeholderText = "Type here to tell Wave what to do differently";
362
+ const placeholderText = "Type here to tell Wave what to change";
307
363
  const showPlaceholder =
308
364
  state.selectedOption === "alternative" && !state.hasUserInput;
309
365
 
310
366
  return (
311
- <Box flexDirection="column">
367
+ <Box ref={boxRef} flexDirection="column">
312
368
  {toolName === ASK_USER_QUESTION_TOOL_NAME &&
313
369
  currentQuestion &&
314
370
  !isExpanded && (
@@ -324,9 +380,10 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
324
380
  <Box flexDirection="column">
325
381
  {[...currentQuestion.options, { label: "Other" }].map(
326
382
  (option, index) => {
327
- const isSelected = selectedOptionIndex === index;
383
+ const isSelected =
384
+ questionState.selectedOptionIndex === index;
328
385
  const isChecked = currentQuestion.multiSelect
329
- ? selectedOptionIndices.has(index)
386
+ ? questionState.selectedOptionIndices.has(index)
330
387
  : isSelected;
331
388
  const isOther = index === currentQuestion.options.length;
332
389
  return (
@@ -346,13 +403,20 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
346
403
  {isOther && isSelected && (
347
404
  <Text>
348
405
  :{" "}
349
- {otherText ? (
406
+ {questionState.otherText ? (
350
407
  <>
351
- {otherText.slice(0, otherCursorPosition)}
408
+ {questionState.otherText.slice(
409
+ 0,
410
+ questionState.otherCursorPosition,
411
+ )}
352
412
  <Text backgroundColor="white" color="black">
353
- {otherText[otherCursorPosition] || " "}
413
+ {questionState.otherText[
414
+ questionState.otherCursorPosition
415
+ ] || " "}
354
416
  </Text>
355
- {otherText.slice(otherCursorPosition + 1)}
417
+ {questionState.otherText.slice(
418
+ questionState.otherCursorPosition + 1,
419
+ )}
356
420
  </>
357
421
  ) : (
358
422
  <Text color="gray" dimColor>
@@ -369,7 +433,8 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
369
433
  </Box>
370
434
  <Box marginTop={1}>
371
435
  <Text dimColor>
372
- Question {currentQuestionIndex + 1} of {questions.length}
436
+ Question {questionState.currentQuestionIndex + 1} of{" "}
437
+ {questions.length} •
373
438
  {currentQuestion.multiSelect ? " Space to toggle •" : ""} Use ↑↓
374
439
  to navigate • Enter to confirm
375
440
  </Text>
@@ -383,6 +448,20 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
383
448
  <Text>Do you want to proceed?</Text>
384
449
  </Box>
385
450
  <Box marginTop={1} flexDirection="column">
451
+ {toolName === EXIT_PLAN_MODE_TOOL_NAME && (
452
+ <Box key="clear-option">
453
+ <Text
454
+ color={state.selectedOption === "clear" ? "black" : "white"}
455
+ backgroundColor={
456
+ state.selectedOption === "clear" ? "yellow" : undefined
457
+ }
458
+ bold={state.selectedOption === "clear"}
459
+ >
460
+ {state.selectedOption === "clear" ? "> " : " "}
461
+ Yes, clear context and auto-accept edits
462
+ </Text>
463
+ </Box>
464
+ )}
386
465
  <Box key="allow-option">
387
466
  <Text
388
467
  color={state.selectedOption === "allow" ? "black" : "white"}
@@ -393,8 +472,8 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
393
472
  >
394
473
  {state.selectedOption === "allow" ? "> " : " "}
395
474
  {toolName === EXIT_PLAN_MODE_TOOL_NAME
396
- ? "Yes, proceed with default mode"
397
- : "Yes"}
475
+ ? "Yes, manually approve edits"
476
+ : "Yes, proceed"}
398
477
  </Text>
399
478
  </Box>
400
479
  {!hidePersistentOption && (
@@ -444,7 +523,7 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
444
523
  )}
445
524
  </>
446
525
  ) : (
447
- "Type here to tell Wave what to do differently"
526
+ "Type here to tell Wave what to change"
448
527
  )}
449
528
  </Text>
450
529
  )}