wave-code 0.4.0 → 0.6.1

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 (105) hide show
  1. package/dist/commands/plugin/uninstall.js +1 -1
  2. package/dist/components/App.d.ts.map +1 -1
  3. package/dist/components/App.js +38 -2
  4. package/dist/components/BackgroundTaskManager.d.ts +6 -0
  5. package/dist/components/BackgroundTaskManager.d.ts.map +1 -0
  6. package/dist/components/BackgroundTaskManager.js +114 -0
  7. package/dist/components/ChatInterface.d.ts.map +1 -1
  8. package/dist/components/ChatInterface.js +39 -5
  9. package/dist/components/CommandSelector.d.ts.map +1 -1
  10. package/dist/components/CommandSelector.js +13 -5
  11. package/dist/components/CompressDisplay.d.ts.map +1 -1
  12. package/dist/components/CompressDisplay.js +6 -10
  13. package/dist/components/ConfirmationDetails.d.ts +9 -0
  14. package/dist/components/ConfirmationDetails.d.ts.map +1 -0
  15. package/dist/components/ConfirmationDetails.js +53 -0
  16. package/dist/components/{Confirmation.d.ts → ConfirmationSelector.d.ts} +3 -3
  17. package/dist/components/ConfirmationSelector.d.ts.map +1 -0
  18. package/dist/components/{Confirmation.js → ConfirmationSelector.js} +92 -101
  19. package/dist/components/DiffDisplay.d.ts +0 -1
  20. package/dist/components/DiffDisplay.d.ts.map +1 -1
  21. package/dist/components/DiffDisplay.js +82 -60
  22. package/dist/components/FileSelector.d.ts.map +1 -1
  23. package/dist/components/FileSelector.js +2 -2
  24. package/dist/components/HistorySearch.d.ts.map +1 -1
  25. package/dist/components/HistorySearch.js +12 -4
  26. package/dist/components/InputBox.d.ts +1 -3
  27. package/dist/components/InputBox.d.ts.map +1 -1
  28. package/dist/components/InputBox.js +9 -18
  29. package/dist/components/LoadingIndicator.d.ts +11 -0
  30. package/dist/components/LoadingIndicator.d.ts.map +1 -0
  31. package/dist/components/LoadingIndicator.js +6 -0
  32. package/dist/components/Markdown.d.ts.map +1 -1
  33. package/dist/components/Markdown.js +114 -120
  34. package/dist/components/MessageItem.d.ts.map +1 -1
  35. package/dist/components/MessageItem.js +1 -2
  36. package/dist/components/MessageList.d.ts +2 -3
  37. package/dist/components/MessageList.d.ts.map +1 -1
  38. package/dist/components/MessageList.js +7 -7
  39. package/dist/components/PlanDisplay.d.ts.map +1 -1
  40. package/dist/components/PlanDisplay.js +4 -12
  41. package/dist/components/PluginDetail.js +1 -1
  42. package/dist/components/RewindCommand.d.ts +4 -0
  43. package/dist/components/RewindCommand.d.ts.map +1 -1
  44. package/dist/components/RewindCommand.js +19 -2
  45. package/dist/components/SubagentBlock.d.ts.map +1 -1
  46. package/dist/components/SubagentBlock.js +12 -5
  47. package/dist/components/TaskList.d.ts +3 -0
  48. package/dist/components/TaskList.d.ts.map +1 -0
  49. package/dist/components/TaskList.js +49 -0
  50. package/dist/components/ToolResultDisplay.d.ts.map +1 -1
  51. package/dist/components/ToolResultDisplay.js +2 -1
  52. package/dist/contexts/useChat.d.ts +15 -6
  53. package/dist/contexts/useChat.d.ts.map +1 -1
  54. package/dist/contexts/useChat.js +52 -43
  55. package/dist/hooks/useInputManager.d.ts +2 -13
  56. package/dist/hooks/useInputManager.d.ts.map +1 -1
  57. package/dist/hooks/useInputManager.js +8 -57
  58. package/dist/hooks/usePluginManager.d.ts.map +1 -1
  59. package/dist/hooks/usePluginManager.js +8 -4
  60. package/dist/hooks/useTasks.d.ts +2 -0
  61. package/dist/hooks/useTasks.d.ts.map +1 -0
  62. package/dist/hooks/useTasks.js +5 -0
  63. package/dist/managers/InputManager.d.ts +5 -28
  64. package/dist/managers/InputManager.d.ts.map +1 -1
  65. package/dist/managers/InputManager.js +26 -127
  66. package/package.json +9 -10
  67. package/src/commands/plugin/uninstall.ts +1 -1
  68. package/src/components/App.tsx +50 -3
  69. package/src/components/{BashShellManager.tsx → BackgroundTaskManager.tsx} +79 -73
  70. package/src/components/ChatInterface.tsx +79 -23
  71. package/src/components/CommandSelector.tsx +38 -20
  72. package/src/components/CompressDisplay.tsx +5 -22
  73. package/src/components/ConfirmationDetails.tsx +108 -0
  74. package/src/components/{Confirmation.tsx → ConfirmationSelector.tsx} +162 -187
  75. package/src/components/DiffDisplay.tsx +122 -107
  76. package/src/components/FileSelector.tsx +0 -2
  77. package/src/components/HistorySearch.tsx +45 -21
  78. package/src/components/InputBox.tsx +14 -34
  79. package/src/components/LoadingIndicator.tsx +56 -0
  80. package/src/components/Markdown.tsx +126 -318
  81. package/src/components/MessageItem.tsx +1 -3
  82. package/src/components/MessageList.tsx +10 -67
  83. package/src/components/PlanDisplay.tsx +5 -33
  84. package/src/components/PluginDetail.tsx +1 -1
  85. package/src/components/RewindCommand.tsx +38 -1
  86. package/src/components/SubagentBlock.tsx +28 -14
  87. package/src/components/TaskList.tsx +70 -0
  88. package/src/components/ToolResultDisplay.tsx +6 -2
  89. package/src/contexts/useChat.tsx +82 -60
  90. package/src/hooks/useInputManager.ts +9 -73
  91. package/src/hooks/usePluginManager.ts +10 -4
  92. package/src/hooks/useTasks.ts +6 -0
  93. package/src/managers/InputManager.ts +30 -157
  94. package/dist/components/BashShellManager.d.ts +0 -6
  95. package/dist/components/BashShellManager.d.ts.map +0 -1
  96. package/dist/components/BashShellManager.js +0 -116
  97. package/dist/components/Confirmation.d.ts.map +0 -1
  98. package/dist/components/MemoryDisplay.d.ts +0 -8
  99. package/dist/components/MemoryDisplay.d.ts.map +0 -1
  100. package/dist/components/MemoryDisplay.js +0 -25
  101. package/dist/components/MemoryTypeSelector.d.ts +0 -8
  102. package/dist/components/MemoryTypeSelector.d.ts.map +0 -1
  103. package/dist/components/MemoryTypeSelector.js +0 -38
  104. package/src/components/MemoryDisplay.tsx +0 -62
  105. package/src/components/MemoryTypeSelector.tsx +0 -98
@@ -0,0 +1,108 @@
1
+ import React, { useLayoutEffect, useRef, useState } from "react";
2
+ import { Box, Text, useStdout, measureElement, Static } from "ink";
3
+ import {
4
+ BASH_TOOL_NAME,
5
+ EDIT_TOOL_NAME,
6
+ MULTI_EDIT_TOOL_NAME,
7
+ DELETE_FILE_TOOL_NAME,
8
+ WRITE_TOOL_NAME,
9
+ EXIT_PLAN_MODE_TOOL_NAME,
10
+ ASK_USER_QUESTION_TOOL_NAME,
11
+ } from "wave-agent-sdk";
12
+ import { DiffDisplay } from "./DiffDisplay.js";
13
+ import { PlanDisplay } from "./PlanDisplay.js";
14
+
15
+ // Helper function to generate descriptive action text
16
+ const getActionDescription = (
17
+ toolName: string,
18
+ toolInput?: Record<string, unknown>,
19
+ ): string => {
20
+ if (!toolInput) {
21
+ return "Execute operation";
22
+ }
23
+
24
+ switch (toolName) {
25
+ case BASH_TOOL_NAME:
26
+ return `Execute command: ${toolInput.command || "unknown command"}`;
27
+ case EDIT_TOOL_NAME:
28
+ return `Edit file: ${toolInput.file_path || "unknown file"}`;
29
+ case MULTI_EDIT_TOOL_NAME:
30
+ return `Edit multiple sections in: ${toolInput.file_path || "unknown file"}`;
31
+ case DELETE_FILE_TOOL_NAME:
32
+ return `Delete file: ${toolInput.target_file || "unknown file"}`;
33
+ case WRITE_TOOL_NAME:
34
+ return `Write to file: ${toolInput.file_path || "unknown file"}`;
35
+ case EXIT_PLAN_MODE_TOOL_NAME:
36
+ return "Review and approve the plan";
37
+ case ASK_USER_QUESTION_TOOL_NAME:
38
+ return "Answer questions to clarify intent";
39
+ default:
40
+ return "Execute operation";
41
+ }
42
+ };
43
+
44
+ export interface ConfirmationDetailsProps {
45
+ toolName: string;
46
+ toolInput?: Record<string, unknown>;
47
+ isExpanded?: boolean;
48
+ onHeightMeasured?: (height: number) => void;
49
+ }
50
+
51
+ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
52
+ toolName,
53
+ toolInput,
54
+ isExpanded = false,
55
+ onHeightMeasured,
56
+ }) => {
57
+ const { stdout } = useStdout();
58
+ const [isStatic, setIsStatic] = useState(false);
59
+ const boxRef = useRef(null);
60
+
61
+ useLayoutEffect(() => {
62
+ if (boxRef.current) {
63
+ const { height } = measureElement(boxRef.current);
64
+ const terminalHeight = stdout?.rows || 24;
65
+ if (height > terminalHeight - 10) {
66
+ setIsStatic(true);
67
+ }
68
+ onHeightMeasured?.(height);
69
+ }
70
+ }, [stdout?.rows, onHeightMeasured]);
71
+
72
+ const content = (
73
+ <Box
74
+ ref={boxRef}
75
+ flexDirection="column"
76
+ borderStyle="single"
77
+ borderColor="yellow"
78
+ borderBottom={false}
79
+ borderLeft={false}
80
+ borderRight={false}
81
+ paddingTop={1}
82
+ >
83
+ <Text color="yellow" bold>
84
+ Tool: {toolName}
85
+ </Text>
86
+ <Text color="yellow">{getActionDescription(toolName, toolInput)}</Text>
87
+
88
+ <DiffDisplay toolName={toolName} parameters={JSON.stringify(toolInput)} />
89
+
90
+ {toolName !== ASK_USER_QUESTION_TOOL_NAME &&
91
+ toolName === EXIT_PLAN_MODE_TOOL_NAME &&
92
+ !!toolInput?.plan_content && (
93
+ <PlanDisplay
94
+ plan={toolInput.plan_content as string}
95
+ isExpanded={isExpanded}
96
+ />
97
+ )}
98
+ </Box>
99
+ );
100
+
101
+ if (isStatic) {
102
+ return <Static items={[1]}>{() => content}</Static>;
103
+ }
104
+
105
+ return content;
106
+ };
107
+
108
+ ConfirmationDetails.displayName = "ConfirmationDetails";
@@ -3,44 +3,9 @@ import { Box, Text, useInput } from "ink";
3
3
  import type { PermissionDecision, AskUserQuestionInput } from "wave-agent-sdk";
4
4
  import {
5
5
  BASH_TOOL_NAME,
6
- EDIT_TOOL_NAME,
7
- MULTI_EDIT_TOOL_NAME,
8
- DELETE_FILE_TOOL_NAME,
9
- WRITE_TOOL_NAME,
10
6
  EXIT_PLAN_MODE_TOOL_NAME,
11
7
  ASK_USER_QUESTION_TOOL_NAME,
12
8
  } from "wave-agent-sdk";
13
- import { DiffDisplay } from "./DiffDisplay.js";
14
- import { PlanDisplay } from "./PlanDisplay.js";
15
-
16
- // Helper function to generate descriptive action text
17
- const getActionDescription = (
18
- toolName: string,
19
- toolInput?: Record<string, unknown>,
20
- ): string => {
21
- if (!toolInput) {
22
- return "Execute operation";
23
- }
24
-
25
- switch (toolName) {
26
- case BASH_TOOL_NAME:
27
- return `Execute command: ${toolInput.command || "unknown command"}`;
28
- case EDIT_TOOL_NAME:
29
- return `Edit file: ${toolInput.file_path || "unknown file"}`;
30
- case MULTI_EDIT_TOOL_NAME:
31
- return `Edit multiple sections in: ${toolInput.file_path || "unknown file"}`;
32
- case DELETE_FILE_TOOL_NAME:
33
- return `Delete file: ${toolInput.target_file || "unknown file"}`;
34
- case WRITE_TOOL_NAME:
35
- return `Write to file: ${toolInput.file_path || "unknown file"}`;
36
- case EXIT_PLAN_MODE_TOOL_NAME:
37
- return "Review and approve the plan";
38
- case ASK_USER_QUESTION_TOOL_NAME:
39
- return "Answer questions to clarify intent";
40
- default:
41
- return "Execute operation";
42
- }
43
- };
44
9
 
45
10
  const getHeaderColor = (header: string) => {
46
11
  const colors = ["red", "green", "blue", "magenta", "cyan"] as const;
@@ -51,7 +16,7 @@ const getHeaderColor = (header: string) => {
51
16
  return colors[Math.abs(hash) % colors.length];
52
17
  };
53
18
 
54
- export interface ConfirmationProps {
19
+ export interface ConfirmationSelectorProps {
55
20
  toolName: string;
56
21
  toolInput?: Record<string, unknown>;
57
22
  suggestedPrefix?: string;
@@ -65,10 +30,11 @@ export interface ConfirmationProps {
65
30
  interface ConfirmationState {
66
31
  selectedOption: "allow" | "auto" | "alternative";
67
32
  alternativeText: string;
68
- hasUserInput: boolean; // to hide placeholder
33
+ alternativeCursorPosition: number;
34
+ hasUserInput: boolean;
69
35
  }
70
36
 
71
- export const Confirmation: React.FC<ConfirmationProps> = ({
37
+ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
72
38
  toolName,
73
39
  toolInput,
74
40
  suggestedPrefix,
@@ -81,10 +47,10 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
81
47
  const [state, setState] = useState<ConfirmationState>({
82
48
  selectedOption: "allow",
83
49
  alternativeText: "",
50
+ alternativeCursorPosition: 0,
84
51
  hasUserInput: false,
85
52
  });
86
53
 
87
- // Specialized state for AskUserQuestion
88
54
  const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
89
55
  const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
90
56
  const [selectedOptionIndices, setSelectedOptionIndices] = useState<
@@ -92,6 +58,7 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
92
58
  >(new Set());
93
59
  const [userAnswers, setUserAnswers] = useState<Record<string, string>>({});
94
60
  const [otherText, setOtherText] = useState("");
61
+ const [otherCursorPosition, setOtherCursorPosition] = useState(0);
95
62
 
96
63
  const questions =
97
64
  (toolInput as unknown as AskUserQuestionInput)?.questions || [];
@@ -111,7 +78,6 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
111
78
  };
112
79
 
113
80
  useInput((input, key) => {
114
- // Handle ESC to cancel and abort
115
81
  if (key.escape) {
116
82
  onCancel();
117
83
  onAbort();
@@ -120,10 +86,8 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
120
86
 
121
87
  if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
122
88
  if (!currentQuestion) return;
123
-
124
89
  const options = [...currentQuestion.options, { label: "Other" }];
125
- const isMultiSelect = !!currentQuestion.multiSelect;
126
-
90
+ const isMultiSelect = currentQuestion.multiSelect;
127
91
  const isOtherFocused = selectedOptionIndex === options.length - 1;
128
92
 
129
93
  if (key.return) {
@@ -132,7 +96,6 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
132
96
  const selectedLabels = Array.from(selectedOptionIndices)
133
97
  .filter((i) => i < currentQuestion.options.length)
134
98
  .map((i) => currentQuestion.options[i].label);
135
-
136
99
  const isOtherChecked = selectedOptionIndices.has(options.length - 1);
137
100
  if (isOtherChecked && otherText.trim()) {
138
101
  selectedLabels.push(otherText.trim());
@@ -145,22 +108,19 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
145
108
  answer = options[selectedOptionIndex].label;
146
109
  }
147
110
  }
148
-
149
111
  if (!answer) return;
150
-
151
112
  const newAnswers = {
152
113
  ...userAnswers,
153
114
  [currentQuestion.question]: answer,
154
115
  };
155
116
  setUserAnswers(newAnswers);
156
-
157
117
  if (currentQuestionIndex < questions.length - 1) {
158
118
  setCurrentQuestionIndex(currentQuestionIndex + 1);
159
119
  setSelectedOptionIndex(0);
160
120
  setSelectedOptionIndices(new Set());
161
121
  setOtherText("");
122
+ setOtherCursorPosition(0);
162
123
  } else {
163
- // All questions answered
164
124
  onDecision({
165
125
  behavior: "allow",
166
126
  message: JSON.stringify(newAnswers),
@@ -176,49 +136,62 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
176
136
  ) {
177
137
  setSelectedOptionIndices((prev) => {
178
138
  const next = new Set(prev);
179
- if (next.has(selectedOptionIndex)) {
180
- next.delete(selectedOptionIndex);
181
- } else {
182
- next.add(selectedOptionIndex);
183
- }
139
+ if (next.has(selectedOptionIndex)) next.delete(selectedOptionIndex);
140
+ else next.add(selectedOptionIndex);
184
141
  return next;
185
142
  });
186
143
  return;
187
144
  }
188
-
189
- if (!isOtherFocused) {
190
- return;
191
- }
192
- // If isOtherFocused is true, fall through to handle space as text input
145
+ if (!isOtherFocused) return;
193
146
  }
194
147
 
195
148
  if (key.upArrow) {
196
- if (selectedOptionIndex > 0) {
149
+ if (selectedOptionIndex > 0)
197
150
  setSelectedOptionIndex(selectedOptionIndex - 1);
198
- }
199
151
  return;
200
152
  }
201
-
202
153
  if (key.downArrow) {
203
- if (selectedOptionIndex < options.length - 1) {
154
+ if (selectedOptionIndex < options.length - 1)
204
155
  setSelectedOptionIndex(selectedOptionIndex + 1);
205
- }
206
156
  return;
207
157
  }
208
158
 
209
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
+ }
210
170
  if (key.backspace || key.delete) {
211
- setOtherText((prev) => prev.slice(0, -1));
212
- } else if (input && !key.ctrl && !key.meta) {
213
- setOtherText((prev) => prev + input);
171
+ if (otherCursorPosition > 0) {
172
+ setOtherText(
173
+ (prev) =>
174
+ prev.slice(0, otherCursorPosition - 1) +
175
+ prev.slice(otherCursorPosition),
176
+ );
177
+ setOtherCursorPosition((prev) => prev - 1);
178
+ }
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;
214
190
  }
215
- return;
216
191
  }
217
-
218
192
  return;
219
193
  }
220
194
 
221
- // Handle Enter to confirm selection
222
195
  if (key.return) {
223
196
  if (state.selectedOption === "allow") {
224
197
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
@@ -231,37 +204,46 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
231
204
  const rule = suggestedPrefix
232
205
  ? `Bash(${suggestedPrefix}*)`
233
206
  : `Bash(${toolInput?.command})`;
234
- onDecision({
235
- behavior: "allow",
236
- newPermissionRule: rule,
237
- });
207
+ onDecision({ behavior: "allow", newPermissionRule: rule });
238
208
  } else {
239
- onDecision({
240
- behavior: "allow",
241
- newPermissionMode: "acceptEdits",
242
- });
243
- }
244
- } else {
245
- // For alternative option, require text input
246
- if (state.alternativeText.trim()) {
247
- onDecision({
248
- behavior: "deny",
249
- message: state.alternativeText.trim(),
250
- });
209
+ onDecision({ behavior: "allow", newPermissionMode: "acceptEdits" });
251
210
  }
211
+ } else if (state.alternativeText.trim()) {
212
+ onDecision({ behavior: "deny", message: state.alternativeText.trim() });
252
213
  }
253
214
  return;
254
215
  }
255
216
 
256
- // Handle arrow keys for navigation
217
+ if (state.selectedOption === "alternative") {
218
+ if (key.leftArrow) {
219
+ setState((prev) => ({
220
+ ...prev,
221
+ alternativeCursorPosition: Math.max(
222
+ 0,
223
+ prev.alternativeCursorPosition - 1,
224
+ ),
225
+ }));
226
+ return;
227
+ }
228
+ if (key.rightArrow) {
229
+ setState((prev) => ({
230
+ ...prev,
231
+ alternativeCursorPosition: Math.min(
232
+ prev.alternativeText.length,
233
+ prev.alternativeCursorPosition + 1,
234
+ ),
235
+ }));
236
+ return;
237
+ }
238
+ }
239
+
257
240
  if (key.upArrow) {
258
241
  setState((prev) => {
259
- if (prev.selectedOption === "alternative") {
242
+ if (prev.selectedOption === "alternative")
260
243
  return {
261
244
  ...prev,
262
245
  selectedOption: hidePersistentOption ? "allow" : "auto",
263
246
  };
264
- }
265
247
  if (prev.selectedOption === "auto")
266
248
  return { ...prev, selectedOption: "allow" };
267
249
  return prev;
@@ -271,12 +253,11 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
271
253
 
272
254
  if (key.downArrow) {
273
255
  setState((prev) => {
274
- if (prev.selectedOption === "allow") {
256
+ if (prev.selectedOption === "allow")
275
257
  return {
276
258
  ...prev,
277
259
  selectedOption: hidePersistentOption ? "alternative" : "auto",
278
260
  };
279
- }
280
261
  if (prev.selectedOption === "auto")
281
262
  return { ...prev, selectedOption: "alternative" };
282
263
  return prev;
@@ -284,30 +265,42 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
284
265
  return;
285
266
  }
286
267
 
287
- // Handle text input for alternative option
288
268
  if (input && !key.ctrl && !key.meta && !("alt" in key && key.alt)) {
289
- // Focus on alternative option when user starts typing
290
- setState((prev) => ({
291
- selectedOption: "alternative",
292
- alternativeText: prev.alternativeText + input,
293
- hasUserInput: true,
294
- }));
295
- return;
296
- }
297
-
298
- // Handle backspace and delete (same behavior - delete one character)
299
- if (key.backspace || key.delete) {
300
269
  setState((prev) => {
301
- const newText = prev.alternativeText.slice(0, -1);
270
+ const nextText =
271
+ prev.alternativeText.slice(0, prev.alternativeCursorPosition) +
272
+ input +
273
+ prev.alternativeText.slice(prev.alternativeCursorPosition);
302
274
  return {
303
275
  ...prev,
304
276
  selectedOption: "alternative",
305
- alternativeText: newText,
306
- hasUserInput: newText.length > 0,
277
+ alternativeText: nextText,
278
+ alternativeCursorPosition:
279
+ prev.alternativeCursorPosition + input.length,
280
+ hasUserInput: true,
307
281
  };
308
282
  });
309
283
  return;
310
284
  }
285
+
286
+ if (key.backspace || key.delete) {
287
+ setState((prev) => {
288
+ if (prev.alternativeCursorPosition > 0) {
289
+ const nextText =
290
+ prev.alternativeText.slice(0, prev.alternativeCursorPosition - 1) +
291
+ prev.alternativeText.slice(prev.alternativeCursorPosition);
292
+ return {
293
+ ...prev,
294
+ selectedOption: "alternative",
295
+ alternativeText: nextText,
296
+ alternativeCursorPosition: prev.alternativeCursorPosition - 1,
297
+ hasUserInput: nextText.length > 0,
298
+ };
299
+ }
300
+ return prev;
301
+ });
302
+ return;
303
+ }
311
304
  });
312
305
 
313
306
  const placeholderText = "Type here to tell Wave what to do differently";
@@ -315,26 +308,7 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
315
308
  state.selectedOption === "alternative" && !state.hasUserInput;
316
309
 
317
310
  return (
318
- <Box
319
- flexDirection="column"
320
- borderStyle="single"
321
- borderColor="yellow"
322
- borderBottom={false}
323
- borderLeft={false}
324
- borderRight={false}
325
- paddingTop={1}
326
- >
327
- <Text color="yellow" bold>
328
- Tool: {toolName}
329
- </Text>
330
- <Text color="yellow">{getActionDescription(toolName, toolInput)}</Text>
331
-
332
- <DiffDisplay
333
- toolName={toolName}
334
- parameters={JSON.stringify(toolInput)}
335
- isExpanded={isExpanded}
336
- />
337
-
311
+ <Box flexDirection="column">
338
312
  {toolName === ASK_USER_QUESTION_TOOL_NAME &&
339
313
  currentQuestion &&
340
314
  !isExpanded && (
@@ -351,53 +325,52 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
351
325
  </Box>
352
326
  <Text bold>{currentQuestion.question}</Text>
353
327
  </Box>
354
-
355
328
  <Box flexDirection="column">
356
- {(() => {
357
- const isMultiSelect = !!currentQuestion.multiSelect;
358
- return [...currentQuestion.options, { label: "Other" }].map(
359
- (option, index) => {
360
- const isSelected = selectedOptionIndex === index;
361
- const isChecked = isMultiSelect
362
- ? selectedOptionIndices.has(index)
363
- : isSelected;
364
- const isOther = index === currentQuestion.options.length;
365
- const isRecommended = !isOther && option.isRecommended;
366
-
367
- return (
368
- <Box key={index}>
369
- <Text
370
- color={isSelected ? "black" : "white"}
371
- backgroundColor={isSelected ? "yellow" : undefined}
372
- >
373
- {isSelected ? "> " : " "}
374
- {isMultiSelect ? (isChecked ? "[x] " : "[ ] ") : ""}
375
- {option.label}
376
- {isRecommended && (
377
- <Text color="green" bold>
378
- {" "}
379
- (Recommended)
380
- </Text>
381
- )}
382
- {option.description ? ` - ${option.description}` : ""}
383
- {isOther && isSelected && (
384
- <Text>
385
- :{" "}
386
- {otherText || (
387
- <Text color="gray" dimColor>
388
- [Type your answer...]
329
+ {[...currentQuestion.options, { label: "Other" }].map(
330
+ (option, index) => {
331
+ const isSelected = selectedOptionIndex === index;
332
+ const isChecked = currentQuestion.multiSelect
333
+ ? selectedOptionIndices.has(index)
334
+ : isSelected;
335
+ const isOther = index === currentQuestion.options.length;
336
+ return (
337
+ <Box key={index}>
338
+ <Text
339
+ color={isSelected ? "black" : "white"}
340
+ backgroundColor={isSelected ? "yellow" : undefined}
341
+ >
342
+ {isSelected ? "> " : " "}
343
+ {currentQuestion.multiSelect
344
+ ? isChecked
345
+ ? "[x] "
346
+ : "[ ] "
347
+ : ""}
348
+ {option.label}
349
+ {option.description ? ` - ${option.description}` : ""}
350
+ {isOther && isSelected && (
351
+ <Text>
352
+ :{" "}
353
+ {otherText ? (
354
+ <>
355
+ {otherText.slice(0, otherCursorPosition)}
356
+ <Text backgroundColor="white" color="black">
357
+ {otherText[otherCursorPosition] || " "}
389
358
  </Text>
390
- )}
391
- </Text>
392
- )}
393
- </Text>
394
- </Box>
395
- );
396
- },
397
- );
398
- })()}
359
+ {otherText.slice(otherCursorPosition + 1)}
360
+ </>
361
+ ) : (
362
+ <Text color="gray" dimColor>
363
+ [Type your answer...]
364
+ </Text>
365
+ )}
366
+ </Text>
367
+ )}
368
+ </Text>
369
+ </Box>
370
+ );
371
+ },
372
+ )}
399
373
  </Box>
400
-
401
374
  <Box marginTop={1}>
402
375
  <Text dimColor>
403
376
  Question {currentQuestionIndex + 1} of {questions.length} •
@@ -408,23 +381,12 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
408
381
  </Box>
409
382
  )}
410
383
 
411
- {toolName !== ASK_USER_QUESTION_TOOL_NAME &&
412
- toolName === EXIT_PLAN_MODE_TOOL_NAME &&
413
- !!toolInput?.plan_content && (
414
- <PlanDisplay
415
- plan={toolInput.plan_content as string}
416
- isExpanded={isExpanded}
417
- />
418
- )}
419
-
420
384
  {toolName !== ASK_USER_QUESTION_TOOL_NAME && !isExpanded && (
421
385
  <>
422
386
  <Box marginTop={1}>
423
387
  <Text>Do you want to proceed?</Text>
424
388
  </Box>
425
-
426
389
  <Box marginTop={1} flexDirection="column">
427
- {/* Option 1: Yes */}
428
390
  <Box key="allow-option">
429
391
  <Text
430
392
  color={state.selectedOption === "allow" ? "black" : "white"}
@@ -439,8 +401,6 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
439
401
  : "Yes"}
440
402
  </Text>
441
403
  </Box>
442
-
443
- {/* Option 2: Auto-accept/Persistent */}
444
404
  {!hidePersistentOption && (
445
405
  <Box key="auto-option">
446
406
  <Text
@@ -455,8 +415,6 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
455
415
  </Text>
456
416
  </Box>
457
417
  )}
458
-
459
- {/* Option 3: Alternative */}
460
418
  <Box key="alternative-option">
461
419
  <Text
462
420
  color={
@@ -474,14 +432,29 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
474
432
  </Text>
475
433
  ) : (
476
434
  <Text>
477
- {state.alternativeText ||
478
- "Type here to tell Wave what to do differently"}
435
+ {state.alternativeText ? (
436
+ <>
437
+ {state.alternativeText.slice(
438
+ 0,
439
+ state.alternativeCursorPosition,
440
+ )}
441
+ <Text backgroundColor="white" color="black">
442
+ {state.alternativeText[
443
+ state.alternativeCursorPosition
444
+ ] || " "}
445
+ </Text>
446
+ {state.alternativeText.slice(
447
+ state.alternativeCursorPosition + 1,
448
+ )}
449
+ </>
450
+ ) : (
451
+ "Type here to tell Wave what to do differently"
452
+ )}
479
453
  </Text>
480
454
  )}
481
455
  </Text>
482
456
  </Box>
483
457
  </Box>
484
-
485
458
  <Box marginTop={1}>
486
459
  <Text dimColor>Use ↑↓ to navigate • ESC to cancel</Text>
487
460
  </Box>
@@ -490,3 +463,5 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
490
463
  </Box>
491
464
  );
492
465
  };
466
+
467
+ ConfirmationSelector.displayName = "ConfirmationSelector";