wave-code 0.7.0 → 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 (63) hide show
  1. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  2. package/dist/components/BackgroundTaskManager.js +20 -12
  3. package/dist/components/ChatInterface.d.ts.map +1 -1
  4. package/dist/components/ChatInterface.js +28 -15
  5. package/dist/components/ConfirmationDetails.d.ts +1 -0
  6. package/dist/components/ConfirmationDetails.d.ts.map +1 -1
  7. package/dist/components/ConfirmationDetails.js +6 -9
  8. package/dist/components/ConfirmationSelector.d.ts +1 -0
  9. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  10. package/dist/components/ConfirmationSelector.js +164 -117
  11. package/dist/components/DiffDisplay.d.ts +1 -0
  12. package/dist/components/DiffDisplay.d.ts.map +1 -1
  13. package/dist/components/DiffDisplay.js +92 -29
  14. package/dist/components/HistorySearch.d.ts.map +1 -1
  15. package/dist/components/HistorySearch.js +26 -20
  16. package/dist/components/Markdown.d.ts.map +1 -1
  17. package/dist/components/Markdown.js +3 -1
  18. package/dist/components/McpManager.d.ts.map +1 -1
  19. package/dist/components/McpManager.js +49 -52
  20. package/dist/components/MessageBlockItem.d.ts +9 -0
  21. package/dist/components/MessageBlockItem.d.ts.map +1 -0
  22. package/dist/components/MessageBlockItem.js +11 -0
  23. package/dist/components/MessageList.d.ts +2 -4
  24. package/dist/components/MessageList.d.ts.map +1 -1
  25. package/dist/components/MessageList.js +28 -23
  26. package/dist/components/PluginDetail.d.ts.map +1 -1
  27. package/dist/components/PluginDetail.js +19 -22
  28. package/dist/components/SessionSelector.d.ts.map +1 -1
  29. package/dist/components/SessionSelector.js +8 -5
  30. package/dist/components/TaskList.d.ts.map +1 -1
  31. package/dist/components/TaskList.js +2 -5
  32. package/dist/components/ToolDisplay.d.ts.map +1 -1
  33. package/dist/components/ToolDisplay.js +1 -1
  34. package/dist/contexts/useChat.d.ts.map +1 -1
  35. package/dist/contexts/useChat.js +17 -2
  36. package/dist/utils/highlightUtils.d.ts +2 -0
  37. package/dist/utils/highlightUtils.d.ts.map +1 -0
  38. package/dist/utils/highlightUtils.js +69 -0
  39. package/dist/utils/toolParameterTransforms.d.ts +1 -1
  40. package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
  41. package/dist/utils/toolParameterTransforms.js +10 -3
  42. package/package.json +4 -2
  43. package/src/components/BackgroundTaskManager.tsx +20 -12
  44. package/src/components/ChatInterface.tsx +35 -23
  45. package/src/components/ConfirmationDetails.tsx +13 -9
  46. package/src/components/ConfirmationSelector.tsx +207 -128
  47. package/src/components/DiffDisplay.tsx +162 -59
  48. package/src/components/HistorySearch.tsx +31 -25
  49. package/src/components/Markdown.tsx +3 -1
  50. package/src/components/McpManager.tsx +51 -59
  51. package/src/components/MessageBlockItem.tsx +83 -0
  52. package/src/components/MessageList.tsx +55 -52
  53. package/src/components/PluginDetail.tsx +30 -31
  54. package/src/components/SessionSelector.tsx +8 -5
  55. package/src/components/TaskList.tsx +2 -5
  56. package/src/components/ToolDisplay.tsx +5 -1
  57. package/src/contexts/useChat.tsx +18 -2
  58. package/src/utils/highlightUtils.ts +76 -0
  59. package/src/utils/toolParameterTransforms.ts +11 -2
  60. package/dist/components/MessageItem.d.ts +0 -8
  61. package/dist/components/MessageItem.d.ts.map +0 -1
  62. package/dist/components/MessageItem.js +0 -13
  63. package/src/components/MessageItem.tsx +0 -81
@@ -4,7 +4,7 @@ import { Box, Text } from "ink";
4
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, }) => {
7
+ export const DiffDisplay = ({ toolName, parameters, startLineNumber, }) => {
8
8
  const showDiff = toolName && [WRITE_TOOL_NAME, EDIT_TOOL_NAME].includes(toolName);
9
9
  // Diff detection and transformation using typed parameters
10
10
  const changes = useMemo(() => {
@@ -12,13 +12,13 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
12
12
  return [];
13
13
  try {
14
14
  // Use local transformation with JSON parsing and type guards
15
- return transformToolBlockToChanges(toolName, parameters);
15
+ return transformToolBlockToChanges(toolName, parameters, startLineNumber);
16
16
  }
17
17
  catch (error) {
18
18
  console.warn("Error transforming tool block to changes:", error);
19
19
  return [];
20
20
  }
21
- }, [toolName, parameters, showDiff]);
21
+ }, [toolName, parameters, showDiff, startLineNumber]);
22
22
  // Render word-level diff between two lines of text
23
23
  const renderWordLevelDiff = (oldLine, newLine, keyPrefix) => {
24
24
  try {
@@ -58,35 +58,51 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
58
58
  try {
59
59
  if (changes.length === 0)
60
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
+ };
61
79
  const allElements = [];
62
80
  changes.forEach((change, changeIndex) => {
63
81
  try {
64
82
  // Get line-level diff to understand the structure
65
83
  const lineDiffs = diffLines(change.oldContent || "", change.newContent || "");
84
+ let oldLineNum = change.startLineNumber || 1;
85
+ let newLineNum = change.startLineNumber || 1;
66
86
  // Process line diffs
67
87
  const diffElements = [];
68
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
+ }
69
94
  if (part.added) {
70
- const lines = part.value
71
- .split("\n")
72
- .filter((line) => line !== "");
73
95
  lines.forEach((line, lineIndex) => {
74
- 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}`));
75
97
  });
76
98
  }
77
99
  else if (part.removed) {
78
- const lines = part.value
79
- .split("\n")
80
- .filter((line) => line !== "");
81
100
  lines.forEach((line, lineIndex) => {
82
- 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}`));
83
102
  });
84
103
  }
85
104
  else {
86
105
  // Context lines - show unchanged content
87
- const lines = part.value
88
- .split("\n")
89
- .filter((line) => line !== "");
90
106
  const isFirstBlock = partIndex === 0;
91
107
  const isLastBlock = partIndex === lineDiffs.length - 1;
92
108
  let linesToDisplay = lines;
@@ -95,6 +111,9 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
95
111
  if (isFirstBlock && !isLastBlock) {
96
112
  // First block: keep last 3
97
113
  if (lines.length > 3) {
114
+ const skipCount = lines.length - 3;
115
+ oldLineNum += skipCount;
116
+ newLineNum += skipCount;
98
117
  linesToDisplay = lines.slice(-3);
99
118
  showEllipsisTop = true;
100
119
  }
@@ -113,12 +132,8 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
113
132
  showEllipsisTop = false; // We'll put ellipsis in the middle
114
133
  }
115
134
  }
116
- else if (isFirstBlock && isLastBlock) {
117
- // Only one block (no changes?) - keep all or apply a general limit
118
- // For now, let's keep all if it's the only block
119
- }
120
135
  if (showEllipsisTop) {
121
- 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}`));
122
137
  }
123
138
  linesToDisplay.forEach((line, lineIndex) => {
124
139
  // If it's a middle block and we are at the split point
@@ -126,12 +141,20 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
126
141
  !isLastBlock &&
127
142
  lines.length > 6 &&
128
143
  lineIndex === 3) {
129
- 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}`));
130
148
  }
131
- 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}`));
132
150
  });
133
151
  if (showEllipsisBottom) {
134
- 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}`));
135
158
  }
136
159
  }
137
160
  });
@@ -145,10 +168,12 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
145
168
  diffElements[1].key.includes("add-")) {
146
169
  const removedText = extractTextFromElement(diffElements[0]);
147
170
  const addedText = extractTextFromElement(diffElements[1]);
171
+ const oldLineNumVal = extractOldLineNumFromElement(diffElements[0]);
172
+ const newLineNumVal = extractNewLineNumFromElement(diffElements[1]);
148
173
  if (removedText && addedText) {
149
174
  const { removedParts, addedParts } = renderWordLevelDiff(removedText, addedText, `word-${changeIndex}`);
150
- allElements.push(_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "red", children: "-" }), removedParts] }, `word-diff-removed-${changeIndex}`));
151
- 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}`));
152
177
  }
153
178
  else {
154
179
  allElements.push(...diffElements);
@@ -175,19 +200,57 @@ export const DiffDisplay = ({ toolName, parameters, }) => {
175
200
  if (!showDiff) {
176
201
  return null;
177
202
  }
178
- 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() }) }));
179
204
  };
180
205
  // Helper function to extract text content from a React element
181
206
  const extractTextFromElement = (element) => {
182
207
  if (!React.isValidElement(element))
183
208
  return null;
184
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;
185
244
  const children = element.props.children;
186
245
  if (Array.isArray(children) && children.length >= 2) {
187
- const textElement = children[1]; // Second child should be the Text with content
188
- if (React.isValidElement(textElement) &&
189
- textElement.props.children) {
190
- 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;
191
254
  }
192
255
  }
193
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"}
@@ -1,34 +1,33 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import React, { useState, useEffect } from "react";
2
+ import { useState, useEffect } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { PromptHistoryManager } from "wave-agent-sdk";
5
5
  export const HistorySearch = ({ searchQuery, onSelect, onCancel, }) => {
6
6
  const MAX_VISIBLE_ITEMS = 5;
7
- const [selectedIndex, setSelectedIndex] = useState(0);
8
- const [entries, setEntries] = useState([]);
9
- const entriesRef = React.useRef([]);
10
- const selectedIndexRef = React.useRef(0);
11
- useEffect(() => {
12
- entriesRef.current = entries;
13
- }, [entries]);
14
- useEffect(() => {
15
- selectedIndexRef.current = selectedIndex;
16
- }, [selectedIndex]);
7
+ const [state, setState] = useState({
8
+ selectedIndex: 0,
9
+ entries: [],
10
+ });
17
11
  useEffect(() => {
18
12
  const fetchHistory = async () => {
19
13
  const results = await PromptHistoryManager.searchHistory(searchQuery);
20
14
  const limitedResults = results.slice(0, 20);
21
- setEntries(limitedResults); // Limit to 20 results
22
- setSelectedIndex(0);
15
+ setState({
16
+ entries: limitedResults,
17
+ selectedIndex: 0,
18
+ });
23
19
  };
24
20
  fetchHistory();
25
21
  }, [searchQuery]);
26
22
  useInput((input, key) => {
27
23
  if (key.return) {
28
- if (entriesRef.current.length > 0 &&
29
- selectedIndexRef.current < entriesRef.current.length) {
30
- onSelect(entriesRef.current[selectedIndexRef.current].prompt);
31
- }
24
+ setState((prev) => {
25
+ if (prev.entries.length > 0 &&
26
+ prev.selectedIndex < prev.entries.length) {
27
+ onSelect(prev.entries[prev.selectedIndex].prompt);
28
+ }
29
+ return prev;
30
+ });
32
31
  return;
33
32
  }
34
33
  if (key.escape) {
@@ -36,14 +35,21 @@ export const HistorySearch = ({ searchQuery, onSelect, onCancel, }) => {
36
35
  return;
37
36
  }
38
37
  if (key.upArrow) {
39
- setSelectedIndex((prev) => Math.max(0, prev - 1));
38
+ setState((prev) => ({
39
+ ...prev,
40
+ selectedIndex: Math.max(0, prev.selectedIndex - 1),
41
+ }));
40
42
  return;
41
43
  }
42
44
  if (key.downArrow) {
43
- setSelectedIndex((prev) => Math.min(entriesRef.current.length - 1, prev + 1));
45
+ setState((prev) => ({
46
+ ...prev,
47
+ selectedIndex: Math.min(prev.entries.length - 1, prev.selectedIndex + 1),
48
+ }));
44
49
  return;
45
50
  }
46
51
  });
52
+ const { entries, selectedIndex } = state;
47
53
  if (entries.length === 0) {
48
54
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", borderBottom: false, borderLeft: false, borderRight: false, children: [_jsxs(Text, { color: "yellow", children: ["No history found ", searchQuery && `for "${searchQuery}"`] }), _jsx(Text, { dimColor: true, children: "Press Escape to cancel" })] }));
49
55
  }
@@ -70,6 +76,6 @@ export const HistorySearch = ({ searchQuery, onSelect, onCancel, }) => {
70
76
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "blue", borderBottom: false, borderLeft: false, borderRight: false, gap: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "blue", bold: true, children: ["Prompt History ", searchQuery && `(filtering: "${searchQuery}")`] }) }), _jsx(Box, { flexDirection: "column", children: visibleEntries.map((entry, index) => {
71
77
  const actualIndex = startIndex + index;
72
78
  const isSelected = actualIndex === selectedIndex;
73
- return (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Box, { flexShrink: 1, children: _jsx(Text, { color: isSelected ? "black" : "white", backgroundColor: isSelected ? "blue" : undefined, wrap: "truncate-end", children: entry.prompt.replace(/\n/g, " ") }) }), isSelected && (_jsx(Box, { marginLeft: 2, flexShrink: 0, children: _jsx(Text, { color: "gray", dimColor: true, children: formatTimestamp(entry.timestamp) }) }))] }, actualIndex));
79
+ return (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Box, { flexShrink: 1, children: _jsxs(Text, { color: isSelected ? "black" : "white", backgroundColor: isSelected ? "blue" : undefined, wrap: "truncate-end", children: [isSelected ? "> " : " ", entry.prompt.replace(/\n/g, " ")] }) }), isSelected && (_jsx(Box, { marginLeft: 2, flexShrink: 0, children: _jsx(Text, { color: "gray", dimColor: true, children: formatTimestamp(entry.timestamp) }) }))] }, actualIndex));
74
80
  }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate, Enter to select, Escape to cancel" }) })] }));
75
81
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../src/components/Markdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAKvC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAwID,eAAO,MAAM,QAAQ,2CAA6B,aAAa,6CAc7D,CAAC"}
1
+ {"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../src/components/Markdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAMvC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAyID,eAAO,MAAM,QAAQ,2CAA6B,aAAa,6CAc7D,CAAC"}
@@ -3,6 +3,7 @@ import React, { useMemo } from "react";
3
3
  import { Box, Text } from "ink";
4
4
  import { Renderer, marked } from "marked";
5
5
  import chalk from "chalk";
6
+ import { highlightToAnsi } from "../utils/highlightUtils.js";
6
7
  const unescapeHtml = (html) => {
7
8
  return html
8
9
  .replace(/&amp;/g, "&")
@@ -16,7 +17,8 @@ class AnsiRenderer extends Renderer {
16
17
  code({ text, lang }) {
17
18
  const prefix = lang ? `\`\`\`${lang}` : "```";
18
19
  const suffix = "```";
19
- return `\n${chalk.gray(prefix)}\n${text}\n${chalk.gray(suffix)}\n`;
20
+ const highlighted = highlightToAnsi(text, lang);
21
+ return `\n${chalk.gray(prefix)}\n${highlighted}\n${chalk.gray(suffix)}\n`;
20
22
  }
21
23
  blockquote({ tokens }) {
22
24
  const body = this.parser.parse(tokens);
@@ -1 +1 @@
1
- {"version":3,"file":"McpManager.d.ts","sourceRoot":"","sources":["../../src/components/McpManager.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAkUhD,CAAC"}
1
+ {"version":3,"file":"McpManager.d.ts","sourceRoot":"","sources":["../../src/components/McpManager.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA0ThD,CAAC"}
@@ -44,64 +44,61 @@ export const McpManager = ({ onCancel, servers, onConnectServer, onDisconnectSer
44
44
  await onDisconnectServer(serverName);
45
45
  };
46
46
  useInput((input, key) => {
47
- if (viewMode === "list") {
48
- // List mode navigation
49
- if (key.return) {
50
- if (servers.length > 0 && selectedIndex < servers.length) {
51
- setViewMode("detail");
47
+ if (key.return) {
48
+ setViewMode((prevMode) => {
49
+ if (prevMode === "list") {
50
+ setSelectedIndex((prevIndex) => {
51
+ if (servers.length > 0 && prevIndex < servers.length) {
52
+ // We can't call setViewMode here because we're already in a setViewMode call
53
+ // But we can return the new mode from the outer setViewMode
54
+ }
55
+ return prevIndex;
56
+ });
57
+ return "detail";
58
+ }
59
+ return prevMode;
60
+ });
61
+ return;
62
+ }
63
+ if (key.escape) {
64
+ setViewMode((prev) => {
65
+ if (prev === "detail") {
66
+ return "list";
52
67
  }
53
- return;
54
- }
55
- if (key.escape) {
56
68
  onCancel();
57
- return;
58
- }
59
- if (key.upArrow) {
60
- setSelectedIndex(Math.max(0, selectedIndex - 1));
61
- return;
62
- }
63
- if (key.downArrow) {
64
- setSelectedIndex(Math.min(servers.length - 1, selectedIndex + 1));
65
- return;
66
- }
67
- // Hotkeys for server actions
68
- if (input === "c" &&
69
- servers.length > 0 &&
70
- selectedIndex < servers.length) {
71
- const server = servers[selectedIndex];
72
- if (server.status === "disconnected" || server.status === "error") {
69
+ return prev;
70
+ });
71
+ return;
72
+ }
73
+ if (key.upArrow) {
74
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
75
+ return;
76
+ }
77
+ if (key.downArrow) {
78
+ setSelectedIndex((prev) => Math.min(servers.length - 1, prev + 1));
79
+ return;
80
+ }
81
+ // Hotkeys for server actions
82
+ if (input === "c") {
83
+ setSelectedIndex((prev) => {
84
+ const server = servers[prev];
85
+ if (server &&
86
+ (server.status === "disconnected" || server.status === "error")) {
73
87
  handleConnect(server.name);
74
88
  }
75
- return;
76
- }
77
- if (input === "d" &&
78
- servers.length > 0 &&
79
- selectedIndex < servers.length) {
80
- const server = servers[selectedIndex];
81
- if (server.status === "connected") {
82
- handleDisconnect(server.name);
83
- }
84
- return;
85
- }
89
+ return prev;
90
+ });
91
+ return;
86
92
  }
87
- else if (viewMode === "detail") {
88
- // Detail mode navigation
89
- if (key.escape) {
90
- setViewMode("list");
91
- return;
92
- }
93
- if (selectedServer) {
94
- if (input === "c" &&
95
- (selectedServer.status === "disconnected" ||
96
- selectedServer.status === "error")) {
97
- handleConnect(selectedServer.name);
98
- return;
99
- }
100
- if (input === "d" && selectedServer.status === "connected") {
101
- handleDisconnect(selectedServer.name);
102
- return;
93
+ if (input === "d") {
94
+ setSelectedIndex((prev) => {
95
+ const server = servers[prev];
96
+ if (server && server.status === "connected") {
97
+ handleDisconnect(server.name);
103
98
  }
104
- }
99
+ return prev;
100
+ });
101
+ return;
105
102
  }
106
103
  });
107
104
  if (viewMode === "detail" && selectedServer) {
@@ -0,0 +1,9 @@
1
+ import type { Message, MessageBlock } from "wave-agent-sdk";
2
+ export interface MessageBlockItemProps {
3
+ block: MessageBlock;
4
+ message: Message;
5
+ isExpanded: boolean;
6
+ paddingTop?: number;
7
+ }
8
+ export declare const MessageBlockItem: ({ block, message, isExpanded, paddingTop, }: MessageBlockItemProps) => import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=MessageBlockItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageBlockItem.d.ts","sourceRoot":"","sources":["../../src/components/MessageBlockItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQ5D,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,gBAAgB,GAAI,6CAK9B,qBAAqB,4CA4DvB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { MessageSource } from "wave-agent-sdk";
4
+ import { CommandOutputDisplay } from "./CommandOutputDisplay.js";
5
+ import { ToolDisplay } from "./ToolDisplay.js";
6
+ import { CompressDisplay } from "./CompressDisplay.js";
7
+ import { ReasoningDisplay } from "./ReasoningDisplay.js";
8
+ import { Markdown } from "./Markdown.js";
9
+ export const MessageBlockItem = ({ block, message, isExpanded, paddingTop = 0, }) => {
10
+ return (_jsxs(Box, { flexDirection: "column", paddingTop: paddingTop, children: [block.type === "text" && block.content.trim() && (_jsxs(Box, { children: [block.customCommandContent && (_jsxs(Text, { color: "cyan", bold: true, children: ["$", " "] })), block.source === MessageSource.HOOK && (_jsxs(Text, { color: "magenta", bold: true, children: ["~", " "] })), message.role === "user" ? (_jsx(Text, { backgroundColor: "gray", color: "white", children: block.content })) : (_jsx(Markdown, { children: block.content }))] })), block.type === "error" && (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", block.content] }) })), block.type === "command_output" && (_jsx(CommandOutputDisplay, { block: block, isExpanded: isExpanded })), block.type === "tool" && (_jsx(ToolDisplay, { block: block, isExpanded: isExpanded })), block.type === "image" && (_jsxs(Box, { children: [_jsx(Text, { color: "magenta", bold: true, children: "# Image" }), block.imageUrls && block.imageUrls.length > 0 && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", "(", block.imageUrls.length, ")"] }))] })), block.type === "compress" && (_jsx(CompressDisplay, { block: block, isExpanded: isExpanded })), block.type === "reasoning" && _jsx(ReasoningDisplay, { block: block })] }));
11
+ };
@@ -2,10 +2,8 @@ import React from "react";
2
2
  import type { Message } from "wave-agent-sdk";
3
3
  export interface MessageListProps {
4
4
  messages: Message[];
5
- isLoading?: boolean;
6
- isCommandRunning?: boolean;
7
5
  isExpanded?: boolean;
8
- forceStaticLastMessage?: boolean;
6
+ hideDynamicBlocks?: boolean;
9
7
  }
10
- export declare const MessageList: React.MemoExoticComponent<({ messages, isLoading, isCommandRunning, isExpanded, forceStaticLastMessage, }: MessageListProps) => import("react/jsx-runtime").JSX.Element>;
8
+ export declare const MessageList: React.MemoExoticComponent<({ messages, isExpanded, hideDynamicBlocks, }: MessageListProps) => import("react/jsx-runtime").JSX.Element>;
11
9
  //# sourceMappingURL=MessageList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAG9C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,eAAO,MAAM,WAAW,6GAOnB,gBAAgB,6CA4EpB,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAG9C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,eAAO,MAAM,WAAW,2EAKnB,gBAAgB,6CAmFpB,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from "react";
3
3
  import { Box, Text, Static } from "ink";
4
- import { MessageItem } from "./MessageItem.js";
5
- export const MessageList = React.memo(({ messages, isLoading = false, isCommandRunning = false, isExpanded = false, forceStaticLastMessage = false, }) => {
4
+ import { MessageBlockItem } from "./MessageBlockItem.js";
5
+ export const MessageList = React.memo(({ messages, isExpanded = false, hideDynamicBlocks = false, }) => {
6
6
  // Empty message state
7
7
  if (messages.length === 0) {
8
8
  return (_jsx(Box, { flexDirection: "column", gap: 1, children: _jsx(Box, { flexDirection: "column", paddingY: 1, children: _jsx(Text, { color: "gray", children: "Welcome to WAVE Code Assistant!" }) }) }));
@@ -13,27 +13,32 @@ export const MessageList = React.memo(({ messages, isLoading = false, isCommandR
13
13
  const displayMessages = shouldLimitMessages
14
14
  ? messages.slice(-maxExpandedMessages)
15
15
  : messages;
16
- // Compute which messages to render statically vs dynamically
17
- const lastMessage = displayMessages[displayMessages.length - 1];
18
- const hasNonEndTool = lastMessage?.blocks.some((block) => block.type === "tool" && block.stage !== "end");
19
- const hasRunningCommand = lastMessage?.blocks.some((block) => block.type === "command_output" && block.isRunning);
20
- const shouldRenderLastDynamic = !forceStaticLastMessage &&
21
- (isLoading || isCommandRunning || hasNonEndTool || hasRunningCommand);
22
- const staticMessages = shouldRenderLastDynamic
23
- ? displayMessages.slice(0, -1)
24
- : displayMessages;
25
- const dynamicMessages = shouldRenderLastDynamic && displayMessages.length > 0
26
- ? [displayMessages[displayMessages.length - 1]]
27
- : [];
28
- return (_jsxs(Box, { flexDirection: "column", paddingBottom: 1, children: [_jsx(Static, { items: staticMessages, children: (message, key) => {
29
- // Get previous message
30
- const previousMessage = key > 0 ? staticMessages[key - 1] : undefined;
31
- return (_jsx(MessageItem, { message: message, shouldShowHeader: previousMessage?.role !== message.role, isExpanded: isExpanded }, key));
32
- } }), dynamicMessages.map((message, index) => {
33
- const messageIndex = staticMessages.length + index;
34
- const previousMessage = messageIndex > 0 ? displayMessages[messageIndex - 1] : undefined;
35
- return (_jsx(Box, { children: _jsx(MessageItem, { message: message, shouldShowHeader: previousMessage?.role !== message.role, isExpanded: isExpanded }) }, `dynamic-${index}`));
36
- })] }));
16
+ // Flatten messages into blocks with metadata
17
+ const allBlocks = displayMessages.flatMap((message, index) => {
18
+ const messageIndex = shouldLimitMessages
19
+ ? messages.length - maxExpandedMessages + index
20
+ : index;
21
+ return message.blocks.map((block, blockIndex) => ({
22
+ block,
23
+ message,
24
+ isLastMessage: messageIndex === messages.length - 1,
25
+ // Unique key for each block to help Static component
26
+ key: `${message.id || messageIndex}-${blockIndex}`,
27
+ }));
28
+ });
29
+ // Determine which blocks are static vs dynamic
30
+ const blocksWithStatus = allBlocks.map((item) => {
31
+ const { block, isLastMessage } = item;
32
+ const isDynamic = isLastMessage &&
33
+ ((block.type === "tool" && block.stage !== "end") ||
34
+ (block.type === "command_output" && block.isRunning));
35
+ return { ...item, isDynamic };
36
+ });
37
+ const staticBlocks = blocksWithStatus.filter((b) => !b.isDynamic);
38
+ const dynamicBlocks = hideDynamicBlocks
39
+ ? []
40
+ : blocksWithStatus.filter((b) => b.isDynamic);
41
+ return (_jsxs(Box, { flexDirection: "column", paddingBottom: 1, children: [staticBlocks.length > 0 && (_jsx(Static, { items: staticBlocks, children: (item) => (_jsx(MessageBlockItem, { block: item.block, message: item.message, isExpanded: isExpanded, paddingTop: 1 }, item.key)) })), dynamicBlocks.length > 0 && (_jsx(Box, { flexDirection: "column", children: dynamicBlocks.map((item) => (_jsx(MessageBlockItem, { block: item.block, message: item.message, isExpanded: isExpanded, paddingTop: 1 }, item.key))) }))] }));
37
42
  });
38
43
  // Add display name for debugging
39
44
  MessageList.displayName = "MessageList";
@@ -1 +1 @@
1
- {"version":3,"file":"PluginDetail.d.ts","sourceRoot":"","sources":["../../src/components/PluginDetail.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAUxC,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAwIhC,CAAC"}
1
+ {"version":3,"file":"PluginDetail.d.ts","sourceRoot":"","sources":["../../src/components/PluginDetail.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAUxC,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAuIhC,CAAC"}
@@ -24,36 +24,33 @@ export const PluginDetail = () => {
24
24
  actions.setView(isFromDiscover ? "DISCOVER" : "INSTALLED");
25
25
  }
26
26
  else if (key.upArrow) {
27
- if (isInstalledAndEnabled) {
28
- setSelectedActionIndex((prev) => prev > 0 ? prev - 1 : INSTALLED_ACTIONS.length - 1);
29
- }
30
- else {
31
- setSelectedScopeIndex((prev) => prev > 0 ? prev - 1 : SCOPES.length - 1);
32
- }
27
+ setSelectedActionIndex((prev) => prev > 0 ? prev - 1 : INSTALLED_ACTIONS.length - 1);
28
+ setSelectedScopeIndex((prev) => prev > 0 ? prev - 1 : SCOPES.length - 1);
33
29
  }
34
30
  else if (key.downArrow) {
35
- if (isInstalledAndEnabled) {
36
- setSelectedActionIndex((prev) => prev < INSTALLED_ACTIONS.length - 1 ? prev + 1 : 0);
37
- }
38
- else {
39
- setSelectedScopeIndex((prev) => prev < SCOPES.length - 1 ? prev + 1 : 0);
40
- }
31
+ setSelectedActionIndex((prev) => prev < INSTALLED_ACTIONS.length - 1 ? prev + 1 : 0);
32
+ setSelectedScopeIndex((prev) => prev < SCOPES.length - 1 ? prev + 1 : 0);
41
33
  }
42
34
  else if (key.return && plugin) {
43
35
  if (isInstalledAndEnabled) {
44
- const action = INSTALLED_ACTIONS[selectedActionIndex].id;
45
- if (action === "uninstall") {
46
- actions.uninstallPlugin(plugin.name, plugin.marketplace);
47
- }
48
- else {
49
- actions.updatePlugin(plugin.name, plugin.marketplace);
50
- }
51
- actions.setView("INSTALLED");
36
+ setSelectedActionIndex((prev) => {
37
+ const action = INSTALLED_ACTIONS[prev].id;
38
+ if (action === "uninstall") {
39
+ actions.uninstallPlugin(plugin.name, plugin.marketplace);
40
+ }
41
+ else {
42
+ actions.updatePlugin(plugin.name, plugin.marketplace);
43
+ }
44
+ return prev;
45
+ });
52
46
  }
53
47
  else {
54
- actions.installPlugin(plugin.name, plugin.marketplace, SCOPES[selectedScopeIndex].id);
55
- actions.setView("INSTALLED");
48
+ setSelectedScopeIndex((prev) => {
49
+ actions.installPlugin(plugin.name, plugin.marketplace, SCOPES[prev].id);
50
+ return prev;
51
+ });
56
52
  }
53
+ actions.setView("INSTALLED");
57
54
  }
58
55
  });
59
56
  if (!plugin) {