wave-code 1.0.9 → 1.1.0

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 (244) hide show
  1. package/bin/wave-code.js +8 -2
  2. package/dist/bundle/wave.mjs +3207 -0
  3. package/package.json +24 -25
  4. package/src/commands/update.ts +2 -7
  5. package/src/components/App.tsx +3 -7
  6. package/src/components/BtwDisplay.tsx +10 -1
  7. package/src/components/ChatInterface.tsx +21 -52
  8. package/src/components/ConfirmationDetails.tsx +144 -37
  9. package/src/components/DiffDisplay.tsx +335 -301
  10. package/src/components/InputBox.tsx +3 -1
  11. package/src/components/LoadingIndicator.tsx +16 -2
  12. package/src/components/MessageBlockItem.tsx +2 -4
  13. package/src/components/MessageList.tsx +75 -14
  14. package/src/components/ReasoningDisplay.tsx +2 -4
  15. package/src/components/SessionSelector.tsx +82 -11
  16. package/src/components/StatusCommand.tsx +2 -9
  17. package/src/components/ToolDisplay.tsx +2 -4
  18. package/src/contexts/useChat.tsx +172 -350
  19. package/src/daemon/commands.ts +57 -27
  20. package/src/daemon/jsonRpcClient.ts +1 -1
  21. package/src/daemon/socketClient.ts +2 -2
  22. package/src/hooks/useInputManager.ts +1 -1
  23. package/src/index.ts +57 -10
  24. package/src/managers/inputReducer.ts +186 -104
  25. package/src/print-cli.ts +21 -17
  26. package/src/reducers/selectorReducer.ts +10 -0
  27. package/src/session-selector-cli.tsx +269 -28
  28. package/src/stdio/agentBridge.ts +22 -21
  29. package/src/stdio/daemonServer.ts +3 -1
  30. package/src/stdio/jsonRpcConnection.ts +19 -1
  31. package/src/stdio/stdioServer.ts +17 -0
  32. package/src/utils/clipboard.ts +21 -0
  33. package/src/utils/highlightUtils.ts +46 -1
  34. package/src/utils/readPackageJson.ts +29 -0
  35. package/src/utils/streamingText.ts +11 -0
  36. package/src/utils/worktree.ts +191 -43
  37. package/dist/cli.d.ts +0 -6
  38. package/dist/cli.js +0 -53
  39. package/dist/commands/plugin/disable.d.ts +0 -5
  40. package/dist/commands/plugin/disable.js +0 -15
  41. package/dist/commands/plugin/enable.d.ts +0 -5
  42. package/dist/commands/plugin/enable.js +0 -15
  43. package/dist/commands/plugin/install.d.ts +0 -5
  44. package/dist/commands/plugin/install.js +0 -21
  45. package/dist/commands/plugin/list.d.ts +0 -1
  46. package/dist/commands/plugin/list.js +0 -31
  47. package/dist/commands/plugin/marketplace.d.ts +0 -10
  48. package/dist/commands/plugin/marketplace.js +0 -90
  49. package/dist/commands/plugin/uninstall.d.ts +0 -3
  50. package/dist/commands/plugin/uninstall.js +0 -16
  51. package/dist/commands/plugin/update.d.ts +0 -3
  52. package/dist/commands/plugin/update.js +0 -15
  53. package/dist/commands/update.d.ts +0 -1
  54. package/dist/commands/update.js +0 -97
  55. package/dist/components/AgentsManager.d.ts +0 -7
  56. package/dist/components/AgentsManager.js +0 -109
  57. package/dist/components/App.d.ts +0 -9
  58. package/dist/components/App.js +0 -76
  59. package/dist/components/BackgroundTaskManager.d.ts +0 -5
  60. package/dist/components/BackgroundTaskManager.js +0 -99
  61. package/dist/components/BangDisplay.d.ts +0 -8
  62. package/dist/components/BangDisplay.js +0 -17
  63. package/dist/components/BtwDisplay.d.ts +0 -7
  64. package/dist/components/BtwDisplay.js +0 -19
  65. package/dist/components/ChatInterface.d.ts +0 -2
  66. package/dist/components/ChatInterface.js +0 -61
  67. package/dist/components/CommandSelector.d.ts +0 -10
  68. package/dist/components/CommandSelector.js +0 -66
  69. package/dist/components/CompactDisplay.d.ts +0 -8
  70. package/dist/components/CompactDisplay.js +0 -13
  71. package/dist/components/ConfirmationDetails.d.ts +0 -9
  72. package/dist/components/ConfirmationDetails.js +0 -45
  73. package/dist/components/ConfirmationSelector.d.ts +0 -13
  74. package/dist/components/ConfirmationSelector.js +0 -127
  75. package/dist/components/DiffDisplay.d.ts +0 -8
  76. package/dist/components/DiffDisplay.js +0 -201
  77. package/dist/components/DiscoverView.d.ts +0 -2
  78. package/dist/components/DiscoverView.js +0 -37
  79. package/dist/components/FileSelector.d.ts +0 -11
  80. package/dist/components/FileSelector.js +0 -50
  81. package/dist/components/HelpView.d.ts +0 -7
  82. package/dist/components/HelpView.js +0 -74
  83. package/dist/components/HistorySearch.d.ts +0 -8
  84. package/dist/components/HistorySearch.js +0 -66
  85. package/dist/components/InputBox.d.ts +0 -24
  86. package/dist/components/InputBox.js +0 -149
  87. package/dist/components/InstalledView.d.ts +0 -2
  88. package/dist/components/InstalledView.js +0 -45
  89. package/dist/components/LoadingIndicator.d.ts +0 -10
  90. package/dist/components/LoadingIndicator.js +0 -6
  91. package/dist/components/LoginCommand.d.ts +0 -5
  92. package/dist/components/LoginCommand.js +0 -153
  93. package/dist/components/Markdown.d.ts +0 -5
  94. package/dist/components/Markdown.js +0 -127
  95. package/dist/components/MarketplaceAddForm.d.ts +0 -2
  96. package/dist/components/MarketplaceAddForm.js +0 -55
  97. package/dist/components/MarketplaceDetail.d.ts +0 -2
  98. package/dist/components/MarketplaceDetail.js +0 -74
  99. package/dist/components/MarketplaceList.d.ts +0 -8
  100. package/dist/components/MarketplaceList.js +0 -17
  101. package/dist/components/MarketplaceView.d.ts +0 -2
  102. package/dist/components/MarketplaceView.js +0 -41
  103. package/dist/components/McpManager.d.ts +0 -9
  104. package/dist/components/McpManager.js +0 -88
  105. package/dist/components/MessageBlockItem.d.ts +0 -8
  106. package/dist/components/MessageBlockItem.js +0 -15
  107. package/dist/components/MessageList.d.ts +0 -10
  108. package/dist/components/MessageList.js +0 -80
  109. package/dist/components/ModelSelector.d.ts +0 -8
  110. package/dist/components/ModelSelector.js +0 -45
  111. package/dist/components/Notifications.d.ts +0 -7
  112. package/dist/components/Notifications.js +0 -9
  113. package/dist/components/PlanDisplay.d.ts +0 -7
  114. package/dist/components/PlanDisplay.js +0 -6
  115. package/dist/components/PluginDetail.d.ts +0 -2
  116. package/dist/components/PluginDetail.js +0 -87
  117. package/dist/components/PluginList.d.ts +0 -13
  118. package/dist/components/PluginList.js +0 -15
  119. package/dist/components/PluginManagerShell.d.ts +0 -6
  120. package/dist/components/PluginManagerShell.js +0 -92
  121. package/dist/components/PluginManagerTypes.d.ts +0 -35
  122. package/dist/components/PluginManagerTypes.js +0 -1
  123. package/dist/components/QueuedMessageList.d.ts +0 -2
  124. package/dist/components/QueuedMessageList.js +0 -18
  125. package/dist/components/ReasoningDisplay.d.ts +0 -8
  126. package/dist/components/ReasoningDisplay.js +0 -13
  127. package/dist/components/RewindCommand.d.ts +0 -12
  128. package/dist/components/RewindCommand.js +0 -78
  129. package/dist/components/SessionSelector.d.ts +0 -12
  130. package/dist/components/SessionSelector.js +0 -47
  131. package/dist/components/StatusCommand.d.ts +0 -5
  132. package/dist/components/StatusCommand.js +0 -28
  133. package/dist/components/StatusLine.d.ts +0 -6
  134. package/dist/components/StatusLine.js +0 -9
  135. package/dist/components/TaskList.d.ts +0 -5
  136. package/dist/components/TaskList.js +0 -197
  137. package/dist/components/TaskNotificationMessage.d.ts +0 -5
  138. package/dist/components/TaskNotificationMessage.js +0 -12
  139. package/dist/components/ToolDisplay.d.ts +0 -9
  140. package/dist/components/ToolDisplay.js +0 -55
  141. package/dist/components/WorkflowManager.d.ts +0 -5
  142. package/dist/components/WorkflowManager.js +0 -102
  143. package/dist/components/WorktreeExitPrompt.d.ts +0 -12
  144. package/dist/components/WorktreeExitPrompt.js +0 -35
  145. package/dist/constants/commands.d.ts +0 -2
  146. package/dist/constants/commands.js +0 -92
  147. package/dist/contexts/PluginManagerContext.d.ts +0 -3
  148. package/dist/contexts/PluginManagerContext.js +0 -9
  149. package/dist/contexts/useAppConfig.d.ts +0 -10
  150. package/dist/contexts/useAppConfig.js +0 -13
  151. package/dist/contexts/useChat.d.ts +0 -101
  152. package/dist/contexts/useChat.js +0 -992
  153. package/dist/contracts/status.d.ts +0 -7
  154. package/dist/contracts/status.js +0 -1
  155. package/dist/daemon/commands.d.ts +0 -49
  156. package/dist/daemon/commands.js +0 -341
  157. package/dist/daemon/jsonRpcClient.d.ts +0 -38
  158. package/dist/daemon/jsonRpcClient.js +0 -129
  159. package/dist/daemon/socketClient.d.ts +0 -13
  160. package/dist/daemon/socketClient.js +0 -26
  161. package/dist/daemon-cli.d.ts +0 -10
  162. package/dist/daemon-cli.js +0 -15
  163. package/dist/hooks/useInputManager.d.ts +0 -75
  164. package/dist/hooks/useInputManager.js +0 -583
  165. package/dist/hooks/usePluginManager.d.ts +0 -4
  166. package/dist/hooks/usePluginManager.js +0 -265
  167. package/dist/hooks/useTasks.d.ts +0 -1
  168. package/dist/hooks/useTasks.js +0 -5
  169. package/dist/index.d.ts +0 -4
  170. package/dist/index.js +0 -447
  171. package/dist/managers/inputHandlers.d.ts +0 -30
  172. package/dist/managers/inputHandlers.js +0 -625
  173. package/dist/managers/inputReducer.d.ts +0 -271
  174. package/dist/managers/inputReducer.js +0 -1121
  175. package/dist/plugin-manager-cli.d.ts +0 -5
  176. package/dist/plugin-manager-cli.js +0 -12
  177. package/dist/print-cli.d.ts +0 -10
  178. package/dist/print-cli.js +0 -209
  179. package/dist/reducers/agentsManagerReducer.d.ts +0 -26
  180. package/dist/reducers/agentsManagerReducer.js +0 -54
  181. package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
  182. package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
  183. package/dist/reducers/confirmationReducer.d.ts +0 -37
  184. package/dist/reducers/confirmationReducer.js +0 -193
  185. package/dist/reducers/helpSelectorReducer.d.ts +0 -25
  186. package/dist/reducers/helpSelectorReducer.js +0 -52
  187. package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
  188. package/dist/reducers/marketplaceAddFormReducer.js +0 -58
  189. package/dist/reducers/mcpManagerReducer.d.ts +0 -37
  190. package/dist/reducers/mcpManagerReducer.js +0 -74
  191. package/dist/reducers/pluginDetailReducer.d.ts +0 -32
  192. package/dist/reducers/pluginDetailReducer.js +0 -70
  193. package/dist/reducers/questionReducer.d.ts +0 -82
  194. package/dist/reducers/questionReducer.js +0 -375
  195. package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
  196. package/dist/reducers/rewindSelectorReducer.js +0 -37
  197. package/dist/reducers/selectorReducer.d.ts +0 -23
  198. package/dist/reducers/selectorReducer.js +0 -42
  199. package/dist/reducers/workflowManagerReducer.d.ts +0 -40
  200. package/dist/reducers/workflowManagerReducer.js +0 -100
  201. package/dist/session-selector-cli.d.ts +0 -3
  202. package/dist/session-selector-cli.js +0 -25
  203. package/dist/stdio/agentBridge.d.ts +0 -139
  204. package/dist/stdio/agentBridge.js +0 -999
  205. package/dist/stdio/daemonServer.d.ts +0 -67
  206. package/dist/stdio/daemonServer.js +0 -191
  207. package/dist/stdio/index.d.ts +0 -5
  208. package/dist/stdio/index.js +0 -5
  209. package/dist/stdio/jsonRpcConnection.d.ts +0 -30
  210. package/dist/stdio/jsonRpcConnection.js +0 -127
  211. package/dist/stdio/protocol.d.ts +0 -41
  212. package/dist/stdio/protocol.js +0 -26
  213. package/dist/stdio/stdioServer.d.ts +0 -28
  214. package/dist/stdio/stdioServer.js +0 -36
  215. package/dist/stdio-cli.d.ts +0 -8
  216. package/dist/stdio-cli.js +0 -15
  217. package/dist/types.d.ts +0 -20
  218. package/dist/types.js +0 -1
  219. package/dist/utils/bracketedPaste.d.ts +0 -39
  220. package/dist/utils/bracketedPaste.js +0 -122
  221. package/dist/utils/clipboard.d.ts +0 -21
  222. package/dist/utils/clipboard.js +0 -347
  223. package/dist/utils/constants.d.ts +0 -16
  224. package/dist/utils/constants.js +0 -18
  225. package/dist/utils/highlightUtils.d.ts +0 -1
  226. package/dist/utils/highlightUtils.js +0 -93
  227. package/dist/utils/inputUtils.d.ts +0 -17
  228. package/dist/utils/inputUtils.js +0 -67
  229. package/dist/utils/logger.d.ts +0 -71
  230. package/dist/utils/logger.js +0 -259
  231. package/dist/utils/markdownTable.d.ts +0 -34
  232. package/dist/utils/markdownTable.js +0 -302
  233. package/dist/utils/rewindCheckpoints.d.ts +0 -8
  234. package/dist/utils/rewindCheckpoints.js +0 -17
  235. package/dist/utils/throttle.d.ts +0 -14
  236. package/dist/utils/throttle.js +0 -55
  237. package/dist/utils/toolParameterTransforms.d.ts +0 -18
  238. package/dist/utils/toolParameterTransforms.js +0 -73
  239. package/dist/utils/usageSummary.d.ts +0 -28
  240. package/dist/utils/usageSummary.js +0 -121
  241. package/dist/utils/version.d.ts +0 -1
  242. package/dist/utils/version.js +0 -4
  243. package/dist/utils/worktree.d.ts +0 -35
  244. package/dist/utils/worktree.js +0 -219
@@ -1,6 +1,7 @@
1
1
  import React, { useMemo } from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import { WRITE_TOOL_NAME, EDIT_TOOL_NAME } from "wave-agent-sdk";
4
+ import type { Change } from "wave-agent-sdk";
4
5
  import { transformToolBlockToChanges } from "../utils/toolParameterTransforms.js";
5
6
  import { diffLines, diffWords } from "diff";
6
7
 
@@ -8,351 +9,384 @@ interface DiffDisplayProps {
8
9
  toolName?: string;
9
10
  parameters?: string;
10
11
  startLineNumber?: number;
12
+ /** Return the diff as an array of line elements instead of a rendered Box,
13
+ * so callers can slice/scroll through the lines. */
14
+ asLines?: boolean;
11
15
  }
12
16
 
13
- export const DiffDisplay: React.FC<DiffDisplayProps> = ({
14
- toolName,
15
- parameters,
16
- startLineNumber,
17
- }) => {
18
- const showDiff =
19
- toolName && [WRITE_TOOL_NAME, EDIT_TOOL_NAME].includes(toolName);
17
+ // Render word-level diff between two lines of text
18
+ const renderWordLevelDiff = (
19
+ oldLine: string,
20
+ newLine: string,
21
+ keyPrefix: string,
22
+ ) => {
23
+ try {
24
+ const changes = diffWords(oldLine, newLine);
20
25
 
21
- // Diff detection and transformation using typed parameters
22
- const changes = useMemo(() => {
23
- if (!showDiff || !toolName || !parameters) return [];
24
- try {
25
- // Use local transformation with JSON parsing and type guards
26
- return transformToolBlockToChanges(toolName, parameters, startLineNumber);
27
- } catch (error) {
28
- console.warn("Error transforming tool block to changes:", error);
29
- return [];
30
- }
31
- }, [toolName, parameters, showDiff, startLineNumber]);
32
-
33
- // Render word-level diff between two lines of text
34
- const renderWordLevelDiff = (
35
- oldLine: string,
36
- newLine: string,
37
- keyPrefix: string,
38
- ) => {
39
- try {
40
- const changes = diffWords(oldLine, newLine);
41
-
42
- const removedParts: React.ReactNode[] = [];
43
- const addedParts: React.ReactNode[] = [];
44
-
45
- changes.forEach((part, index) => {
46
- if (part.removed) {
47
- removedParts.push(
48
- <Text
49
- key={`removed-${keyPrefix}-${index}`}
50
- color="black"
51
- backgroundColor="red"
52
- >
53
- {part.value}
54
- </Text>,
55
- );
56
- } else if (part.added) {
57
- addedParts.push(
58
- <Text
59
- key={`added-${keyPrefix}-${index}`}
60
- color="black"
61
- backgroundColor="green"
62
- >
63
- {part.value}
64
- </Text>,
65
- );
66
- } else {
67
- // Unchanged parts
68
- removedParts.push(
69
- <Text key={`removed-unchanged-${keyPrefix}-${index}`} color="red">
70
- {part.value}
71
- </Text>,
72
- );
73
- addedParts.push(
74
- <Text key={`added-unchanged-${keyPrefix}-${index}`} color="green">
75
- {part.value}
76
- </Text>,
77
- );
78
- }
79
- });
26
+ const removedParts: React.ReactNode[] = [];
27
+ const addedParts: React.ReactNode[] = [];
80
28
 
81
- return { removedParts, addedParts };
82
- } catch (error) {
83
- console.warn("Error rendering word-level diff:", error);
84
- // Fallback to simple line display
85
- return {
86
- removedParts: [
87
- <Text key={`fallback-removed-${keyPrefix}`} color="red">
88
- {oldLine}
29
+ changes.forEach((part, index) => {
30
+ if (part.removed) {
31
+ removedParts.push(
32
+ <Text
33
+ key={`removed-${keyPrefix}-${index}`}
34
+ color="black"
35
+ backgroundColor="red"
36
+ >
37
+ {part.value}
89
38
  </Text>,
90
- ],
91
- addedParts: [
92
- <Text key={`fallback-added-${keyPrefix}`} color="green">
93
- {newLine}
39
+ );
40
+ } else if (part.added) {
41
+ addedParts.push(
42
+ <Text
43
+ key={`added-${keyPrefix}-${index}`}
44
+ color="black"
45
+ backgroundColor="green"
46
+ >
47
+ {part.value}
48
+ </Text>,
49
+ );
50
+ } else {
51
+ // Unchanged parts
52
+ removedParts.push(
53
+ <Text key={`removed-unchanged-${keyPrefix}-${index}`} color="red">
54
+ {part.value}
55
+ </Text>,
56
+ );
57
+ addedParts.push(
58
+ <Text key={`added-unchanged-${keyPrefix}-${index}`} color="green">
59
+ {part.value}
94
60
  </Text>,
95
- ],
96
- };
97
- }
98
- };
61
+ );
62
+ }
63
+ });
99
64
 
100
- // Render expanded diff display
101
- const renderExpandedDiff = () => {
102
- try {
103
- if (changes.length === 0) return null;
65
+ return { removedParts, addedParts };
66
+ } catch (error) {
67
+ console.warn("Error rendering word-level diff:", error);
68
+ // Fallback to simple line display
69
+ return {
70
+ removedParts: [
71
+ <Text key={`fallback-removed-${keyPrefix}`} color="red">
72
+ {oldLine}
73
+ </Text>,
74
+ ],
75
+ addedParts: [
76
+ <Text key={`fallback-added-${keyPrefix}`} color="green">
77
+ {newLine}
78
+ </Text>,
79
+ ],
80
+ };
81
+ }
82
+ };
104
83
 
105
- const maxLineNum = changes.reduce((max, change) => {
106
- const oldLines = (change.oldContent || "").split("\n").length;
107
- const newLines = (change.newContent || "").split("\n").length;
108
- const start = change.startLineNumber || 1;
109
- // For Edit tool, the diff might show context lines before/after the change.
110
- // The startLineNumber is the line where old_string starts.
111
- // diffLines will include context lines if they are part of the change object.
112
- // However, our transformEditParameters currently only puts old_string/new_string.
113
- // If we ever support context lines in the Change object, we need to be careful.
114
- return Math.max(max, start + oldLines, start + newLines);
115
- }, 0);
116
- const maxDigits = Math.max(2, maxLineNum.toString().length);
84
+ /**
85
+ * Render the diff as an array of line elements (one ReactNode per row), so
86
+ * callers can slice/scroll through the lines. Returns an empty array when the
87
+ * tool doesn't produce a diff or the content can't be parsed.
88
+ */
89
+ export function buildDiffLines(changes: Change[]): React.ReactNode[] {
90
+ try {
91
+ if (changes.length === 0) return [];
117
92
 
118
- const renderLine = (
119
- oldLineNum: number | null,
120
- newLineNum: number | null,
121
- prefix: string,
122
- content: React.ReactNode,
123
- color: string,
124
- key: string,
125
- ) => {
126
- const formatNum = (num: number | null) =>
127
- num === null
128
- ? " ".repeat(maxDigits)
129
- : num.toString().padStart(maxDigits);
93
+ const maxLineNum = changes.reduce((max, change) => {
94
+ const oldLines = (change.oldContent || "").split("\n").length;
95
+ const newLines = (change.newContent || "").split("\n").length;
96
+ const start = change.startLineNumber || 1;
97
+ // For Edit tool, the diff might show context lines before/after the change.
98
+ // The startLineNumber is the line where old_string starts.
99
+ // diffLines will include context lines if they are part of the change object.
100
+ // However, our transformEditParameters currently only puts old_string/new_string.
101
+ // If we ever support context lines in the Change object, we need to be careful.
102
+ return Math.max(max, start + oldLines, start + newLines);
103
+ }, 0);
104
+ const maxDigits = Math.max(2, maxLineNum.toString().length);
130
105
 
131
- return (
132
- <Box key={key} flexDirection="row">
133
- <Text color="gray">{formatNum(oldLineNum)} </Text>
134
- <Text color="gray">{formatNum(newLineNum)} </Text>
135
- <Text color="gray">| </Text>
136
- <Text color={color}>{prefix}</Text>
137
- <Text color={color}>{content}</Text>
138
- </Box>
139
- );
140
- };
106
+ const renderLine = (
107
+ oldLineNum: number | null,
108
+ newLineNum: number | null,
109
+ prefix: string,
110
+ content: React.ReactNode,
111
+ color: string,
112
+ key: string,
113
+ ) => {
114
+ const formatNum = (num: number | null) =>
115
+ num === null
116
+ ? " ".repeat(maxDigits)
117
+ : num.toString().padStart(maxDigits);
141
118
 
142
- const allElements: React.ReactNode[] = [];
119
+ return (
120
+ <Box key={key} flexDirection="row">
121
+ <Text color="gray">{formatNum(oldLineNum)} </Text>
122
+ <Text color="gray">{formatNum(newLineNum)} </Text>
123
+ <Text color="gray">| </Text>
124
+ <Text color={color}>{prefix}</Text>
125
+ <Text color={color}>{content}</Text>
126
+ </Box>
127
+ );
128
+ };
143
129
 
144
- changes.forEach((change, changeIndex) => {
145
- try {
146
- // Get line-level diff to understand the structure
147
- const lineDiffs = diffLines(
148
- change.oldContent || "",
149
- change.newContent || "",
150
- );
130
+ const allElements: React.ReactNode[] = [];
151
131
 
152
- let oldLineNum = change.startLineNumber || 1;
153
- let newLineNum = change.startLineNumber || 1;
132
+ changes.forEach((change, changeIndex) => {
133
+ try {
134
+ // Get line-level diff to understand the structure
135
+ const lineDiffs = diffLines(
136
+ change.oldContent || "",
137
+ change.newContent || "",
138
+ );
154
139
 
155
- // Process line diffs
156
- const diffElements: React.ReactNode[] = [];
157
- for (let i = 0; i < lineDiffs.length; i++) {
158
- const part = lineDiffs[i];
159
- const lines = part.value.split("\n");
160
- // diffLines might return a trailing empty string if the content ends with a newline
161
- if (lines[lines.length - 1] === "") {
162
- lines.pop();
163
- }
140
+ let oldLineNum = change.startLineNumber || 1;
141
+ let newLineNum = change.startLineNumber || 1;
164
142
 
165
- if (part.removed) {
166
- // Look ahead for an added block
167
- if (i + 1 < lineDiffs.length && lineDiffs[i + 1].added) {
168
- const nextPart = lineDiffs[i + 1];
169
- const addedLines = nextPart.value.split("\n");
170
- if (addedLines[addedLines.length - 1] === "") {
171
- addedLines.pop();
172
- }
143
+ // Process line diffs
144
+ const diffElements: React.ReactNode[] = [];
145
+ for (let i = 0; i < lineDiffs.length; i++) {
146
+ const part = lineDiffs[i];
147
+ const lines = part.value.split("\n");
148
+ // diffLines might return a trailing empty string if the content ends with a newline
149
+ if (lines[lines.length - 1] === "") {
150
+ lines.pop();
151
+ }
173
152
 
174
- if (lines.length === addedLines.length) {
175
- // Word-level diffing
176
- lines.forEach((line, lineIndex) => {
177
- const { removedParts, addedParts } = renderWordLevelDiff(
178
- line,
179
- addedLines[lineIndex],
180
- `word-${changeIndex}-${i}-${lineIndex}`,
181
- );
182
- diffElements.push(
183
- renderLine(
184
- oldLineNum++,
185
- null,
186
- "-",
187
- removedParts,
188
- "red",
189
- `remove-${changeIndex}-${i}-${lineIndex}`,
190
- ),
191
- );
192
- diffElements.push(
193
- renderLine(
194
- null,
195
- newLineNum++,
196
- "+",
197
- addedParts,
198
- "green",
199
- `add-${changeIndex}-${i}-${lineIndex}`,
200
- ),
201
- );
202
- });
203
- i++; // Skip the added block
204
- continue;
205
- }
153
+ if (part.removed) {
154
+ // Look ahead for an added block
155
+ if (i + 1 < lineDiffs.length && lineDiffs[i + 1].added) {
156
+ const nextPart = lineDiffs[i + 1];
157
+ const addedLines = nextPart.value.split("\n");
158
+ if (addedLines[addedLines.length - 1] === "") {
159
+ addedLines.pop();
206
160
  }
207
161
 
208
- // Fallback to standard removed rendering
209
- lines.forEach((line, lineIndex) => {
210
- diffElements.push(
211
- renderLine(
212
- oldLineNum++,
213
- null,
214
- "-",
162
+ if (lines.length === addedLines.length) {
163
+ // Word-level diffing
164
+ lines.forEach((line, lineIndex) => {
165
+ const { removedParts, addedParts } = renderWordLevelDiff(
215
166
  line,
216
- "red",
217
- `remove-${changeIndex}-${i}-${lineIndex}`,
218
- ),
219
- );
220
- });
221
- } else if (part.added) {
222
- lines.forEach((line, lineIndex) => {
223
- diffElements.push(
224
- renderLine(
225
- null,
226
- newLineNum++,
227
- "+",
228
- line,
229
- "green",
230
- `add-${changeIndex}-${i}-${lineIndex}`,
231
- ),
232
- );
233
- });
234
- } else {
235
- // Context lines - show unchanged content
236
- const isFirstBlock = i === 0;
237
- const isLastBlock = i === lineDiffs.length - 1;
167
+ addedLines[lineIndex],
168
+ `word-${changeIndex}-${i}-${lineIndex}`,
169
+ );
170
+ diffElements.push(
171
+ renderLine(
172
+ oldLineNum++,
173
+ null,
174
+ "-",
175
+ removedParts,
176
+ "red",
177
+ `remove-${changeIndex}-${i}-${lineIndex}`,
178
+ ),
179
+ );
180
+ diffElements.push(
181
+ renderLine(
182
+ null,
183
+ newLineNum++,
184
+ "+",
185
+ addedParts,
186
+ "green",
187
+ `add-${changeIndex}-${i}-${lineIndex}`,
188
+ ),
189
+ );
190
+ });
191
+ i++; // Skip the added block
192
+ continue;
193
+ }
194
+ }
238
195
 
239
- let linesToDisplay = lines;
240
- let showEllipsisTop = false;
241
- let showEllipsisBottom = false;
196
+ // Fallback to standard removed rendering
197
+ lines.forEach((line, lineIndex) => {
198
+ diffElements.push(
199
+ renderLine(
200
+ oldLineNum++,
201
+ null,
202
+ "-",
203
+ line,
204
+ "red",
205
+ `remove-${changeIndex}-${i}-${lineIndex}`,
206
+ ),
207
+ );
208
+ });
209
+ } else if (part.added) {
210
+ lines.forEach((line, lineIndex) => {
211
+ diffElements.push(
212
+ renderLine(
213
+ null,
214
+ newLineNum++,
215
+ "+",
216
+ line,
217
+ "green",
218
+ `add-${changeIndex}-${i}-${lineIndex}`,
219
+ ),
220
+ );
221
+ });
222
+ } else {
223
+ // Context lines - show unchanged content
224
+ const isFirstBlock = i === 0;
225
+ const isLastBlock = i === lineDiffs.length - 1;
242
226
 
243
- if (isFirstBlock && !isLastBlock) {
244
- // First block: keep last 3
245
- if (lines.length > 3) {
246
- const skipCount = lines.length - 3;
247
- oldLineNum += skipCount;
248
- newLineNum += skipCount;
249
- linesToDisplay = lines.slice(-3);
250
- showEllipsisTop = true;
251
- }
252
- } else if (isLastBlock && !isFirstBlock) {
253
- // Last block: keep first 3
254
- if (lines.length > 3) {
255
- linesToDisplay = lines.slice(0, 3);
256
- showEllipsisBottom = true;
257
- }
258
- } else if (!isFirstBlock && !isLastBlock) {
259
- // Middle block: keep first 3 and last 3
260
- if (lines.length > 6) {
261
- linesToDisplay = [...lines.slice(0, 3), ...lines.slice(-3)];
262
- showEllipsisTop = false; // We'll put ellipsis in the middle
263
- }
264
- }
227
+ let linesToDisplay = lines;
228
+ let showEllipsisTop = false;
229
+ let showEllipsisBottom = false;
265
230
 
266
- if (showEllipsisTop) {
267
- diffElements.push(
268
- <Box key={`ellipsis-top-${changeIndex}-${i}`}>
269
- <Text color="gray">{" ".repeat(maxDigits * 2 + 2)}...</Text>
270
- </Box>,
271
- );
231
+ if (isFirstBlock && !isLastBlock) {
232
+ // First block: keep last 3
233
+ if (lines.length > 3) {
234
+ const skipCount = lines.length - 3;
235
+ oldLineNum += skipCount;
236
+ newLineNum += skipCount;
237
+ linesToDisplay = lines.slice(-3);
238
+ showEllipsisTop = true;
272
239
  }
240
+ } else if (isLastBlock && !isFirstBlock) {
241
+ // Last block: keep first 3
242
+ if (lines.length > 3) {
243
+ linesToDisplay = lines.slice(0, 3);
244
+ showEllipsisBottom = true;
245
+ }
246
+ } else if (!isFirstBlock && !isLastBlock) {
247
+ // Middle block: keep first 3 and last 3
248
+ if (lines.length > 6) {
249
+ linesToDisplay = [...lines.slice(0, 3), ...lines.slice(-3)];
250
+ showEllipsisTop = false; // We'll put ellipsis in the middle
251
+ }
252
+ }
273
253
 
274
- linesToDisplay.forEach((line, lineIndex) => {
275
- // If it's a middle block and we are at the split point
276
- if (
277
- !isFirstBlock &&
278
- !isLastBlock &&
279
- lines.length > 6 &&
280
- lineIndex === 3
281
- ) {
282
- const skipCount = lines.length - 6;
283
- oldLineNum += skipCount;
284
- newLineNum += skipCount;
285
- diffElements.push(
286
- <Box key={`ellipsis-mid-${changeIndex}-${i}`}>
287
- <Text color="gray">
288
- {" ".repeat(maxDigits * 2 + 2)}...
289
- </Text>
290
- </Box>,
291
- );
292
- }
293
-
294
- diffElements.push(
295
- renderLine(
296
- oldLineNum++,
297
- newLineNum++,
298
- " ",
299
- line,
300
- "white",
301
- `context-${changeIndex}-${i}-${lineIndex}`,
302
- ),
303
- );
304
- });
254
+ if (showEllipsisTop) {
255
+ diffElements.push(
256
+ <Box key={`ellipsis-top-${changeIndex}-${i}`}>
257
+ <Text color="gray">{" ".repeat(maxDigits * 2 + 2)}...</Text>
258
+ </Box>,
259
+ );
260
+ }
305
261
 
306
- if (showEllipsisBottom) {
307
- const skipCount = lines.length - linesToDisplay.length;
308
- // We don't increment oldLineNum/newLineNum here because they are already incremented in the loop
309
- // But we need to account for the lines we skipped at the end of this block
262
+ linesToDisplay.forEach((line, lineIndex) => {
263
+ // If it's a middle block and we are at the split point
264
+ if (
265
+ !isFirstBlock &&
266
+ !isLastBlock &&
267
+ lines.length > 6 &&
268
+ lineIndex === 3
269
+ ) {
270
+ const skipCount = lines.length - 6;
310
271
  oldLineNum += skipCount;
311
272
  newLineNum += skipCount;
312
273
  diffElements.push(
313
- <Box key={`ellipsis-bottom-${changeIndex}-${i}`}>
274
+ <Box key={`ellipsis-mid-${changeIndex}-${i}`}>
314
275
  <Text color="gray">{" ".repeat(maxDigits * 2 + 2)}...</Text>
315
276
  </Box>,
316
277
  );
317
278
  }
279
+
280
+ diffElements.push(
281
+ renderLine(
282
+ oldLineNum++,
283
+ newLineNum++,
284
+ " ",
285
+ line,
286
+ "white",
287
+ `context-${changeIndex}-${i}-${lineIndex}`,
288
+ ),
289
+ );
290
+ });
291
+
292
+ if (showEllipsisBottom) {
293
+ const skipCount = lines.length - linesToDisplay.length;
294
+ // We don't increment oldLineNum/newLineNum here because they are already incremented in the loop
295
+ // But we need to account for the lines we skipped at the end of this block
296
+ oldLineNum += skipCount;
297
+ newLineNum += skipCount;
298
+ diffElements.push(
299
+ <Box key={`ellipsis-bottom-${changeIndex}-${i}`}>
300
+ <Text color="gray">{" ".repeat(maxDigits * 2 + 2)}...</Text>
301
+ </Box>,
302
+ );
318
303
  }
319
304
  }
320
- allElements.push(...diffElements);
321
- } catch (error) {
322
- console.warn(
323
- `Error rendering diff for change ${changeIndex}:`,
324
- error,
325
- );
326
- // Fallback to simple display
327
- allElements.push(
328
- <Box key={`fallback-${changeIndex}`} flexDirection="column">
329
- <Text color="red">-{change.oldContent || ""}</Text>
330
- <Text color="green">+{change.newContent || ""}</Text>
331
- </Box>,
332
- );
333
305
  }
334
- });
306
+ allElements.push(...diffElements);
307
+ } catch (error) {
308
+ console.warn(`Error rendering diff for change ${changeIndex}:`, error);
309
+ // Fallback to simple display
310
+ allElements.push(
311
+ <Box key={`fallback-${changeIndex}`} flexDirection="column">
312
+ <Text color="red">-{change.oldContent || ""}</Text>
313
+ <Text color="green">+{change.newContent || ""}</Text>
314
+ </Box>,
315
+ );
316
+ }
317
+ });
335
318
 
336
- return <Box flexDirection="column">{allElements}</Box>;
337
- } catch (error) {
338
- console.warn("Error rendering expanded diff:", error);
339
- return (
340
- <Box>
341
- <Text color="gray">Error rendering diff display</Text>
342
- </Box>
343
- );
344
- }
345
- };
319
+ return allElements;
320
+ } catch (error) {
321
+ console.warn("Error rendering expanded diff:", error);
322
+ return [
323
+ <Box key="diff-error">
324
+ <Text color="gray">Error rendering diff display</Text>
325
+ </Box>,
326
+ ];
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Convenience wrapper used by both <DiffDisplay> and ConfirmationDetails:
332
+ * transforms the raw tool parameters into change rows (empty array when the
333
+ * tool doesn't produce a diff).
334
+ */
335
+ export function buildDiffLinesFromParams(params: {
336
+ toolName?: string;
337
+ parameters?: string;
338
+ startLineNumber?: number;
339
+ }): React.ReactNode[] {
340
+ const { toolName, parameters, startLineNumber } = params;
341
+ if (
342
+ !toolName ||
343
+ ![WRITE_TOOL_NAME, EDIT_TOOL_NAME].includes(toolName) ||
344
+ !parameters
345
+ ) {
346
+ return [];
347
+ }
348
+ let changes: Change[] = [];
349
+ try {
350
+ changes = transformToolBlockToChanges(
351
+ toolName,
352
+ parameters,
353
+ startLineNumber,
354
+ );
355
+ } catch (error) {
356
+ console.warn("Error transforming tool block to changes:", error);
357
+ return [];
358
+ }
359
+ return buildDiffLines(changes);
360
+ }
361
+
362
+ export const DiffDisplay = ({
363
+ toolName,
364
+ parameters,
365
+ startLineNumber,
366
+ asLines = false,
367
+ }: DiffDisplayProps): React.ReactNode => {
368
+ const showDiff =
369
+ toolName && [WRITE_TOOL_NAME, EDIT_TOOL_NAME].includes(toolName);
370
+
371
+ // Diff detection and transformation using typed parameters
372
+ const diffLines = useMemo(
373
+ () => buildDiffLinesFromParams({ toolName, parameters, startLineNumber }),
374
+ [toolName, parameters, startLineNumber],
375
+ );
346
376
 
347
377
  // Don't render anything if no diff should be shown
348
378
  if (!showDiff) {
349
- return null;
379
+ return asLines ? [] : null;
380
+ }
381
+
382
+ if (asLines) {
383
+ return diffLines;
350
384
  }
351
385
 
352
386
  return (
353
387
  <Box flexDirection="column">
354
388
  <Box paddingLeft={2} borderLeft borderColor="cyan" flexDirection="column">
355
- {renderExpandedDiff()}
389
+ <Box flexDirection="column">{diffLines}</Box>
356
390
  </Box>
357
391
  </Box>
358
392
  );