wave-code 0.6.5 → 0.7.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 (101) hide show
  1. package/dist/cli.d.ts +1 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +2 -2
  4. package/dist/commands/plugin/disable.d.ts.map +1 -1
  5. package/dist/commands/plugin/disable.js +3 -10
  6. package/dist/commands/plugin/enable.d.ts.map +1 -1
  7. package/dist/commands/plugin/enable.js +3 -10
  8. package/dist/commands/plugin/install.d.ts.map +1 -1
  9. package/dist/commands/plugin/install.js +4 -11
  10. package/dist/commands/plugin/list.d.ts.map +1 -1
  11. package/dist/commands/plugin/list.js +5 -39
  12. package/dist/commands/plugin/marketplace.js +9 -9
  13. package/dist/commands/plugin/uninstall.d.ts.map +1 -1
  14. package/dist/commands/plugin/uninstall.js +4 -17
  15. package/dist/commands/plugin/update.js +3 -3
  16. package/dist/components/App.d.ts +1 -0
  17. package/dist/components/App.d.ts.map +1 -1
  18. package/dist/components/App.js +4 -4
  19. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  20. package/dist/components/BackgroundTaskManager.js +34 -18
  21. package/dist/components/ChatInterface.d.ts.map +1 -1
  22. package/dist/components/ChatInterface.js +28 -15
  23. package/dist/components/ConfirmationDetails.d.ts +1 -0
  24. package/dist/components/ConfirmationDetails.d.ts.map +1 -1
  25. package/dist/components/ConfirmationDetails.js +7 -14
  26. package/dist/components/ConfirmationSelector.d.ts +1 -0
  27. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  28. package/dist/components/ConfirmationSelector.js +164 -117
  29. package/dist/components/DiffDisplay.d.ts +1 -0
  30. package/dist/components/DiffDisplay.d.ts.map +1 -1
  31. package/dist/components/DiffDisplay.js +94 -36
  32. package/dist/components/HistorySearch.d.ts.map +1 -1
  33. package/dist/components/HistorySearch.js +26 -20
  34. package/dist/components/Markdown.d.ts.map +1 -1
  35. package/dist/components/Markdown.js +3 -1
  36. package/dist/components/McpManager.d.ts.map +1 -1
  37. package/dist/components/McpManager.js +49 -52
  38. package/dist/components/MessageBlockItem.d.ts +9 -0
  39. package/dist/components/MessageBlockItem.d.ts.map +1 -0
  40. package/dist/components/MessageBlockItem.js +11 -0
  41. package/dist/components/MessageList.d.ts +2 -4
  42. package/dist/components/MessageList.d.ts.map +1 -1
  43. package/dist/components/MessageList.js +28 -23
  44. package/dist/components/PluginDetail.d.ts.map +1 -1
  45. package/dist/components/PluginDetail.js +19 -22
  46. package/dist/components/SessionSelector.d.ts.map +1 -1
  47. package/dist/components/SessionSelector.js +8 -5
  48. package/dist/components/TaskList.d.ts.map +1 -1
  49. package/dist/components/TaskList.js +2 -5
  50. package/dist/components/ToolDisplay.d.ts.map +1 -1
  51. package/dist/components/ToolDisplay.js +1 -1
  52. package/dist/contexts/useChat.d.ts +1 -0
  53. package/dist/contexts/useChat.d.ts.map +1 -1
  54. package/dist/contexts/useChat.js +20 -3
  55. package/dist/hooks/usePluginManager.d.ts.map +1 -1
  56. package/dist/hooks/usePluginManager.js +20 -39
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +16 -0
  59. package/dist/print-cli.d.ts +1 -0
  60. package/dist/print-cli.d.ts.map +1 -1
  61. package/dist/print-cli.js +2 -1
  62. package/dist/utils/highlightUtils.d.ts +2 -0
  63. package/dist/utils/highlightUtils.d.ts.map +1 -0
  64. package/dist/utils/highlightUtils.js +69 -0
  65. package/dist/utils/toolParameterTransforms.d.ts +2 -6
  66. package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
  67. package/dist/utils/toolParameterTransforms.js +10 -14
  68. package/package.json +4 -2
  69. package/src/cli.tsx +3 -0
  70. package/src/commands/plugin/disable.ts +3 -17
  71. package/src/commands/plugin/enable.ts +3 -17
  72. package/src/commands/plugin/install.ts +4 -18
  73. package/src/commands/plugin/list.ts +5 -55
  74. package/src/commands/plugin/marketplace.ts +9 -9
  75. package/src/commands/plugin/uninstall.ts +4 -26
  76. package/src/commands/plugin/update.ts +3 -3
  77. package/src/components/App.tsx +10 -2
  78. package/src/components/BackgroundTaskManager.tsx +69 -44
  79. package/src/components/ChatInterface.tsx +35 -23
  80. package/src/components/ConfirmationDetails.tsx +13 -15
  81. package/src/components/ConfirmationSelector.tsx +207 -128
  82. package/src/components/DiffDisplay.tsx +164 -75
  83. package/src/components/HistorySearch.tsx +31 -25
  84. package/src/components/Markdown.tsx +3 -1
  85. package/src/components/McpManager.tsx +51 -59
  86. package/src/components/MessageBlockItem.tsx +83 -0
  87. package/src/components/MessageList.tsx +55 -52
  88. package/src/components/PluginDetail.tsx +30 -31
  89. package/src/components/SessionSelector.tsx +8 -5
  90. package/src/components/TaskList.tsx +2 -5
  91. package/src/components/ToolDisplay.tsx +5 -1
  92. package/src/contexts/useChat.tsx +22 -2
  93. package/src/hooks/usePluginManager.ts +21 -57
  94. package/src/index.ts +17 -0
  95. package/src/print-cli.ts +3 -0
  96. package/src/utils/highlightUtils.ts +76 -0
  97. package/src/utils/toolParameterTransforms.ts +11 -20
  98. package/dist/components/MessageItem.d.ts +0 -8
  99. package/dist/components/MessageItem.d.ts.map +0 -1
  100. package/dist/components/MessageItem.js +0 -13
  101. package/src/components/MessageItem.tsx +0 -81
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { Box, Text, useInput } from "ink";
2
+ import { useLayoutEffect, useRef, useState } from "react";
3
+ import { Box, Text, useInput, useStdout, measureElement } from "ink";
4
4
  import { BASH_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
5
5
  const getHeaderColor = (header) => {
6
6
  const colors = ["red", "green", "blue", "magenta", "cyan"];
@@ -10,24 +10,34 @@ const getHeaderColor = (header) => {
10
10
  }
11
11
  return colors[Math.abs(hash) % colors.length];
12
12
  };
13
- export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hidePersistentOption, isExpanded = false, onDecision, onCancel, onAbort, }) => {
13
+ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hidePersistentOption, isExpanded = false, onDecision, onCancel, onAbort, onHeightMeasured, }) => {
14
+ const { stdout } = useStdout();
15
+ const boxRef = useRef(null);
16
+ useLayoutEffect(() => {
17
+ if (boxRef.current) {
18
+ const { height } = measureElement(boxRef.current);
19
+ onHeightMeasured?.(height);
20
+ }
21
+ }, [stdout?.rows, onHeightMeasured, toolName, toolInput, isExpanded]);
14
22
  const [state, setState] = useState({
15
- selectedOption: "allow",
23
+ selectedOption: toolName === EXIT_PLAN_MODE_TOOL_NAME ? "clear" : "allow",
16
24
  alternativeText: "",
17
25
  alternativeCursorPosition: 0,
18
26
  hasUserInput: false,
19
27
  });
20
- const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
21
- const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
22
- const [selectedOptionIndices, setSelectedOptionIndices] = useState(new Set());
23
- const [userAnswers, setUserAnswers] = useState({});
24
- const [otherText, setOtherText] = useState("");
25
- const [otherCursorPosition, setOtherCursorPosition] = useState(0);
28
+ const [questionState, setQuestionState] = useState({
29
+ currentQuestionIndex: 0,
30
+ selectedOptionIndex: 0,
31
+ selectedOptionIndices: new Set(),
32
+ userAnswers: {},
33
+ otherText: "",
34
+ otherCursorPosition: 0,
35
+ });
26
36
  const questions = toolInput?.questions || [];
27
- const currentQuestion = questions[currentQuestionIndex];
37
+ const currentQuestion = questions[questionState.currentQuestionIndex];
28
38
  const getAutoOptionText = () => {
29
39
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
30
- return "Yes, and auto-accept edits";
40
+ return "Yes, auto-accept edits";
31
41
  }
32
42
  if (toolName === BASH_TOOL_NAME) {
33
43
  if (suggestedPrefix) {
@@ -48,104 +58,140 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
48
58
  return;
49
59
  const options = [...currentQuestion.options, { label: "Other" }];
50
60
  const isMultiSelect = currentQuestion.multiSelect;
51
- const isOtherFocused = selectedOptionIndex === options.length - 1;
52
61
  if (key.return) {
53
- let answer = "";
54
- if (isMultiSelect) {
55
- const selectedLabels = Array.from(selectedOptionIndices)
56
- .filter((i) => i < currentQuestion.options.length)
57
- .map((i) => currentQuestion.options[i].label);
58
- const isOtherChecked = selectedOptionIndices.has(options.length - 1);
59
- if (isOtherChecked && otherText.trim()) {
60
- selectedLabels.push(otherText.trim());
62
+ setQuestionState((prev) => {
63
+ const isOtherFocused = prev.selectedOptionIndex === options.length - 1;
64
+ let answer = "";
65
+ if (isMultiSelect) {
66
+ const selectedLabels = Array.from(prev.selectedOptionIndices)
67
+ .filter((i) => i < currentQuestion.options.length)
68
+ .map((i) => currentQuestion.options[i].label);
69
+ const isOtherChecked = prev.selectedOptionIndices.has(options.length - 1);
70
+ if (isOtherChecked && prev.otherText.trim()) {
71
+ selectedLabels.push(prev.otherText.trim());
72
+ }
73
+ answer = selectedLabels.join(", ");
61
74
  }
62
- answer = selectedLabels.join(", ");
63
- }
64
- else {
65
- if (isOtherFocused) {
66
- answer = otherText.trim();
75
+ else {
76
+ if (isOtherFocused) {
77
+ answer = prev.otherText.trim();
78
+ }
79
+ else {
80
+ answer = options[prev.selectedOptionIndex].label;
81
+ }
82
+ }
83
+ if (!answer)
84
+ return prev;
85
+ const newAnswers = {
86
+ ...prev.userAnswers,
87
+ [currentQuestion.question]: answer,
88
+ };
89
+ if (prev.currentQuestionIndex < questions.length - 1) {
90
+ return {
91
+ ...prev,
92
+ currentQuestionIndex: prev.currentQuestionIndex + 1,
93
+ selectedOptionIndex: 0,
94
+ selectedOptionIndices: new Set(),
95
+ userAnswers: newAnswers,
96
+ otherText: "",
97
+ otherCursorPosition: 0,
98
+ };
67
99
  }
68
100
  else {
69
- answer = options[selectedOptionIndex].label;
101
+ onDecision({
102
+ behavior: "allow",
103
+ message: JSON.stringify(newAnswers),
104
+ });
105
+ return {
106
+ ...prev,
107
+ userAnswers: newAnswers,
108
+ };
70
109
  }
71
- }
72
- if (!answer)
73
- return;
74
- const newAnswers = {
75
- ...userAnswers,
76
- [currentQuestion.question]: answer,
77
- };
78
- setUserAnswers(newAnswers);
79
- if (currentQuestionIndex < questions.length - 1) {
80
- setCurrentQuestionIndex(currentQuestionIndex + 1);
81
- setSelectedOptionIndex(0);
82
- setSelectedOptionIndices(new Set());
83
- setOtherText("");
84
- setOtherCursorPosition(0);
85
- }
86
- else {
87
- onDecision({
88
- behavior: "allow",
89
- message: JSON.stringify(newAnswers),
90
- });
91
- }
110
+ });
92
111
  return;
93
112
  }
94
113
  if (input === " ") {
95
- if (isMultiSelect &&
96
- (!isOtherFocused || !selectedOptionIndices.has(selectedOptionIndex))) {
97
- setSelectedOptionIndices((prev) => {
98
- const next = new Set(prev);
99
- if (next.has(selectedOptionIndex))
100
- next.delete(selectedOptionIndex);
114
+ setQuestionState((prev) => {
115
+ const isOtherFocused = prev.selectedOptionIndex === options.length - 1;
116
+ if (isMultiSelect &&
117
+ (!isOtherFocused ||
118
+ !prev.selectedOptionIndices.has(prev.selectedOptionIndex))) {
119
+ const nextIndices = new Set(prev.selectedOptionIndices);
120
+ if (nextIndices.has(prev.selectedOptionIndex))
121
+ nextIndices.delete(prev.selectedOptionIndex);
101
122
  else
102
- next.add(selectedOptionIndex);
103
- return next;
104
- });
105
- return;
106
- }
107
- if (!isOtherFocused)
108
- return;
123
+ nextIndices.add(prev.selectedOptionIndex);
124
+ return {
125
+ ...prev,
126
+ selectedOptionIndices: nextIndices,
127
+ };
128
+ }
129
+ return prev;
130
+ });
131
+ // If it's other and focused, we don't return here, allowing the input handler below to handle it
109
132
  }
110
133
  if (key.upArrow) {
111
- if (selectedOptionIndex > 0)
112
- setSelectedOptionIndex(selectedOptionIndex - 1);
134
+ setQuestionState((prev) => ({
135
+ ...prev,
136
+ selectedOptionIndex: Math.max(0, prev.selectedOptionIndex - 1),
137
+ }));
113
138
  return;
114
139
  }
115
140
  if (key.downArrow) {
116
- if (selectedOptionIndex < options.length - 1)
117
- setSelectedOptionIndex(selectedOptionIndex + 1);
141
+ setQuestionState((prev) => ({
142
+ ...prev,
143
+ selectedOptionIndex: Math.min(options.length - 1, prev.selectedOptionIndex + 1),
144
+ }));
118
145
  return;
119
146
  }
120
- if (isOtherFocused) {
121
- if (key.leftArrow) {
122
- setOtherCursorPosition((prev) => Math.max(0, prev - 1));
123
- return;
124
- }
125
- if (key.rightArrow) {
126
- setOtherCursorPosition((prev) => Math.min(otherText.length, prev + 1));
127
- return;
128
- }
129
- if (key.backspace || key.delete) {
130
- if (otherCursorPosition > 0) {
131
- setOtherText((prev) => prev.slice(0, otherCursorPosition - 1) +
132
- prev.slice(otherCursorPosition));
133
- setOtherCursorPosition((prev) => prev - 1);
147
+ setQuestionState((prev) => {
148
+ const isOtherFocused = prev.selectedOptionIndex === options.length - 1;
149
+ if (isOtherFocused) {
150
+ if (key.leftArrow) {
151
+ return {
152
+ ...prev,
153
+ otherCursorPosition: Math.max(0, prev.otherCursorPosition - 1),
154
+ };
155
+ }
156
+ if (key.rightArrow) {
157
+ return {
158
+ ...prev,
159
+ otherCursorPosition: Math.min(prev.otherText.length, prev.otherCursorPosition + 1),
160
+ };
161
+ }
162
+ if (key.backspace || key.delete) {
163
+ if (prev.otherCursorPosition > 0) {
164
+ return {
165
+ ...prev,
166
+ otherText: prev.otherText.slice(0, prev.otherCursorPosition - 1) +
167
+ prev.otherText.slice(prev.otherCursorPosition),
168
+ otherCursorPosition: prev.otherCursorPosition - 1,
169
+ };
170
+ }
171
+ }
172
+ if (input && !key.ctrl && !key.meta) {
173
+ return {
174
+ ...prev,
175
+ otherText: prev.otherText.slice(0, prev.otherCursorPosition) +
176
+ input +
177
+ prev.otherText.slice(prev.otherCursorPosition),
178
+ otherCursorPosition: prev.otherCursorPosition + input.length,
179
+ };
134
180
  }
135
- return;
136
- }
137
- if (input && !key.ctrl && !key.meta) {
138
- setOtherText((prev) => prev.slice(0, otherCursorPosition) +
139
- input +
140
- prev.slice(otherCursorPosition));
141
- setOtherCursorPosition((prev) => prev + input.length);
142
- return;
143
181
  }
144
- }
182
+ return prev;
183
+ });
145
184
  return;
146
185
  }
147
186
  if (key.return) {
148
- if (state.selectedOption === "allow") {
187
+ if (state.selectedOption === "clear") {
188
+ onDecision({
189
+ behavior: "allow",
190
+ newPermissionMode: "acceptEdits",
191
+ clearContext: true,
192
+ });
193
+ }
194
+ else if (state.selectedOption === "allow") {
149
195
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
150
196
  onDecision({ behavior: "allow", newPermissionMode: "default" });
151
197
  }
@@ -185,30 +231,31 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
185
231
  return;
186
232
  }
187
233
  }
234
+ const availableOptions = [];
235
+ if (toolName === EXIT_PLAN_MODE_TOOL_NAME)
236
+ availableOptions.push("clear");
237
+ availableOptions.push("allow");
238
+ if (!hidePersistentOption)
239
+ availableOptions.push("auto");
240
+ availableOptions.push("alternative");
188
241
  if (key.upArrow) {
189
- setState((prev) => {
190
- if (prev.selectedOption === "alternative")
191
- return {
192
- ...prev,
193
- selectedOption: hidePersistentOption ? "allow" : "auto",
194
- };
195
- if (prev.selectedOption === "auto")
196
- return { ...prev, selectedOption: "allow" };
197
- return prev;
198
- });
242
+ const currentIndex = availableOptions.indexOf(state.selectedOption);
243
+ if (currentIndex > 0) {
244
+ setState((prev) => ({
245
+ ...prev,
246
+ selectedOption: availableOptions[currentIndex - 1],
247
+ }));
248
+ }
199
249
  return;
200
250
  }
201
251
  if (key.downArrow) {
202
- setState((prev) => {
203
- if (prev.selectedOption === "allow")
204
- return {
205
- ...prev,
206
- selectedOption: hidePersistentOption ? "alternative" : "auto",
207
- };
208
- if (prev.selectedOption === "auto")
209
- return { ...prev, selectedOption: "alternative" };
210
- return prev;
211
- });
252
+ const currentIndex = availableOptions.indexOf(state.selectedOption);
253
+ if (currentIndex < availableOptions.length - 1) {
254
+ setState((prev) => ({
255
+ ...prev,
256
+ selectedOption: availableOptions[currentIndex + 1],
257
+ }));
258
+ }
212
259
  return;
213
260
  }
214
261
  if (input && !key.ctrl && !key.meta && !("alt" in key && key.alt)) {
@@ -244,23 +291,23 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
244
291
  return;
245
292
  }
246
293
  });
247
- const placeholderText = "Type here to tell Wave what to do differently";
294
+ const placeholderText = "Type here to tell Wave what to change";
248
295
  const showPlaceholder = state.selectedOption === "alternative" && !state.hasUserInput;
249
- return (_jsxs(Box, { flexDirection: "column", children: [toolName === ASK_USER_QUESTION_TOOL_NAME &&
296
+ return (_jsxs(Box, { ref: boxRef, flexDirection: "column", children: [toolName === ASK_USER_QUESTION_TOOL_NAME &&
250
297
  currentQuestion &&
251
298
  !isExpanded && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: getHeaderColor(currentQuestion.header), bold: true, children: currentQuestion.header.slice(0, 12).toUpperCase() }), _jsx(Box, { marginLeft: 1, children: _jsx(Text, { bold: true, children: currentQuestion.question }) })] }), _jsx(Box, { flexDirection: "column", children: [...currentQuestion.options, { label: "Other" }].map((option, index) => {
252
- const isSelected = selectedOptionIndex === index;
299
+ const isSelected = questionState.selectedOptionIndex === index;
253
300
  const isChecked = currentQuestion.multiSelect
254
- ? selectedOptionIndices.has(index)
301
+ ? questionState.selectedOptionIndices.has(index)
255
302
  : isSelected;
256
303
  const isOther = index === currentQuestion.options.length;
257
304
  return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? "black" : "white", backgroundColor: isSelected ? "yellow" : undefined, children: [isSelected ? "> " : " ", currentQuestion.multiSelect
258
305
  ? isChecked
259
306
  ? "[x] "
260
307
  : "[ ] "
261
- : "", option.label, option.description ? ` - ${option.description}` : "", isOther && isSelected && (_jsxs(Text, { children: [":", " ", otherText ? (_jsxs(_Fragment, { children: [otherText.slice(0, otherCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: otherText[otherCursorPosition] || " " }), otherText.slice(otherCursorPosition + 1)] })) : (_jsx(Text, { color: "gray", dimColor: true, children: "[Type your answer...]" }))] }))] }) }, index));
262
- }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Question ", currentQuestionIndex + 1, " of ", questions.length, " \u2022", currentQuestion.multiSelect ? " Space to toggle •" : "", " Use \u2191\u2193 to navigate \u2022 Enter to confirm"] }) })] })), toolName !== ASK_USER_QUESTION_TOOL_NAME && !isExpanded && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Do you want to proceed?" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "allow" ? "black" : "white", backgroundColor: state.selectedOption === "allow" ? "yellow" : undefined, bold: state.selectedOption === "allow", children: [state.selectedOption === "allow" ? "> " : " ", toolName === EXIT_PLAN_MODE_TOOL_NAME
263
- ? "Yes, proceed with default mode"
264
- : "Yes"] }) }, "allow-option"), !hidePersistentOption && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "auto" ? "black" : "white", backgroundColor: state.selectedOption === "auto" ? "yellow" : undefined, bold: state.selectedOption === "auto", children: [state.selectedOption === "auto" ? "> " : " ", getAutoOptionText()] }) }, "auto-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "alternative" ? "black" : "white", backgroundColor: state.selectedOption === "alternative" ? "yellow" : undefined, bold: state.selectedOption === "alternative", children: [state.selectedOption === "alternative" ? "> " : " ", showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: placeholderText })) : (_jsx(Text, { children: state.alternativeText ? (_jsxs(_Fragment, { children: [state.alternativeText.slice(0, state.alternativeCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: state.alternativeText[state.alternativeCursorPosition] || " " }), state.alternativeText.slice(state.alternativeCursorPosition + 1)] })) : ("Type here to tell Wave what to do differently") }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate \u2022 ESC to cancel" }) })] }))] }));
308
+ : "", option.label, option.description ? ` - ${option.description}` : "", isOther && isSelected && (_jsxs(Text, { children: [":", " ", questionState.otherText ? (_jsxs(_Fragment, { children: [questionState.otherText.slice(0, questionState.otherCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: questionState.otherText[questionState.otherCursorPosition] || " " }), questionState.otherText.slice(questionState.otherCursorPosition + 1)] })) : (_jsx(Text, { color: "gray", dimColor: true, children: "[Type your answer...]" }))] }))] }) }, index));
309
+ }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Question ", questionState.currentQuestionIndex + 1, " of", " ", questions.length, " \u2022", currentQuestion.multiSelect ? " Space to toggle •" : "", " Use \u2191\u2193 to navigate \u2022 Enter to confirm"] }) })] })), toolName !== ASK_USER_QUESTION_TOOL_NAME && !isExpanded && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Do you want to proceed?" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [toolName === EXIT_PLAN_MODE_TOOL_NAME && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "clear" ? "black" : "white", backgroundColor: state.selectedOption === "clear" ? "yellow" : undefined, bold: state.selectedOption === "clear", children: [state.selectedOption === "clear" ? "> " : " ", "Yes, clear context and auto-accept edits"] }) }, "clear-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "allow" ? "black" : "white", backgroundColor: state.selectedOption === "allow" ? "yellow" : undefined, bold: state.selectedOption === "allow", children: [state.selectedOption === "allow" ? "> " : " ", toolName === EXIT_PLAN_MODE_TOOL_NAME
310
+ ? "Yes, manually approve edits"
311
+ : "Yes, proceed"] }) }, "allow-option"), !hidePersistentOption && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "auto" ? "black" : "white", backgroundColor: state.selectedOption === "auto" ? "yellow" : undefined, bold: state.selectedOption === "auto", children: [state.selectedOption === "auto" ? "> " : " ", getAutoOptionText()] }) }, "auto-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "alternative" ? "black" : "white", backgroundColor: state.selectedOption === "alternative" ? "yellow" : undefined, bold: state.selectedOption === "alternative", children: [state.selectedOption === "alternative" ? "> " : " ", showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: placeholderText })) : (_jsx(Text, { children: state.alternativeText ? (_jsxs(_Fragment, { children: [state.alternativeText.slice(0, state.alternativeCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: state.alternativeText[state.alternativeCursorPosition] || " " }), state.alternativeText.slice(state.alternativeCursorPosition + 1)] })) : ("Type here to tell Wave what to change") }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate \u2022 ESC to cancel" }) })] }))] }));
265
312
  };
266
313
  ConfirmationSelector.displayName = "ConfirmationSelector";
@@ -2,6 +2,7 @@ import React from "react";
2
2
  interface DiffDisplayProps {
3
3
  toolName?: string;
4
4
  parameters?: string;
5
+ startLineNumber?: number;
5
6
  }
6
7
  export declare const DiffDisplay: React.FC<DiffDisplayProps>;
7
8
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"DiffDisplay.d.ts","sourceRoot":"","sources":["../../src/components/DiffDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAUvC,UAAU,gBAAgB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAkTlD,CAAC"}
1
+ {"version":3,"file":"DiffDisplay.d.ts","sourceRoot":"","sources":["../../src/components/DiffDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAMvC,UAAU,gBAAgB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA8VlD,CAAC"}
@@ -1,25 +1,24 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useMemo } from "react";
3
3
  import { Box, Text } from "ink";
4
- import { WRITE_TOOL_NAME, EDIT_TOOL_NAME, MULTI_EDIT_TOOL_NAME, } from "wave-agent-sdk";
4
+ import { WRITE_TOOL_NAME, EDIT_TOOL_NAME } from "wave-agent-sdk";
5
5
  import { transformToolBlockToChanges } from "../utils/toolParameterTransforms.js";
6
6
  import { diffLines, diffWords } from "diff";
7
- export const DiffDisplay = ({ toolName, parameters, }) => {
8
- const showDiff = toolName &&
9
- [WRITE_TOOL_NAME, EDIT_TOOL_NAME, MULTI_EDIT_TOOL_NAME].includes(toolName);
7
+ export const DiffDisplay = ({ toolName, parameters, startLineNumber, }) => {
8
+ const showDiff = toolName && [WRITE_TOOL_NAME, EDIT_TOOL_NAME].includes(toolName);
10
9
  // Diff detection and transformation using typed parameters
11
10
  const changes = useMemo(() => {
12
11
  if (!showDiff || !toolName || !parameters)
13
12
  return [];
14
13
  try {
15
14
  // Use local transformation with JSON parsing and type guards
16
- return transformToolBlockToChanges(toolName, parameters);
15
+ return transformToolBlockToChanges(toolName, parameters, startLineNumber);
17
16
  }
18
17
  catch (error) {
19
18
  console.warn("Error transforming tool block to changes:", error);
20
19
  return [];
21
20
  }
22
- }, [toolName, parameters, showDiff]);
21
+ }, [toolName, parameters, showDiff, startLineNumber]);
23
22
  // Render word-level diff between two lines of text
24
23
  const renderWordLevelDiff = (oldLine, newLine, keyPrefix) => {
25
24
  try {
@@ -59,39 +58,51 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
59
58
  try {
60
59
  if (changes.length === 0)
61
60
  return null;
61
+ const maxLineNum = changes.reduce((max, change) => {
62
+ const oldLines = (change.oldContent || "").split("\n").length;
63
+ const newLines = (change.newContent || "").split("\n").length;
64
+ const start = change.startLineNumber || 1;
65
+ // For Edit tool, the diff might show context lines before/after the change.
66
+ // The startLineNumber is the line where old_string starts.
67
+ // diffLines will include context lines if they are part of the change object.
68
+ // However, our transformEditParameters currently only puts old_string/new_string.
69
+ // If we ever support context lines in the Change object, we need to be careful.
70
+ return Math.max(max, start + oldLines, start + newLines);
71
+ }, 0);
72
+ const maxDigits = Math.max(2, maxLineNum.toString().length);
73
+ const renderLine = (oldLineNum, newLineNum, prefix, content, color, key) => {
74
+ const formatNum = (num) => num === null
75
+ ? " ".repeat(maxDigits)
76
+ : num.toString().padStart(maxDigits);
77
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: "gray", children: [formatNum(oldLineNum), " "] }), _jsxs(Text, { color: "gray", children: [formatNum(newLineNum), " "] }), _jsx(Text, { color: "gray", children: "| " }), _jsx(Text, { color: color, children: prefix }), _jsx(Text, { color: color, children: content })] }, key));
78
+ };
62
79
  const allElements = [];
63
80
  changes.forEach((change, changeIndex) => {
64
81
  try {
65
- // Add ellipsis between non-contiguous edits in MultiEdit
66
- if (toolName === MULTI_EDIT_TOOL_NAME && changeIndex > 0) {
67
- allElements.push(_jsx(Box, { children: _jsx(Text, { color: "gray", children: "..." }) }, `multi-edit-separator-${changeIndex}`));
68
- }
69
82
  // Get line-level diff to understand the structure
70
83
  const lineDiffs = diffLines(change.oldContent || "", change.newContent || "");
84
+ let oldLineNum = change.startLineNumber || 1;
85
+ let newLineNum = change.startLineNumber || 1;
71
86
  // Process line diffs
72
87
  const diffElements = [];
73
88
  lineDiffs.forEach((part, partIndex) => {
89
+ const lines = part.value.split("\n");
90
+ // diffLines might return a trailing empty string if the content ends with a newline
91
+ if (lines[lines.length - 1] === "") {
92
+ lines.pop();
93
+ }
74
94
  if (part.added) {
75
- const lines = part.value
76
- .split("\n")
77
- .filter((line) => line !== "");
78
95
  lines.forEach((line, lineIndex) => {
79
- diffElements.push(_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "green", children: "+" }), _jsx(Text, { color: "green", children: line })] }, `add-${changeIndex}-${partIndex}-${lineIndex}`));
96
+ diffElements.push(renderLine(null, newLineNum++, "+", line, "green", `add-${changeIndex}-${partIndex}-${lineIndex}`));
80
97
  });
81
98
  }
82
99
  else if (part.removed) {
83
- const lines = part.value
84
- .split("\n")
85
- .filter((line) => line !== "");
86
100
  lines.forEach((line, lineIndex) => {
87
- diffElements.push(_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "red", children: "-" }), _jsx(Text, { color: "red", children: line })] }, `remove-${changeIndex}-${partIndex}-${lineIndex}`));
101
+ diffElements.push(renderLine(oldLineNum++, null, "-", line, "red", `remove-${changeIndex}-${partIndex}-${lineIndex}`));
88
102
  });
89
103
  }
90
104
  else {
91
105
  // Context lines - show unchanged content
92
- const lines = part.value
93
- .split("\n")
94
- .filter((line) => line !== "");
95
106
  const isFirstBlock = partIndex === 0;
96
107
  const isLastBlock = partIndex === lineDiffs.length - 1;
97
108
  let linesToDisplay = lines;
@@ -100,6 +111,9 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
100
111
  if (isFirstBlock && !isLastBlock) {
101
112
  // First block: keep last 3
102
113
  if (lines.length > 3) {
114
+ const skipCount = lines.length - 3;
115
+ oldLineNum += skipCount;
116
+ newLineNum += skipCount;
103
117
  linesToDisplay = lines.slice(-3);
104
118
  showEllipsisTop = true;
105
119
  }
@@ -118,12 +132,8 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
118
132
  showEllipsisTop = false; // We'll put ellipsis in the middle
119
133
  }
120
134
  }
121
- else if (isFirstBlock && isLastBlock) {
122
- // Only one block (no changes?) - keep all or apply a general limit
123
- // For now, let's keep all if it's the only block
124
- }
125
135
  if (showEllipsisTop) {
126
- diffElements.push(_jsx(Box, { children: _jsx(Text, { color: "gray", children: " ..." }) }, `ellipsis-top-${changeIndex}-${partIndex}`));
136
+ diffElements.push(_jsx(Box, { children: _jsxs(Text, { color: "gray", children: [" ".repeat(maxDigits * 2 + 2), "..."] }) }, `ellipsis-top-${changeIndex}-${partIndex}`));
127
137
  }
128
138
  linesToDisplay.forEach((line, lineIndex) => {
129
139
  // If it's a middle block and we are at the split point
@@ -131,12 +141,20 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
131
141
  !isLastBlock &&
132
142
  lines.length > 6 &&
133
143
  lineIndex === 3) {
134
- diffElements.push(_jsx(Box, { children: _jsx(Text, { color: "gray", children: " ..." }) }, `ellipsis-mid-${changeIndex}-${partIndex}`));
144
+ const skipCount = lines.length - 6;
145
+ oldLineNum += skipCount;
146
+ newLineNum += skipCount;
147
+ diffElements.push(_jsx(Box, { children: _jsxs(Text, { color: "gray", children: [" ".repeat(maxDigits * 2 + 2), "..."] }) }, `ellipsis-mid-${changeIndex}-${partIndex}`));
135
148
  }
136
- diffElements.push(_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "white", children: " " }), _jsx(Text, { color: "white", children: line })] }, `context-${changeIndex}-${partIndex}-${lineIndex}`));
149
+ diffElements.push(renderLine(oldLineNum++, newLineNum++, " ", line, "white", `context-${changeIndex}-${partIndex}-${lineIndex}`));
137
150
  });
138
151
  if (showEllipsisBottom) {
139
- diffElements.push(_jsx(Box, { children: _jsx(Text, { color: "gray", children: " ..." }) }, `ellipsis-bottom-${changeIndex}-${partIndex}`));
152
+ const skipCount = lines.length - linesToDisplay.length;
153
+ // We don't increment oldLineNum/newLineNum here because they are already incremented in the loop
154
+ // But we need to account for the lines we skipped at the end of this block
155
+ oldLineNum += skipCount;
156
+ newLineNum += skipCount;
157
+ diffElements.push(_jsx(Box, { children: _jsxs(Text, { color: "gray", children: [" ".repeat(maxDigits * 2 + 2), "..."] }) }, `ellipsis-bottom-${changeIndex}-${partIndex}`));
140
158
  }
141
159
  }
142
160
  });
@@ -150,10 +168,12 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
150
168
  diffElements[1].key.includes("add-")) {
151
169
  const removedText = extractTextFromElement(diffElements[0]);
152
170
  const addedText = extractTextFromElement(diffElements[1]);
171
+ const oldLineNumVal = extractOldLineNumFromElement(diffElements[0]);
172
+ const newLineNumVal = extractNewLineNumFromElement(diffElements[1]);
153
173
  if (removedText && addedText) {
154
174
  const { removedParts, addedParts } = renderWordLevelDiff(removedText, addedText, `word-${changeIndex}`);
155
- allElements.push(_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "red", children: "-" }), removedParts] }, `word-diff-removed-${changeIndex}`));
156
- allElements.push(_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "green", children: "+" }), addedParts] }, `word-diff-added-${changeIndex}`));
175
+ allElements.push(renderLine(oldLineNumVal, null, "-", removedParts, "red", `word-diff-removed-${changeIndex}`));
176
+ allElements.push(renderLine(null, newLineNumVal, "+", addedParts, "green", `word-diff-added-${changeIndex}`));
157
177
  }
158
178
  else {
159
179
  allElements.push(...diffElements);
@@ -180,19 +200,57 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
180
200
  if (!showDiff) {
181
201
  return null;
182
202
  }
183
- return (_jsx(Box, { flexDirection: "column", children: _jsxs(Box, { paddingLeft: 2, borderLeft: true, borderColor: "cyan", flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Diff:" }), renderExpandedDiff()] }) }));
203
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(Box, { paddingLeft: 2, borderLeft: true, borderColor: "cyan", flexDirection: "column", children: renderExpandedDiff() }) }));
184
204
  };
185
205
  // Helper function to extract text content from a React element
186
206
  const extractTextFromElement = (element) => {
187
207
  if (!React.isValidElement(element))
188
208
  return null;
189
209
  // Navigate through Box -> Text structure
210
+ // Our new structure is: Box -> Text (old), Text (new), Text (|), Text (prefix), Text (content)
211
+ const children = element.props.children;
212
+ if (Array.isArray(children) && children.length >= 5) {
213
+ const textElement = children[4]; // Fifth child should be the Text with content
214
+ if (React.isValidElement(textElement)) {
215
+ const textChildren = textElement.props
216
+ .children;
217
+ return Array.isArray(textChildren)
218
+ ? textChildren.join("")
219
+ : String(textChildren || "");
220
+ }
221
+ }
222
+ return null;
223
+ };
224
+ const extractOldLineNumFromElement = (element) => {
225
+ if (!React.isValidElement(element))
226
+ return null;
227
+ const children = element.props.children;
228
+ if (Array.isArray(children) && children.length >= 1) {
229
+ const textElement = children[0];
230
+ if (React.isValidElement(textElement)) {
231
+ const textChildren = textElement.props
232
+ .children;
233
+ const val = (Array.isArray(textChildren)
234
+ ? textChildren.join("")
235
+ : String(textChildren || "")).trim();
236
+ return val ? parseInt(val, 10) : null;
237
+ }
238
+ }
239
+ return null;
240
+ };
241
+ const extractNewLineNumFromElement = (element) => {
242
+ if (!React.isValidElement(element))
243
+ return null;
190
244
  const children = element.props.children;
191
245
  if (Array.isArray(children) && children.length >= 2) {
192
- const textElement = children[1]; // Second child should be the Text with content
193
- if (React.isValidElement(textElement) &&
194
- textElement.props.children) {
195
- return textElement.props.children;
246
+ const textElement = children[1];
247
+ if (React.isValidElement(textElement)) {
248
+ const textChildren = textElement.props
249
+ .children;
250
+ const val = (Array.isArray(textChildren)
251
+ ? textChildren.join("")
252
+ : String(textChildren || "")).trim();
253
+ return val ? parseInt(val, 10) : null;
196
254
  }
197
255
  }
198
256
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"HistorySearch.d.ts","sourceRoot":"","sources":["../../src/components/HistorySearch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAiKtD,CAAC"}
1
+ {"version":3,"file":"HistorySearch.d.ts","sourceRoot":"","sources":["../../src/components/HistorySearch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAuKtD,CAAC"}