wave-code 0.5.1 → 0.6.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 (88) hide show
  1. package/dist/components/BackgroundTaskManager.d.ts +6 -0
  2. package/dist/components/BackgroundTaskManager.d.ts.map +1 -0
  3. package/dist/components/{TaskManager.js → BackgroundTaskManager.js} +1 -1
  4. package/dist/components/ChatInterface.d.ts.map +1 -1
  5. package/dist/components/ChatInterface.js +55 -5
  6. package/dist/components/CommandSelector.d.ts.map +1 -1
  7. package/dist/components/CommandSelector.js +10 -2
  8. package/dist/components/CompressDisplay.d.ts.map +1 -1
  9. package/dist/components/CompressDisplay.js +6 -10
  10. package/dist/components/ConfirmationDetails.d.ts +9 -0
  11. package/dist/components/ConfirmationDetails.d.ts.map +1 -0
  12. package/dist/components/ConfirmationDetails.js +53 -0
  13. package/dist/components/{Confirmation.d.ts → ConfirmationSelector.d.ts} +3 -3
  14. package/dist/components/ConfirmationSelector.d.ts.map +1 -0
  15. package/dist/components/{Confirmation.js → ConfirmationSelector.js} +34 -96
  16. package/dist/components/DiffDisplay.d.ts.map +1 -1
  17. package/dist/components/DiffDisplay.js +44 -1
  18. package/dist/components/FileSelector.d.ts.map +1 -1
  19. package/dist/components/FileSelector.js +2 -2
  20. package/dist/components/HistorySearch.d.ts.map +1 -1
  21. package/dist/components/HistorySearch.js +12 -4
  22. package/dist/components/InputBox.d.ts +1 -2
  23. package/dist/components/InputBox.d.ts.map +1 -1
  24. package/dist/components/InputBox.js +5 -9
  25. package/dist/components/LoadingIndicator.d.ts +11 -0
  26. package/dist/components/LoadingIndicator.d.ts.map +1 -0
  27. package/dist/components/LoadingIndicator.js +6 -0
  28. package/dist/components/Markdown.d.ts.map +1 -1
  29. package/dist/components/Markdown.js +114 -121
  30. package/dist/components/MessageItem.d.ts.map +1 -1
  31. package/dist/components/MessageItem.js +1 -2
  32. package/dist/components/MessageList.d.ts +2 -3
  33. package/dist/components/MessageList.d.ts.map +1 -1
  34. package/dist/components/MessageList.js +7 -7
  35. package/dist/components/PlanDisplay.d.ts.map +1 -1
  36. package/dist/components/PlanDisplay.js +4 -12
  37. package/dist/components/SubagentBlock.d.ts.map +1 -1
  38. package/dist/components/SubagentBlock.js +9 -6
  39. package/dist/components/TaskList.d.ts +3 -0
  40. package/dist/components/TaskList.d.ts.map +1 -0
  41. package/dist/components/TaskList.js +49 -0
  42. package/dist/components/ToolResultDisplay.js +1 -1
  43. package/dist/contexts/useChat.d.ts +5 -2
  44. package/dist/contexts/useChat.d.ts.map +1 -1
  45. package/dist/contexts/useChat.js +25 -25
  46. package/dist/hooks/useInputManager.d.ts +2 -7
  47. package/dist/hooks/useInputManager.d.ts.map +1 -1
  48. package/dist/hooks/useInputManager.js +8 -40
  49. package/dist/hooks/useTasks.d.ts +2 -0
  50. package/dist/hooks/useTasks.d.ts.map +1 -0
  51. package/dist/hooks/useTasks.js +5 -0
  52. package/dist/managers/InputManager.d.ts +4 -19
  53. package/dist/managers/InputManager.d.ts.map +1 -1
  54. package/dist/managers/InputManager.js +22 -65
  55. package/package.json +5 -6
  56. package/src/components/{TaskManager.tsx → BackgroundTaskManager.tsx} +4 -2
  57. package/src/components/ChatInterface.tsx +100 -20
  58. package/src/components/CommandSelector.tsx +35 -17
  59. package/src/components/CompressDisplay.tsx +5 -22
  60. package/src/components/ConfirmationDetails.tsx +108 -0
  61. package/src/components/{Confirmation.tsx → ConfirmationSelector.tsx} +69 -184
  62. package/src/components/DiffDisplay.tsx +62 -1
  63. package/src/components/FileSelector.tsx +0 -2
  64. package/src/components/HistorySearch.tsx +45 -21
  65. package/src/components/InputBox.tsx +9 -24
  66. package/src/components/LoadingIndicator.tsx +56 -0
  67. package/src/components/Markdown.tsx +126 -324
  68. package/src/components/MessageItem.tsx +1 -3
  69. package/src/components/MessageList.tsx +10 -67
  70. package/src/components/PlanDisplay.tsx +4 -27
  71. package/src/components/SubagentBlock.tsx +25 -16
  72. package/src/components/TaskList.tsx +70 -0
  73. package/src/components/ToolResultDisplay.tsx +2 -2
  74. package/src/contexts/useChat.tsx +38 -33
  75. package/src/hooks/useInputManager.ts +9 -47
  76. package/src/hooks/useTasks.ts +6 -0
  77. package/src/managers/InputManager.ts +25 -83
  78. package/dist/components/Confirmation.d.ts.map +0 -1
  79. package/dist/components/MemoryDisplay.d.ts +0 -8
  80. package/dist/components/MemoryDisplay.d.ts.map +0 -1
  81. package/dist/components/MemoryDisplay.js +0 -25
  82. package/dist/components/MemoryTypeSelector.d.ts +0 -8
  83. package/dist/components/MemoryTypeSelector.d.ts.map +0 -1
  84. package/dist/components/MemoryTypeSelector.js +0 -38
  85. package/dist/components/TaskManager.d.ts +0 -6
  86. package/dist/components/TaskManager.d.ts.map +0 -1
  87. package/src/components/MemoryDisplay.tsx +0 -62
  88. package/src/components/MemoryTypeSelector.tsx +0 -98
@@ -18,9 +18,6 @@ export class InputManager {
18
18
  // History search state
19
19
  this.showHistorySearch = false;
20
20
  this.historySearchQuery = "";
21
- // Memory type selector state
22
- this.showMemoryTypeSelector = false;
23
- this.memoryMessage = "";
24
21
  // Input history state
25
22
  this.userInputHistory = [];
26
23
  this.historyIndex = -1;
@@ -37,7 +34,7 @@ export class InputManager {
37
34
  this.attachedImages = [];
38
35
  this.imageIdCounter = 1;
39
36
  // Additional UI state
40
- this.showTaskManager = false;
37
+ this.showBackgroundTaskManager = false;
41
38
  this.showMcpManager = false;
42
39
  this.showRewindManager = false;
43
40
  // Permission mode state
@@ -220,17 +217,16 @@ export class InputManager {
220
217
  }
221
218
  // If not an agent command or execution failed, check local commands
222
219
  if (!commandExecuted) {
223
- if (command === "tasks" && this.callbacks.onShowTaskManager) {
224
- this.callbacks.onShowTaskManager();
220
+ if (command === "tasks") {
221
+ this.setShowBackgroundTaskManager(true);
225
222
  commandExecuted = true;
226
223
  }
227
- else if (command === "mcp" && this.callbacks.onShowMcpManager) {
228
- this.callbacks.onShowMcpManager();
224
+ else if (command === "mcp") {
225
+ this.setShowMcpManager(true);
229
226
  commandExecuted = true;
230
227
  }
231
- else if (command === "rewind" &&
232
- this.callbacks.onShowRewindManager) {
233
- this.callbacks.onShowRewindManager();
228
+ else if (command === "rewind") {
229
+ this.setShowRewindManager(true);
234
230
  commandExecuted = true;
235
231
  }
236
232
  }
@@ -280,29 +276,6 @@ export class InputManager {
280
276
  }
281
277
  return false;
282
278
  }
283
- // Memory type selector methods
284
- activateMemoryTypeSelector(message) {
285
- this.showMemoryTypeSelector = true;
286
- this.memoryMessage = message;
287
- this.callbacks.onMemoryTypeSelectorStateChange?.(true, message);
288
- }
289
- async handleMemoryTypeSelect(type) {
290
- const currentMessage = this.inputText.trim();
291
- if (currentMessage.startsWith("#")) {
292
- await this.callbacks.onSaveMemory?.(currentMessage, type);
293
- }
294
- // Close the selector
295
- this.showMemoryTypeSelector = false;
296
- this.memoryMessage = "";
297
- this.callbacks.onMemoryTypeSelectorStateChange?.(false, "");
298
- // Clear input box
299
- this.clearInput();
300
- }
301
- handleCancelMemoryTypeSelect() {
302
- this.showMemoryTypeSelector = false;
303
- this.memoryMessage = "";
304
- this.callbacks.onMemoryTypeSelectorStateChange?.(false, "");
305
- }
306
279
  // Input history methods
307
280
  setUserInputHistory(history) {
308
281
  this.userInputHistory = history;
@@ -360,9 +333,6 @@ export class InputManager {
360
333
  isCommandSelectorActive() {
361
334
  return this.showCommandSelector;
362
335
  }
363
- isMemoryTypeSelectorActive() {
364
- return this.showMemoryTypeSelector;
365
- }
366
336
  getFileSelectorState() {
367
337
  return {
368
338
  show: this.showFileSelector,
@@ -378,12 +348,6 @@ export class InputManager {
378
348
  position: this.slashPosition,
379
349
  };
380
350
  }
381
- getMemoryTypeSelectorState() {
382
- return {
383
- show: this.showMemoryTypeSelector,
384
- message: this.memoryMessage,
385
- };
386
- }
387
351
  // Update search queries for active selectors
388
352
  updateSearchQueriesForActiveSelectors(inputText, cursorPosition) {
389
353
  if (this.showFileSelector && this.atPosition >= 0) {
@@ -411,9 +375,6 @@ export class InputManager {
411
375
  // Only activate command selector if '/' is at the start of input
412
376
  this.activateCommandSelector(this.cursorPosition - 1);
413
377
  }
414
- else if (char === "#" && this.cursorPosition === 1) {
415
- // Memory message detection will be handled in submit
416
- }
417
378
  else {
418
379
  // Update search queries for active selectors
419
380
  this.updateSearchQueriesForActiveSelectors(this.inputText, this.cursorPosition);
@@ -539,12 +500,12 @@ export class InputManager {
539
500
  }
540
501
  }
541
502
  // Task manager state methods
542
- getShowTaskManager() {
543
- return this.showTaskManager;
503
+ getShowBackgroundTaskManager() {
504
+ return this.showBackgroundTaskManager;
544
505
  }
545
- setShowTaskManager(show) {
546
- this.showTaskManager = show;
547
- this.callbacks.onTaskManagerStateChange?.(show);
506
+ setShowBackgroundTaskManager(show) {
507
+ this.showBackgroundTaskManager = show;
508
+ this.callbacks.onBackgroundTaskManagerStateChange?.(show);
548
509
  }
549
510
  getShowMcpManager() {
550
511
  return this.showMcpManager;
@@ -586,13 +547,6 @@ export class InputManager {
586
547
  return;
587
548
  }
588
549
  if (this.inputText.trim()) {
589
- const trimmedInput = this.inputText.trim();
590
- // Check if it's a memory message (starts with # and only one line)
591
- if (trimmedInput.startsWith("#") && !trimmedInput.includes("\n")) {
592
- // Activate memory type selector
593
- this.activateMemoryTypeSelector(trimmedInput);
594
- return;
595
- }
596
550
  // Extract image information
597
551
  const imageRegex = /\[Image #(\d+)\]/g;
598
552
  const matches = [...this.inputText.matchAll(imageRegex)];
@@ -773,7 +727,11 @@ export class InputManager {
773
727
  return true;
774
728
  }
775
729
  // Handle interrupt request - use Esc key to interrupt AI request or command
776
- if (key.escape && (isLoading || isCommandRunning)) {
730
+ if (key.escape &&
731
+ (isLoading || isCommandRunning) &&
732
+ !this.showBackgroundTaskManager &&
733
+ !this.showMcpManager &&
734
+ !this.showRewindManager) {
777
735
  // Unified interrupt for AI message generation and command execution
778
736
  this.callbacks.onAbortMessage?.();
779
737
  return true;
@@ -788,16 +746,15 @@ export class InputManager {
788
746
  if (this.showFileSelector ||
789
747
  this.showCommandSelector ||
790
748
  this.showHistorySearch ||
791
- this.showMemoryTypeSelector ||
792
- this.showTaskManager ||
749
+ this.showBackgroundTaskManager ||
793
750
  this.showMcpManager ||
794
751
  this.showRewindManager) {
795
- if (this.showMemoryTypeSelector ||
796
- this.showTaskManager ||
752
+ if (this.showBackgroundTaskManager ||
797
753
  this.showMcpManager ||
798
754
  this.showRewindManager) {
799
- // Memory type selector, task manager, MCP manager and Rewind don't need to handle input, handled by component itself
800
- return false;
755
+ // Task manager, MCP manager and Rewind don't need to handle input, handled by component itself
756
+ // Return true to indicate we've "handled" it (by ignoring it) so it doesn't leak to normal input
757
+ return true;
801
758
  }
802
759
  if (this.showHistorySearch) {
803
760
  if (key.escape) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,15 +30,14 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "chalk": "^5.6.2",
33
- "cli-highlight": "^2.1.11",
34
33
  "diff": "^8.0.2",
35
34
  "glob": "^13.0.0",
36
- "ink": "^6.5.1",
37
- "marked": "^11.2.0",
35
+ "ink": "^6.7.0",
36
+ "marked": "^17.0.2",
38
37
  "react": "^19.2.4",
39
38
  "react-dom": "19.2.4",
40
39
  "yargs": "^17.7.2",
41
- "wave-agent-sdk": "0.5.1"
40
+ "wave-agent-sdk": "0.6.0"
42
41
  },
43
42
  "devDependencies": {
44
43
  "@types/react": "^19.1.8",
@@ -60,7 +59,7 @@
60
59
  },
61
60
  "license": "MIT",
62
61
  "scripts": {
63
- "wave": "tsx src/index.ts",
62
+ "wave": "tsx --tsconfig tsconfig.dev.json src/index.ts",
64
63
  "build": "rimraf dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
65
64
  "type-check": "tsc --noEmit --incremental",
66
65
  "watch": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
@@ -12,11 +12,13 @@ interface Task {
12
12
  runtime?: number;
13
13
  }
14
14
 
15
- export interface TaskManagerProps {
15
+ export interface BackgroundTaskManagerProps {
16
16
  onCancel: () => void;
17
17
  }
18
18
 
19
- export const TaskManager: React.FC<TaskManagerProps> = ({ onCancel }) => {
19
+ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
20
+ onCancel,
21
+ }) => {
20
22
  const { backgroundTasks, getBackgroundTaskOutput, stopBackgroundTask } =
21
23
  useChat();
22
24
  const [tasks, setTasks] = useState<Task[]>([]);
@@ -1,11 +1,20 @@
1
- import React from "react";
2
- import { Box } from "ink";
1
+ import React, { useState, useCallback, useEffect } from "react";
2
+ import { Box, useStdout } from "ink";
3
3
  import { MessageList } from "./MessageList.js";
4
4
  import { InputBox } from "./InputBox.js";
5
- import { Confirmation } from "./Confirmation.js";
5
+ import { LoadingIndicator } from "./LoadingIndicator.js";
6
+ import { TaskList } from "./TaskList.js";
7
+ import { ConfirmationDetails } from "./ConfirmationDetails.js";
8
+ import { ConfirmationSelector } from "./ConfirmationSelector.js";
9
+
6
10
  import { useChat } from "../contexts/useChat.js";
11
+ import type { PermissionDecision } from "wave-agent-sdk";
7
12
 
8
13
  export const ChatInterface: React.FC = () => {
14
+ const { stdout } = useStdout();
15
+ const [isDetailsTooTall, setIsDetailsTooTall] = useState(false);
16
+ const [wasLastDetailsTooTall, setWasLastDetailsTooTall] = useState(0);
17
+
9
18
  const {
10
19
  messages,
11
20
  isLoading,
@@ -14,7 +23,6 @@ export const ChatInterface: React.FC = () => {
14
23
  isCompressing,
15
24
  sendMessage,
16
25
  abortMessage,
17
- saveMemory,
18
26
  mcpServers,
19
27
  connectMcpServer,
20
28
  disconnectMcpServer,
@@ -26,35 +34,108 @@ export const ChatInterface: React.FC = () => {
26
34
  isConfirmationVisible,
27
35
  confirmingTool,
28
36
  handleConfirmationDecision,
29
- handleConfirmationCancel,
37
+ handleConfirmationCancel: originalHandleConfirmationCancel,
30
38
  rewindId,
31
39
  } = useChat();
32
40
 
41
+ const [remountKey, setRemountKey] = useState(
42
+ String(isExpanded) + sessionId + rewindId + wasLastDetailsTooTall,
43
+ );
44
+
45
+ useEffect(() => {
46
+ const newKey =
47
+ String(isExpanded) + sessionId + rewindId + wasLastDetailsTooTall;
48
+ if (newKey !== remountKey) {
49
+ stdout?.write("\u001b[2J\u001b[0;0H", (err?: Error | null) => {
50
+ if (err) {
51
+ console.error("Failed to clear terminal:", err);
52
+ }
53
+ setRemountKey(newKey);
54
+ });
55
+ }
56
+ }, [
57
+ isExpanded,
58
+ sessionId,
59
+ rewindId,
60
+ wasLastDetailsTooTall,
61
+ remountKey,
62
+ stdout,
63
+ ]);
64
+
65
+ const handleHeightMeasured = useCallback(
66
+ (height: number) => {
67
+ const terminalHeight = stdout?.rows || 24;
68
+ if (height > terminalHeight - 10) {
69
+ setIsDetailsTooTall(true);
70
+ } else {
71
+ setIsDetailsTooTall(false);
72
+ }
73
+ },
74
+ [stdout?.rows],
75
+ );
76
+
77
+ const handleConfirmationCancel = useCallback(() => {
78
+ if (isDetailsTooTall) {
79
+ setWasLastDetailsTooTall((prev) => prev + 1);
80
+ setIsDetailsTooTall(false);
81
+ }
82
+ originalHandleConfirmationCancel();
83
+ }, [isDetailsTooTall, originalHandleConfirmationCancel]);
84
+
85
+ const wrappedHandleConfirmationDecision = useCallback(
86
+ (decision: PermissionDecision) => {
87
+ if (isDetailsTooTall) {
88
+ setWasLastDetailsTooTall((prev) => prev + 1);
89
+ setIsDetailsTooTall(false);
90
+ }
91
+ handleConfirmationDecision(decision);
92
+ },
93
+ [isDetailsTooTall, handleConfirmationDecision],
94
+ );
95
+
33
96
  if (!sessionId) return null;
34
97
 
35
98
  return (
36
- <Box flexDirection="column" height="100%" paddingY={1} paddingRight={1}>
99
+ <Box flexDirection="column">
37
100
  <MessageList
38
101
  messages={messages}
39
102
  isLoading={isLoading}
40
103
  isCommandRunning={isCommandRunning}
41
- isCompressing={isCompressing}
42
- latestTotalTokens={latestTotalTokens}
43
104
  isExpanded={isExpanded}
44
- key={String(isExpanded) + sessionId + rewindId}
105
+ forceStaticLastMessage={isDetailsTooTall}
106
+ key={remountKey}
45
107
  />
46
108
 
109
+ {(isLoading || isCommandRunning || isCompressing) &&
110
+ !isConfirmationVisible && (
111
+ <LoadingIndicator
112
+ isLoading={isLoading}
113
+ isCommandRunning={isCommandRunning}
114
+ isCompressing={isCompressing}
115
+ latestTotalTokens={latestTotalTokens}
116
+ />
117
+ )}
118
+ {!isConfirmationVisible && <TaskList />}
119
+
47
120
  {isConfirmationVisible && (
48
- <Confirmation
49
- toolName={confirmingTool!.name}
50
- toolInput={confirmingTool!.input}
51
- suggestedPrefix={confirmingTool!.suggestedPrefix}
52
- hidePersistentOption={confirmingTool!.hidePersistentOption}
53
- isExpanded={isExpanded}
54
- onDecision={handleConfirmationDecision}
55
- onCancel={handleConfirmationCancel}
56
- onAbort={abortMessage}
57
- />
121
+ <>
122
+ <ConfirmationDetails
123
+ toolName={confirmingTool!.name}
124
+ toolInput={confirmingTool!.input}
125
+ isExpanded={isExpanded}
126
+ onHeightMeasured={handleHeightMeasured}
127
+ />
128
+ <ConfirmationSelector
129
+ toolName={confirmingTool!.name}
130
+ toolInput={confirmingTool!.input}
131
+ suggestedPrefix={confirmingTool!.suggestedPrefix}
132
+ hidePersistentOption={confirmingTool!.hidePersistentOption}
133
+ isExpanded={isExpanded}
134
+ onDecision={wrappedHandleConfirmationDecision}
135
+ onCancel={handleConfirmationCancel}
136
+ onAbort={abortMessage}
137
+ />
138
+ </>
58
139
  )}
59
140
 
60
141
  {!isConfirmationVisible && !isExpanded && (
@@ -64,7 +145,6 @@ export const ChatInterface: React.FC = () => {
64
145
  userInputHistory={userInputHistory}
65
146
  sendMessage={sendMessage}
66
147
  abortMessage={abortMessage}
67
- saveMemory={saveMemory}
68
148
  mcpServers={mcpServers}
69
149
  connectMcpServer={connectMcpServer}
70
150
  disconnectMcpServer={disconnectMcpServer}
@@ -39,6 +39,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
39
39
  onCancel,
40
40
  commands = [], // Default to empty array
41
41
  }) => {
42
+ const MAX_VISIBLE_ITEMS = 3;
42
43
  const [selectedIndex, setSelectedIndex] = useState(0);
43
44
 
44
45
  // Merge agent commands and local commands
@@ -51,6 +52,19 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
51
52
  command.id.toLowerCase().includes(searchQuery.toLowerCase()),
52
53
  );
53
54
 
55
+ // Calculate visible window
56
+ const startIndex = Math.max(
57
+ 0,
58
+ Math.min(
59
+ selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2),
60
+ Math.max(0, filteredCommands.length - MAX_VISIBLE_ITEMS),
61
+ ),
62
+ );
63
+ const visibleCommands = filteredCommands.slice(
64
+ startIndex,
65
+ startIndex + MAX_VISIBLE_ITEMS,
66
+ );
67
+
54
68
  useInput((input, key) => {
55
69
  if (key.return) {
56
70
  if (
@@ -101,7 +115,6 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
101
115
  borderBottom={false}
102
116
  borderLeft={false}
103
117
  borderRight={false}
104
- paddingTop={1}
105
118
  >
106
119
  <Text color="yellow">No commands found for "{searchQuery}"</Text>
107
120
  <Text dimColor>Press Escape to cancel</Text>
@@ -117,7 +130,6 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
117
130
  borderBottom={false}
118
131
  borderLeft={false}
119
132
  borderRight={false}
120
- paddingTop={1}
121
133
  gap={1}
122
134
  >
123
135
  <Box>
@@ -126,23 +138,29 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
126
138
  </Text>
127
139
  </Box>
128
140
 
129
- {filteredCommands.map((command, index) => (
130
- <Box key={command.id} flexDirection="column">
131
- <Text
132
- color={index === selectedIndex ? "black" : "white"}
133
- backgroundColor={index === selectedIndex ? "magenta" : undefined}
134
- >
135
- {index === selectedIndex ? "▶ " : " "}/{command.id}
136
- </Text>
137
- {index === selectedIndex && (
138
- <Box marginLeft={4}>
139
- <Text color="gray" dimColor>
140
- {command.description}
141
+ <Box flexDirection="column">
142
+ {visibleCommands.map((command, index) => {
143
+ const actualIndex = startIndex + index;
144
+ const isSelected = actualIndex === selectedIndex;
145
+ return (
146
+ <Box key={command.id} flexDirection="column">
147
+ <Text
148
+ color={isSelected ? "black" : "white"}
149
+ backgroundColor={isSelected ? "magenta" : undefined}
150
+ >
151
+ {isSelected ? "" : " "}/{command.id}
141
152
  </Text>
153
+ {isSelected && (
154
+ <Box marginLeft={4}>
155
+ <Text color="gray" dimColor>
156
+ {command.description}
157
+ </Text>
158
+ </Box>
159
+ )}
142
160
  </Box>
143
- )}
144
- </Box>
145
- ))}
161
+ );
162
+ })}
163
+ </Box>
146
164
 
147
165
  <Box>
148
166
  <Text dimColor>
@@ -7,25 +7,16 @@ interface CompressDisplayProps {
7
7
  isExpanded?: boolean;
8
8
  }
9
9
 
10
- export const CompressDisplay: React.FC<CompressDisplayProps> = ({
11
- block,
12
- isExpanded = false,
13
- }) => {
10
+ export const CompressDisplay: React.FC<CompressDisplayProps> = ({ block }) => {
14
11
  const { content } = block;
15
- const MAX_LINES = 3; // Set maximum display lines for compressed content
16
12
 
17
- const { displayContent, isOverflowing } = useMemo(() => {
13
+ const { displayContent } = useMemo(() => {
18
14
  if (!content) {
19
- return { displayContent: "", isOverflowing: false };
15
+ return { displayContent: "" };
20
16
  }
21
17
 
22
- const lines = content.split("\n");
23
- const overflow = !isExpanded && lines.length > MAX_LINES;
24
-
25
- const display = overflow ? lines.slice(0, MAX_LINES).join("\n") : content;
26
-
27
- return { displayContent: display, isOverflowing: overflow };
28
- }, [content, isExpanded]);
18
+ return { displayContent: content };
19
+ }, [content]);
29
20
 
30
21
  return (
31
22
  <Box flexDirection="column">
@@ -43,14 +34,6 @@ export const CompressDisplay: React.FC<CompressDisplayProps> = ({
43
34
  >
44
35
  <Text color="white">{displayContent}</Text>
45
36
  </Box>
46
- {isOverflowing && (
47
- <Box paddingLeft={2} marginTop={1}>
48
- <Text color="yellow" dimColor>
49
- Content truncated ({content.split("\n").length} lines total,
50
- showing first {MAX_LINES} lines. Press Ctrl+O to expand.
51
- </Text>
52
- </Box>
53
- )}
54
37
  </Box>
55
38
  )}
56
39
  </Box>
@@ -0,0 +1,108 @@
1
+ import React, { useLayoutEffect, useRef, useState } from "react";
2
+ import { Box, Text, useStdout, measureElement, Static } from "ink";
3
+ import {
4
+ BASH_TOOL_NAME,
5
+ EDIT_TOOL_NAME,
6
+ MULTI_EDIT_TOOL_NAME,
7
+ DELETE_FILE_TOOL_NAME,
8
+ WRITE_TOOL_NAME,
9
+ EXIT_PLAN_MODE_TOOL_NAME,
10
+ ASK_USER_QUESTION_TOOL_NAME,
11
+ } from "wave-agent-sdk";
12
+ import { DiffDisplay } from "./DiffDisplay.js";
13
+ import { PlanDisplay } from "./PlanDisplay.js";
14
+
15
+ // Helper function to generate descriptive action text
16
+ const getActionDescription = (
17
+ toolName: string,
18
+ toolInput?: Record<string, unknown>,
19
+ ): string => {
20
+ if (!toolInput) {
21
+ return "Execute operation";
22
+ }
23
+
24
+ switch (toolName) {
25
+ case BASH_TOOL_NAME:
26
+ return `Execute command: ${toolInput.command || "unknown command"}`;
27
+ case EDIT_TOOL_NAME:
28
+ return `Edit file: ${toolInput.file_path || "unknown file"}`;
29
+ case MULTI_EDIT_TOOL_NAME:
30
+ return `Edit multiple sections in: ${toolInput.file_path || "unknown file"}`;
31
+ case DELETE_FILE_TOOL_NAME:
32
+ return `Delete file: ${toolInput.target_file || "unknown file"}`;
33
+ case WRITE_TOOL_NAME:
34
+ return `Write to file: ${toolInput.file_path || "unknown file"}`;
35
+ case EXIT_PLAN_MODE_TOOL_NAME:
36
+ return "Review and approve the plan";
37
+ case ASK_USER_QUESTION_TOOL_NAME:
38
+ return "Answer questions to clarify intent";
39
+ default:
40
+ return "Execute operation";
41
+ }
42
+ };
43
+
44
+ export interface ConfirmationDetailsProps {
45
+ toolName: string;
46
+ toolInput?: Record<string, unknown>;
47
+ isExpanded?: boolean;
48
+ onHeightMeasured?: (height: number) => void;
49
+ }
50
+
51
+ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
52
+ toolName,
53
+ toolInput,
54
+ isExpanded = false,
55
+ onHeightMeasured,
56
+ }) => {
57
+ const { stdout } = useStdout();
58
+ const [isStatic, setIsStatic] = useState(false);
59
+ const boxRef = useRef(null);
60
+
61
+ useLayoutEffect(() => {
62
+ if (boxRef.current) {
63
+ const { height } = measureElement(boxRef.current);
64
+ const terminalHeight = stdout?.rows || 24;
65
+ if (height > terminalHeight - 10) {
66
+ setIsStatic(true);
67
+ }
68
+ onHeightMeasured?.(height);
69
+ }
70
+ }, [stdout?.rows, onHeightMeasured]);
71
+
72
+ const content = (
73
+ <Box
74
+ ref={boxRef}
75
+ flexDirection="column"
76
+ borderStyle="single"
77
+ borderColor="yellow"
78
+ borderBottom={false}
79
+ borderLeft={false}
80
+ borderRight={false}
81
+ paddingTop={1}
82
+ >
83
+ <Text color="yellow" bold>
84
+ Tool: {toolName}
85
+ </Text>
86
+ <Text color="yellow">{getActionDescription(toolName, toolInput)}</Text>
87
+
88
+ <DiffDisplay toolName={toolName} parameters={JSON.stringify(toolInput)} />
89
+
90
+ {toolName !== ASK_USER_QUESTION_TOOL_NAME &&
91
+ toolName === EXIT_PLAN_MODE_TOOL_NAME &&
92
+ !!toolInput?.plan_content && (
93
+ <PlanDisplay
94
+ plan={toolInput.plan_content as string}
95
+ isExpanded={isExpanded}
96
+ />
97
+ )}
98
+ </Box>
99
+ );
100
+
101
+ if (isStatic) {
102
+ return <Static items={[1]}>{() => content}</Static>;
103
+ }
104
+
105
+ return content;
106
+ };
107
+
108
+ ConfirmationDetails.displayName = "ConfirmationDetails";