wave-code 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/components/ChatInterface.d.ts.map +1 -1
  2. package/dist/components/ChatInterface.js +28 -15
  3. package/dist/components/ConfirmationDetails.d.ts +1 -0
  4. package/dist/components/ConfirmationDetails.d.ts.map +1 -1
  5. package/dist/components/ConfirmationDetails.js +6 -9
  6. package/dist/components/ConfirmationSelector.d.ts +1 -0
  7. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  8. package/dist/components/ConfirmationSelector.js +164 -117
  9. package/dist/components/DiffDisplay.d.ts +1 -0
  10. package/dist/components/DiffDisplay.d.ts.map +1 -1
  11. package/dist/components/DiffDisplay.js +92 -29
  12. package/dist/components/HistorySearch.d.ts.map +1 -1
  13. package/dist/components/HistorySearch.js +1 -1
  14. package/dist/components/Markdown.d.ts.map +1 -1
  15. package/dist/components/Markdown.js +3 -1
  16. package/dist/components/McpManager.d.ts.map +1 -1
  17. package/dist/components/McpManager.js +49 -52
  18. package/dist/components/MessageBlockItem.d.ts +9 -0
  19. package/dist/components/MessageBlockItem.d.ts.map +1 -0
  20. package/dist/components/MessageBlockItem.js +11 -0
  21. package/dist/components/MessageList.d.ts +2 -4
  22. package/dist/components/MessageList.d.ts.map +1 -1
  23. package/dist/components/MessageList.js +28 -23
  24. package/dist/components/PluginDetail.d.ts.map +1 -1
  25. package/dist/components/PluginDetail.js +19 -22
  26. package/dist/components/TaskList.d.ts.map +1 -1
  27. package/dist/components/TaskList.js +2 -5
  28. package/dist/components/ToolDisplay.d.ts.map +1 -1
  29. package/dist/components/ToolDisplay.js +1 -1
  30. package/dist/contexts/useChat.d.ts.map +1 -1
  31. package/dist/contexts/useChat.js +17 -2
  32. package/dist/utils/highlightUtils.d.ts +2 -0
  33. package/dist/utils/highlightUtils.d.ts.map +1 -0
  34. package/dist/utils/highlightUtils.js +69 -0
  35. package/dist/utils/toolParameterTransforms.d.ts +1 -1
  36. package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
  37. package/dist/utils/toolParameterTransforms.js +10 -3
  38. package/package.json +4 -2
  39. package/src/components/ChatInterface.tsx +35 -23
  40. package/src/components/ConfirmationDetails.tsx +13 -9
  41. package/src/components/ConfirmationSelector.tsx +207 -128
  42. package/src/components/DiffDisplay.tsx +162 -59
  43. package/src/components/HistorySearch.tsx +1 -0
  44. package/src/components/Markdown.tsx +3 -1
  45. package/src/components/McpManager.tsx +51 -59
  46. package/src/components/MessageBlockItem.tsx +83 -0
  47. package/src/components/MessageList.tsx +55 -52
  48. package/src/components/PluginDetail.tsx +30 -31
  49. package/src/components/TaskList.tsx +2 -5
  50. package/src/components/ToolDisplay.tsx +5 -1
  51. package/src/contexts/useChat.tsx +18 -2
  52. package/src/utils/highlightUtils.ts +76 -0
  53. package/src/utils/toolParameterTransforms.ts +11 -2
  54. package/dist/components/MessageItem.d.ts +0 -8
  55. package/dist/components/MessageItem.d.ts.map +0 -1
  56. package/dist/components/MessageItem.js +0 -13
  57. package/src/components/MessageItem.tsx +0 -81
@@ -24,6 +24,7 @@ export const ChatProvider = ({ children, bypassPermissions, pluginDirs, tools, }
24
24
  const [isTaskListVisible, setIsTaskListVisible] = useState(true);
25
25
  // AI State
26
26
  const [messages, setMessages] = useState([]);
27
+ const messagesUpdateTimerRef = useRef(null);
27
28
  const [isLoading, setIsLoading] = useState(false);
28
29
  const [latestTotalTokens, setlatestTotalTokens] = useState(0);
29
30
  const [sessionId, setSessionId] = useState("");
@@ -71,9 +72,16 @@ export const ChatProvider = ({ children, bypassPermissions, pluginDirs, tools, }
71
72
  useEffect(() => {
72
73
  const initializeAgent = async () => {
73
74
  const callbacks = {
74
- onMessagesChange: (newMessages) => {
75
+ onMessagesChange: () => {
75
76
  if (!isExpandedRef.current) {
76
- setMessages([...newMessages]);
77
+ if (!messagesUpdateTimerRef.current) {
78
+ messagesUpdateTimerRef.current = setTimeout(() => {
79
+ if (agentRef.current) {
80
+ setMessages([...agentRef.current.messages]);
81
+ }
82
+ messagesUpdateTimerRef.current = null;
83
+ }, 50);
84
+ }
77
85
  }
78
86
  },
79
87
  onServersChange: (servers) => {
@@ -173,6 +181,9 @@ export const ChatProvider = ({ children, bypassPermissions, pluginDirs, tools, }
173
181
  // Cleanup on unmount
174
182
  useEffect(() => {
175
183
  return () => {
184
+ if (messagesUpdateTimerRef.current) {
185
+ clearTimeout(messagesUpdateTimerRef.current);
186
+ }
176
187
  if (agentRef.current) {
177
188
  try {
178
189
  // Display usage summary before cleanup
@@ -332,6 +343,10 @@ export const ChatProvider = ({ children, bypassPermissions, pluginDirs, tools, }
332
343
  setIsExpanded((prev) => {
333
344
  const newExpanded = !prev;
334
345
  if (newExpanded) {
346
+ if (messagesUpdateTimerRef.current) {
347
+ clearTimeout(messagesUpdateTimerRef.current);
348
+ messagesUpdateTimerRef.current = null;
349
+ }
335
350
  // Transitioning to EXPANDED: Freeze the current view
336
351
  // Deep copy the last message to ensure it doesn't update if the agent is still writing to it
337
352
  setMessages((currentMessages) => {
@@ -0,0 +1,2 @@
1
+ export declare function highlightToAnsi(code: string, language?: string): string;
2
+ //# sourceMappingURL=highlightUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highlightUtils.d.ts","sourceRoot":"","sources":["../../src/utils/highlightUtils.ts"],"names":[],"mappings":"AA6DA,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAcvE"}
@@ -0,0 +1,69 @@
1
+ import hljs from 'highlight.js';
2
+ import { parse, HTMLElement, TextNode } from 'node-html-parser';
3
+ import chalk from 'chalk';
4
+ const theme = {
5
+ 'hljs-keyword': chalk.blue,
6
+ 'hljs-built_in': chalk.cyan,
7
+ 'hljs-type': chalk.cyan,
8
+ 'hljs-literal': chalk.magenta,
9
+ 'hljs-number': chalk.magenta,
10
+ 'hljs-operator': chalk.white,
11
+ 'hljs-punctuation': chalk.white,
12
+ 'hljs-property': chalk.yellow,
13
+ 'hljs-attr': chalk.yellow,
14
+ 'hljs-variable': chalk.white,
15
+ 'hljs-template-variable': chalk.white,
16
+ 'hljs-string': chalk.green,
17
+ 'hljs-char': chalk.green,
18
+ 'hljs-comment': chalk.gray,
19
+ 'hljs-doctag': chalk.gray,
20
+ 'hljs-function': chalk.yellow,
21
+ 'hljs-title': chalk.yellow,
22
+ 'hljs-params': chalk.white,
23
+ 'hljs-tag': chalk.blue,
24
+ 'hljs-name': chalk.blue,
25
+ 'hljs-selector-tag': chalk.blue,
26
+ 'hljs-selector-id': chalk.blue,
27
+ 'hljs-selector-class': chalk.blue,
28
+ 'hljs-selector-attr': chalk.blue,
29
+ 'hljs-selector-pseudo': chalk.blue,
30
+ 'hljs-subst': chalk.white,
31
+ 'hljs-section': chalk.blue.bold,
32
+ 'hljs-bullet': chalk.magenta,
33
+ 'hljs-emphasis': chalk.italic,
34
+ 'hljs-strong': chalk.bold,
35
+ 'hljs-addition': chalk.green,
36
+ 'hljs-deletion': chalk.red,
37
+ 'hljs-link': chalk.blue.underline,
38
+ };
39
+ function nodeToAnsi(node) {
40
+ if (node instanceof TextNode) {
41
+ return node.text;
42
+ }
43
+ if (node instanceof HTMLElement) {
44
+ const content = node.childNodes.map(nodeToAnsi).join('');
45
+ const classes = node.getAttribute('class')?.split(/\s+/) || [];
46
+ for (const className of classes) {
47
+ if (theme[className]) {
48
+ return theme[className](content);
49
+ }
50
+ }
51
+ return content;
52
+ }
53
+ return '';
54
+ }
55
+ export function highlightToAnsi(code, language) {
56
+ if (!code) {
57
+ return '';
58
+ }
59
+ try {
60
+ const highlighted = language
61
+ ? hljs.highlight(code, { language }).value
62
+ : hljs.highlightAuto(code).value;
63
+ const root = parse(highlighted);
64
+ return root.childNodes.map(nodeToAnsi).join('');
65
+ }
66
+ catch {
67
+ return code;
68
+ }
69
+ }
@@ -15,5 +15,5 @@ export declare function transformEditParameters(parameters: EditToolParameters):
15
15
  * Transform tool block parameters into standardized Change[] array for diff display
16
16
  * Forces type judgment based on tool name using type assertions
17
17
  */
18
- export declare function transformToolBlockToChanges(toolName: string, parameters: string): Change[];
18
+ export declare function transformToolBlockToChanges(toolName: string, parameters: string, startLineNumber?: number): Change[];
19
19
  //# sourceMappingURL=toolParameterTransforms.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"toolParameterTransforms.d.ts","sourceRoot":"","sources":["../../src/utils/toolParameterTransforms.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,KAAK,MAAM,EACX,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAmBxB;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,mBAAmB,GAC9B,MAAM,EAAE,CAOV;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,kBAAkB,GAC7B,MAAM,EAAE,CAOV;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,CAsBV"}
1
+ {"version":3,"file":"toolParameterTransforms.d.ts","sourceRoot":"","sources":["../../src/utils/toolParameterTransforms.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,KAAK,MAAM,EACX,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAmBxB;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,mBAAmB,GAC9B,MAAM,EAAE,CAOV;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,kBAAkB,GAC7B,MAAM,EAAE,CAOV;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,eAAe,CAAC,EAAE,MAAM,GACvB,MAAM,EAAE,CA8BV"}
@@ -44,20 +44,27 @@ export function transformEditParameters(parameters) {
44
44
  * Transform tool block parameters into standardized Change[] array for diff display
45
45
  * Forces type judgment based on tool name using type assertions
46
46
  */
47
- export function transformToolBlockToChanges(toolName, parameters) {
47
+ export function transformToolBlockToChanges(toolName, parameters, startLineNumber) {
48
48
  try {
49
49
  if (!toolName) {
50
50
  return [];
51
51
  }
52
52
  const parsedParams = parseToolParameters(parameters);
53
+ let changes = [];
53
54
  switch (toolName) {
54
55
  case "Write":
55
- return transformWriteParameters(parsedParams);
56
+ changes = transformWriteParameters(parsedParams);
57
+ break;
56
58
  case "Edit":
57
- return transformEditParameters(parsedParams);
59
+ changes = transformEditParameters(parsedParams);
60
+ break;
58
61
  default:
59
62
  return [];
60
63
  }
64
+ if (changes.length > 0 && startLineNumber !== undefined) {
65
+ changes[0].startLineNumber = startLineNumber;
66
+ }
67
+ return changes;
61
68
  }
62
69
  catch (error) {
63
70
  logger.warn("Failed to transform tool block to changes:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,12 +32,14 @@
32
32
  "chalk": "^5.6.2",
33
33
  "diff": "^8.0.2",
34
34
  "glob": "^13.0.0",
35
+ "highlight.js": "^11.11.1",
35
36
  "ink": "^6.7.0",
36
37
  "marked": "^17.0.2",
38
+ "node-html-parser": "^7.0.2",
37
39
  "react": "^19.2.4",
38
40
  "react-dom": "19.2.4",
39
41
  "yargs": "^17.7.2",
40
- "wave-agent-sdk": "0.7.0"
42
+ "wave-agent-sdk": "0.7.2"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@types/react": "^19.1.8",
@@ -1,4 +1,4 @@
1
- import React, { useState, useCallback } from "react";
1
+ import React, { useState, useCallback, useLayoutEffect } from "react";
2
2
  import { Box, useStdout } from "ink";
3
3
  import { MessageList } from "./MessageList.js";
4
4
  import { InputBox } from "./InputBox.js";
@@ -12,7 +12,9 @@ import type { PermissionDecision } from "wave-agent-sdk";
12
12
 
13
13
  export const ChatInterface: React.FC = () => {
14
14
  const { stdout } = useStdout();
15
- const [isDetailsTooTall, setIsDetailsTooTall] = useState(false);
15
+ const [detailsHeight, setDetailsHeight] = useState(0);
16
+ const [selectorHeight, setSelectorHeight] = useState(0);
17
+ const [isConfirmationTooTall, setIsConfirmationTooTall] = useState(false);
16
18
 
17
19
  const {
18
20
  messages,
@@ -36,39 +38,49 @@ export const ChatInterface: React.FC = () => {
36
38
  setWasLastDetailsTooTall,
37
39
  } = useChat();
38
40
 
39
- const handleHeightMeasured = useCallback(
40
- (height: number) => {
41
- const terminalHeight = stdout?.rows || 24;
42
- if (height > terminalHeight - 10) {
43
- setIsDetailsTooTall(true);
44
- } else {
45
- setIsDetailsTooTall(false);
46
- }
47
- },
48
- [stdout?.rows],
49
- );
41
+ const handleDetailsHeightMeasured = useCallback((height: number) => {
42
+ setDetailsHeight(height);
43
+ }, []);
44
+
45
+ const handleSelectorHeightMeasured = useCallback((height: number) => {
46
+ setSelectorHeight(height);
47
+ }, []);
48
+
49
+ useLayoutEffect(() => {
50
+ const terminalHeight = stdout?.rows || 24;
51
+ const totalHeight = detailsHeight + selectorHeight;
52
+ if (totalHeight > terminalHeight) {
53
+ setIsConfirmationTooTall(true);
54
+ } else {
55
+ setIsConfirmationTooTall(false);
56
+ }
57
+ }, [detailsHeight, selectorHeight, stdout?.rows]);
50
58
 
51
59
  const handleConfirmationCancel = useCallback(() => {
52
- if (isDetailsTooTall) {
60
+ if (isConfirmationTooTall) {
53
61
  setWasLastDetailsTooTall((prev) => prev + 1);
54
- setIsDetailsTooTall(false);
62
+ setIsConfirmationTooTall(false);
55
63
  }
56
64
  originalHandleConfirmationCancel();
57
65
  }, [
58
- isDetailsTooTall,
66
+ isConfirmationTooTall,
59
67
  originalHandleConfirmationCancel,
60
68
  setWasLastDetailsTooTall,
61
69
  ]);
62
70
 
63
71
  const wrappedHandleConfirmationDecision = useCallback(
64
72
  (decision: PermissionDecision) => {
65
- if (isDetailsTooTall) {
73
+ if (isConfirmationTooTall) {
66
74
  setWasLastDetailsTooTall((prev) => prev + 1);
67
- setIsDetailsTooTall(false);
75
+ setIsConfirmationTooTall(false);
68
76
  }
69
77
  handleConfirmationDecision(decision);
70
78
  },
71
- [isDetailsTooTall, handleConfirmationDecision, setWasLastDetailsTooTall],
79
+ [
80
+ isConfirmationTooTall,
81
+ handleConfirmationDecision,
82
+ setWasLastDetailsTooTall,
83
+ ],
72
84
  );
73
85
 
74
86
  if (!sessionId) return null;
@@ -77,10 +89,8 @@ export const ChatInterface: React.FC = () => {
77
89
  <Box flexDirection="column">
78
90
  <MessageList
79
91
  messages={messages}
80
- isLoading={isLoading}
81
- isCommandRunning={isCommandRunning}
82
92
  isExpanded={isExpanded}
83
- forceStaticLastMessage={isDetailsTooTall}
93
+ hideDynamicBlocks={isConfirmationVisible}
84
94
  />
85
95
 
86
96
  {(isLoading || isCommandRunning || isCompressing) &&
@@ -101,7 +111,8 @@ export const ChatInterface: React.FC = () => {
101
111
  toolName={confirmingTool!.name}
102
112
  toolInput={confirmingTool!.input}
103
113
  isExpanded={isExpanded}
104
- onHeightMeasured={handleHeightMeasured}
114
+ onHeightMeasured={handleDetailsHeightMeasured}
115
+ isStatic={isConfirmationTooTall}
105
116
  />
106
117
  <ConfirmationSelector
107
118
  toolName={confirmingTool!.name}
@@ -112,6 +123,7 @@ export const ChatInterface: React.FC = () => {
112
123
  onDecision={wrappedHandleConfirmationDecision}
113
124
  onCancel={handleConfirmationCancel}
114
125
  onAbort={abortMessage}
126
+ onHeightMeasured={handleSelectorHeightMeasured}
115
127
  />
116
128
  </>
117
129
  )}
@@ -1,4 +1,4 @@
1
- import React, { useLayoutEffect, useRef, useState } from "react";
1
+ import React, { useLayoutEffect, useRef } from "react";
2
2
  import { Box, Text, useStdout, measureElement, Static } from "ink";
3
3
  import {
4
4
  BASH_TOOL_NAME,
@@ -40,6 +40,7 @@ export interface ConfirmationDetailsProps {
40
40
  toolInput?: Record<string, unknown>;
41
41
  isExpanded?: boolean;
42
42
  onHeightMeasured?: (height: number) => void;
43
+ isStatic?: boolean;
43
44
  }
44
45
 
45
46
  export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
@@ -47,21 +48,21 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
47
48
  toolInput,
48
49
  isExpanded = false,
49
50
  onHeightMeasured,
51
+ isStatic = false,
50
52
  }) => {
51
53
  const { stdout } = useStdout();
52
- const [isStatic, setIsStatic] = useState(false);
53
54
  const boxRef = useRef(null);
54
55
 
56
+ const startLineNumber =
57
+ (toolInput?.startLineNumber as number | undefined) ??
58
+ (toolName === WRITE_TOOL_NAME ? 1 : undefined);
59
+
55
60
  useLayoutEffect(() => {
56
61
  if (boxRef.current) {
57
62
  const { height } = measureElement(boxRef.current);
58
- const terminalHeight = stdout?.rows || 24;
59
- if (height > terminalHeight - 10) {
60
- setIsStatic(true);
61
- }
62
63
  onHeightMeasured?.(height);
63
64
  }
64
- }, [stdout?.rows, onHeightMeasured]);
65
+ }, [stdout?.rows, onHeightMeasured, toolInput, isExpanded]);
65
66
 
66
67
  const content = (
67
68
  <Box
@@ -72,14 +73,17 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
72
73
  borderBottom={false}
73
74
  borderLeft={false}
74
75
  borderRight={false}
75
- paddingTop={1}
76
76
  >
77
77
  <Text color="yellow" bold>
78
78
  Tool: {toolName}
79
79
  </Text>
80
80
  <Text color="yellow">{getActionDescription(toolName, toolInput)}</Text>
81
81
 
82
- <DiffDisplay toolName={toolName} parameters={JSON.stringify(toolInput)} />
82
+ <DiffDisplay
83
+ toolName={toolName}
84
+ parameters={JSON.stringify(toolInput)}
85
+ startLineNumber={startLineNumber}
86
+ />
83
87
 
84
88
  {toolName !== ASK_USER_QUESTION_TOOL_NAME &&
85
89
  toolName === EXIT_PLAN_MODE_TOOL_NAME &&