wave-code 0.5.0 → 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.
- package/dist/components/BackgroundTaskManager.d.ts +6 -0
- package/dist/components/BackgroundTaskManager.d.ts.map +1 -0
- package/dist/components/{TaskManager.js → BackgroundTaskManager.js} +1 -1
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +55 -5
- package/dist/components/CommandSelector.d.ts.map +1 -1
- package/dist/components/CommandSelector.js +10 -2
- package/dist/components/CompressDisplay.d.ts.map +1 -1
- package/dist/components/CompressDisplay.js +6 -10
- package/dist/components/ConfirmationDetails.d.ts +9 -0
- package/dist/components/ConfirmationDetails.d.ts.map +1 -0
- package/dist/components/ConfirmationDetails.js +53 -0
- package/dist/components/{Confirmation.d.ts → ConfirmationSelector.d.ts} +3 -3
- package/dist/components/ConfirmationSelector.d.ts.map +1 -0
- package/dist/components/{Confirmation.js → ConfirmationSelector.js} +34 -96
- package/dist/components/DiffDisplay.d.ts.map +1 -1
- package/dist/components/DiffDisplay.js +44 -1
- package/dist/components/FileSelector.d.ts.map +1 -1
- package/dist/components/FileSelector.js +2 -2
- package/dist/components/HistorySearch.d.ts.map +1 -1
- package/dist/components/HistorySearch.js +12 -4
- package/dist/components/InputBox.d.ts +1 -2
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +5 -9
- package/dist/components/LoadingIndicator.d.ts +11 -0
- package/dist/components/LoadingIndicator.d.ts.map +1 -0
- package/dist/components/LoadingIndicator.js +6 -0
- package/dist/components/Markdown.d.ts.map +1 -1
- package/dist/components/Markdown.js +114 -121
- package/dist/components/MessageItem.d.ts.map +1 -1
- package/dist/components/MessageItem.js +1 -2
- package/dist/components/MessageList.d.ts +2 -3
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +7 -7
- package/dist/components/PlanDisplay.d.ts.map +1 -1
- package/dist/components/PlanDisplay.js +4 -12
- package/dist/components/SubagentBlock.d.ts.map +1 -1
- package/dist/components/SubagentBlock.js +9 -6
- package/dist/components/TaskList.d.ts +3 -0
- package/dist/components/TaskList.d.ts.map +1 -0
- package/dist/components/TaskList.js +49 -0
- package/dist/components/ToolResultDisplay.js +1 -1
- package/dist/contexts/useChat.d.ts +5 -2
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +25 -25
- package/dist/hooks/useInputManager.d.ts +2 -7
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +8 -40
- package/dist/hooks/useTasks.d.ts +2 -0
- package/dist/hooks/useTasks.d.ts.map +1 -0
- package/dist/hooks/useTasks.js +5 -0
- package/dist/managers/InputManager.d.ts +4 -19
- package/dist/managers/InputManager.d.ts.map +1 -1
- package/dist/managers/InputManager.js +22 -65
- package/package.json +5 -6
- package/src/components/{TaskManager.tsx → BackgroundTaskManager.tsx} +4 -2
- package/src/components/ChatInterface.tsx +100 -20
- package/src/components/CommandSelector.tsx +35 -17
- package/src/components/CompressDisplay.tsx +5 -22
- package/src/components/ConfirmationDetails.tsx +108 -0
- package/src/components/{Confirmation.tsx → ConfirmationSelector.tsx} +69 -184
- package/src/components/DiffDisplay.tsx +62 -1
- package/src/components/FileSelector.tsx +0 -2
- package/src/components/HistorySearch.tsx +45 -21
- package/src/components/InputBox.tsx +9 -24
- package/src/components/LoadingIndicator.tsx +56 -0
- package/src/components/Markdown.tsx +126 -323
- package/src/components/MessageItem.tsx +1 -3
- package/src/components/MessageList.tsx +10 -67
- package/src/components/PlanDisplay.tsx +4 -27
- package/src/components/SubagentBlock.tsx +25 -16
- package/src/components/TaskList.tsx +70 -0
- package/src/components/ToolResultDisplay.tsx +2 -2
- package/src/contexts/useChat.tsx +38 -33
- package/src/hooks/useInputManager.ts +9 -47
- package/src/hooks/useTasks.ts +6 -0
- package/src/managers/InputManager.ts +25 -83
- package/dist/components/Confirmation.d.ts.map +0 -1
- package/dist/components/MemoryDisplay.d.ts +0 -8
- package/dist/components/MemoryDisplay.d.ts.map +0 -1
- package/dist/components/MemoryDisplay.js +0 -25
- package/dist/components/MemoryTypeSelector.d.ts +0 -8
- package/dist/components/MemoryTypeSelector.d.ts.map +0 -1
- package/dist/components/MemoryTypeSelector.js +0 -38
- package/dist/components/TaskManager.d.ts +0 -6
- package/dist/components/TaskManager.d.ts.map +0 -1
- package/src/components/MemoryDisplay.tsx +0 -62
- 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.
|
|
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"
|
|
224
|
-
this.
|
|
220
|
+
if (command === "tasks") {
|
|
221
|
+
this.setShowBackgroundTaskManager(true);
|
|
225
222
|
commandExecuted = true;
|
|
226
223
|
}
|
|
227
|
-
else if (command === "mcp"
|
|
228
|
-
this.
|
|
224
|
+
else if (command === "mcp") {
|
|
225
|
+
this.setShowMcpManager(true);
|
|
229
226
|
commandExecuted = true;
|
|
230
227
|
}
|
|
231
|
-
else if (command === "rewind"
|
|
232
|
-
this.
|
|
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
|
-
|
|
543
|
-
return this.
|
|
503
|
+
getShowBackgroundTaskManager() {
|
|
504
|
+
return this.showBackgroundTaskManager;
|
|
544
505
|
}
|
|
545
|
-
|
|
546
|
-
this.
|
|
547
|
-
this.callbacks.
|
|
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 &&
|
|
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.
|
|
792
|
-
this.showTaskManager ||
|
|
749
|
+
this.showBackgroundTaskManager ||
|
|
793
750
|
this.showMcpManager ||
|
|
794
751
|
this.showRewindManager) {
|
|
795
|
-
if (this.
|
|
796
|
-
this.showTaskManager ||
|
|
752
|
+
if (this.showBackgroundTaskManager ||
|
|
797
753
|
this.showMcpManager ||
|
|
798
754
|
this.showRewindManager) {
|
|
799
|
-
//
|
|
800
|
-
|
|
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.
|
|
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.
|
|
37
|
-
"marked": "^
|
|
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.
|
|
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
|
|
15
|
+
export interface BackgroundTaskManagerProps {
|
|
16
16
|
onCancel: () => void;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export const
|
|
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 {
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
|
13
|
+
const { displayContent } = useMemo(() => {
|
|
18
14
|
if (!content) {
|
|
19
|
-
return { displayContent: ""
|
|
15
|
+
return { displayContent: "" };
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
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";
|