wave-code 0.12.1 → 0.12.3
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/App.d.ts +1 -0
- package/dist/components/App.d.ts.map +1 -1
- package/dist/components/App.js +9 -14
- package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
- package/dist/components/BackgroundTaskManager.js +2 -1
- package/dist/components/BangDisplay.d.ts.map +1 -1
- package/dist/components/BangDisplay.js +2 -14
- package/dist/components/ChatInterface.d.ts +3 -1
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +4 -65
- package/dist/components/ConfirmationDetails.d.ts +0 -2
- package/dist/components/ConfirmationDetails.d.ts.map +1 -1
- package/dist/components/ConfirmationDetails.js +3 -15
- package/dist/components/ConfirmationSelector.d.ts +0 -1
- package/dist/components/ConfirmationSelector.d.ts.map +1 -1
- package/dist/components/ConfirmationSelector.js +4 -12
- package/dist/components/MessageList.d.ts +1 -2
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +5 -16
- package/dist/components/RewindCommand.d.ts.map +1 -1
- package/dist/components/RewindCommand.js +2 -4
- package/dist/components/ToolDisplay.d.ts.map +1 -1
- package/dist/components/ToolDisplay.js +2 -5
- package/dist/contexts/useChat.d.ts +0 -2
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +26 -15
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -6
- package/dist/utils/throttle.d.ts +15 -0
- package/dist/utils/throttle.d.ts.map +1 -0
- package/dist/utils/throttle.js +55 -0
- package/package.json +2 -2
- package/src/components/App.tsx +11 -17
- package/src/components/BackgroundTaskManager.tsx +3 -6
- package/src/components/BangDisplay.tsx +4 -22
- package/src/components/ChatInterface.tsx +8 -84
- package/src/components/ConfirmationDetails.tsx +2 -25
- package/src/components/ConfirmationSelector.tsx +3 -15
- package/src/components/MessageList.tsx +5 -25
- package/src/components/RewindCommand.tsx +3 -5
- package/src/components/ToolDisplay.tsx +2 -5
- package/src/contexts/useChat.tsx +34 -16
- package/src/utils/logger.ts +3 -7
- package/src/utils/throttle.ts +75 -0
package/dist/utils/logger.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import { Chalk } from "chalk";
|
|
12
|
+
import { getLastLines } from "wave-agent-sdk";
|
|
12
13
|
import { LOG_FILE, DATA_DIRECTORY } from "./constants.js";
|
|
13
14
|
const chalk = new Chalk({ level: 3 });
|
|
14
15
|
const logFile = process.env.LOG_FILE || LOG_FILE;
|
|
@@ -225,15 +226,11 @@ const truncateLogFileIfNeeded = (config) => {
|
|
|
225
226
|
// If file size exceeds limit, truncate file
|
|
226
227
|
if (stats.size > config.maxFileSize) {
|
|
227
228
|
const content = fs.readFileSync(logFile, "utf8");
|
|
228
|
-
const
|
|
229
|
-
// Keep the last specified number of lines
|
|
230
|
-
const keepLines = Math.min(config.keepLines, lines.length);
|
|
231
|
-
const truncatedContent = lines.slice(-keepLines).join("\n");
|
|
229
|
+
const truncatedContent = getLastLines(content, config.keepLines);
|
|
232
230
|
// Write truncated content
|
|
233
231
|
fs.writeFileSync(logFile, truncatedContent);
|
|
234
232
|
// Record truncation operation
|
|
235
|
-
|
|
236
|
-
logger.debug(`Log file truncated: removed ${removedLines} lines, kept last ${keepLines} lines`);
|
|
233
|
+
logger.debug(`Log file truncated: file size ${stats.size} exceeded limit ${config.maxFileSize}, kept last ${config.keepLines} lines`);
|
|
237
234
|
}
|
|
238
235
|
}
|
|
239
236
|
catch (error) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ThrottleOptions {
|
|
2
|
+
leading?: boolean;
|
|
3
|
+
trailing?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface ThrottledFunction<T extends (...args: unknown[]) => void> {
|
|
6
|
+
(...args: Parameters<T>): void;
|
|
7
|
+
cancel: () => void;
|
|
8
|
+
flush: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates a throttled function that only invokes `func` at most once per
|
|
12
|
+
* every `wait` milliseconds.
|
|
13
|
+
*/
|
|
14
|
+
export declare function throttle<T extends (...args: unknown[]) => void>(func: T, wait: number, options?: ThrottleOptions): ThrottledFunction<T>;
|
|
15
|
+
//# sourceMappingURL=throttle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/utils/throttle.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI;IACvE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,EAC7D,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,eAAmD,GAC3D,iBAAiB,CAAC,CAAC,CAAC,CAuDtB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a throttled function that only invokes `func` at most once per
|
|
3
|
+
* every `wait` milliseconds.
|
|
4
|
+
*/
|
|
5
|
+
export function throttle(func, wait, options = { leading: true, trailing: true }) {
|
|
6
|
+
let timeoutId = null;
|
|
7
|
+
let lastArgs = null;
|
|
8
|
+
let lastCallTime = 0;
|
|
9
|
+
const invokeFunc = () => {
|
|
10
|
+
if (lastArgs) {
|
|
11
|
+
func(...lastArgs);
|
|
12
|
+
lastCallTime = Date.now();
|
|
13
|
+
lastArgs = null;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const throttled = (...args) => {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
const remaining = wait - (now - lastCallTime);
|
|
19
|
+
lastArgs = args;
|
|
20
|
+
if (remaining <= 0 || remaining > wait) {
|
|
21
|
+
if (timeoutId) {
|
|
22
|
+
clearTimeout(timeoutId);
|
|
23
|
+
timeoutId = null;
|
|
24
|
+
}
|
|
25
|
+
if (options.leading !== false || lastCallTime !== 0) {
|
|
26
|
+
invokeFunc();
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
lastCallTime = now;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (!timeoutId && options.trailing !== false) {
|
|
33
|
+
timeoutId = setTimeout(() => {
|
|
34
|
+
timeoutId = null;
|
|
35
|
+
invokeFunc();
|
|
36
|
+
}, remaining);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
throttled.cancel = () => {
|
|
40
|
+
if (timeoutId) {
|
|
41
|
+
clearTimeout(timeoutId);
|
|
42
|
+
timeoutId = null;
|
|
43
|
+
}
|
|
44
|
+
lastArgs = null;
|
|
45
|
+
lastCallTime = 0;
|
|
46
|
+
};
|
|
47
|
+
throttled.flush = () => {
|
|
48
|
+
if (timeoutId) {
|
|
49
|
+
clearTimeout(timeoutId);
|
|
50
|
+
timeoutId = null;
|
|
51
|
+
}
|
|
52
|
+
invokeFunc();
|
|
53
|
+
};
|
|
54
|
+
return throttled;
|
|
55
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"semver": "^7.7.4",
|
|
43
43
|
"yargs": "^17.7.2",
|
|
44
44
|
"zod": "^3.23.8",
|
|
45
|
-
"wave-agent-sdk": "0.12.
|
|
45
|
+
"wave-agent-sdk": "0.12.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/react": "^19.1.8",
|
package/src/components/App.tsx
CHANGED
|
@@ -112,32 +112,33 @@ const AppWithProviders: React.FC<AppWithProvidersProps> = ({
|
|
|
112
112
|
);
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
const ChatInterfaceWithRemount: React.FC = () => {
|
|
115
|
+
export const ChatInterfaceWithRemount: React.FC = () => {
|
|
116
116
|
const { stdout } = useStdout();
|
|
117
|
-
const { isExpanded, rewindId,
|
|
117
|
+
const { isExpanded, rewindId, sessionId } = useChat();
|
|
118
118
|
|
|
119
|
-
const [remountKey, setRemountKey] = useState(
|
|
120
|
-
String(isExpanded) + rewindId + wasLastDetailsTooTall,
|
|
121
|
-
);
|
|
119
|
+
const [remountKey, setRemountKey] = useState(String(isExpanded) + rewindId);
|
|
122
120
|
|
|
123
121
|
const prevSessionId = useRef(sessionId);
|
|
122
|
+
const isRemountScheduled = useRef(false);
|
|
124
123
|
|
|
125
124
|
useEffect(() => {
|
|
126
125
|
const newKey =
|
|
127
126
|
String(isExpanded) +
|
|
128
127
|
rewindId +
|
|
129
|
-
wasLastDetailsTooTall +
|
|
130
128
|
(prevSessionId.current && sessionId && prevSessionId.current !== sessionId
|
|
131
129
|
? sessionId
|
|
132
130
|
: "");
|
|
133
131
|
|
|
134
|
-
if (newKey !== remountKey) {
|
|
132
|
+
if (newKey !== remountKey && !isRemountScheduled.current) {
|
|
133
|
+
isRemountScheduled.current = true;
|
|
134
|
+
|
|
135
135
|
const timeout = setTimeout(() => {
|
|
136
136
|
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", (err?: Error | null) => {
|
|
137
137
|
if (err) {
|
|
138
138
|
console.error("Failed to clear terminal:", err);
|
|
139
139
|
}
|
|
140
140
|
setRemountKey(newKey);
|
|
141
|
+
isRemountScheduled.current = false;
|
|
141
142
|
});
|
|
142
143
|
}, 100);
|
|
143
144
|
|
|
@@ -147,16 +148,9 @@ const ChatInterfaceWithRemount: React.FC = () => {
|
|
|
147
148
|
if (sessionId) {
|
|
148
149
|
prevSessionId.current = sessionId;
|
|
149
150
|
}
|
|
150
|
-
}, [
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
wasLastDetailsTooTall,
|
|
154
|
-
sessionId,
|
|
155
|
-
remountKey,
|
|
156
|
-
stdout,
|
|
157
|
-
]);
|
|
158
|
-
|
|
159
|
-
return <ChatInterface key={remountKey} />;
|
|
151
|
+
}, [isExpanded, rewindId, sessionId, remountKey, stdout]);
|
|
152
|
+
|
|
153
|
+
return <ChatInterface remountKey={remountKey} />;
|
|
160
154
|
};
|
|
161
155
|
|
|
162
156
|
export const App: React.FC<AppProps> = ({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { useChat } from "../contexts/useChat.js";
|
|
4
|
+
import { getLastLines } from "wave-agent-sdk";
|
|
4
5
|
|
|
5
6
|
interface Task {
|
|
6
7
|
id: string;
|
|
@@ -207,9 +208,7 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
|
|
|
207
208
|
OUTPUT (last 10 lines):
|
|
208
209
|
</Text>
|
|
209
210
|
<Box borderStyle="single" borderColor="green" padding={1}>
|
|
210
|
-
<Text>
|
|
211
|
-
{detailOutput.stdout.split("\n").slice(-10).join("\n")}
|
|
212
|
-
</Text>
|
|
211
|
+
<Text>{getLastLines(detailOutput.stdout, 10)}</Text>
|
|
213
212
|
</Box>
|
|
214
213
|
</Box>
|
|
215
214
|
)}
|
|
@@ -220,9 +219,7 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
|
|
|
220
219
|
ERRORS:
|
|
221
220
|
</Text>
|
|
222
221
|
<Box borderStyle="single" borderColor="red" padding={1}>
|
|
223
|
-
<Text color="red">
|
|
224
|
-
{detailOutput.stderr.split("\n").slice(-10).join("\n")}
|
|
225
|
-
</Text>
|
|
222
|
+
<Text color="red">{getLastLines(detailOutput.stderr, 10)}</Text>
|
|
226
223
|
</Box>
|
|
227
224
|
</Box>
|
|
228
225
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import type { BangBlock } from "wave-agent-sdk";
|
|
4
|
+
import { getLastLines } from "wave-agent-sdk";
|
|
4
5
|
|
|
5
6
|
interface BangDisplayProps {
|
|
6
7
|
block: BangBlock;
|
|
@@ -12,17 +13,8 @@ export const BangDisplay: React.FC<BangDisplayProps> = ({
|
|
|
12
13
|
isExpanded = false,
|
|
13
14
|
}) => {
|
|
14
15
|
const { command, output, isRunning, exitCode } = block;
|
|
15
|
-
const [isOverflowing, setIsOverflowing] = useState(false);
|
|
16
16
|
const MAX_LINES = 3; // Set maximum display lines
|
|
17
17
|
|
|
18
|
-
// Detect if content is overflowing
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (output) {
|
|
21
|
-
const lines = output.split("\n");
|
|
22
|
-
setIsOverflowing(!isExpanded && lines.length > MAX_LINES);
|
|
23
|
-
}
|
|
24
|
-
}, [output, isExpanded]);
|
|
25
|
-
|
|
26
18
|
const getStatusColor = () => {
|
|
27
19
|
if (isRunning) return "yellow";
|
|
28
20
|
if (exitCode === 0) return "green";
|
|
@@ -38,19 +30,9 @@ export const BangDisplay: React.FC<BangDisplayProps> = ({
|
|
|
38
30
|
</Box>
|
|
39
31
|
|
|
40
32
|
{output && (
|
|
41
|
-
<Box
|
|
42
|
-
paddingLeft={2}
|
|
43
|
-
height={
|
|
44
|
-
isExpanded
|
|
45
|
-
? undefined
|
|
46
|
-
: Math.min(output.split("\n").length, MAX_LINES)
|
|
47
|
-
}
|
|
48
|
-
overflow="hidden"
|
|
49
|
-
>
|
|
33
|
+
<Box paddingLeft={2} overflow="hidden">
|
|
50
34
|
<Text color="gray">
|
|
51
|
-
{
|
|
52
|
-
? output.split("\n").slice(-MAX_LINES).join("\n")
|
|
53
|
-
: output}
|
|
35
|
+
{isExpanded ? output : getLastLines(output, MAX_LINES)}
|
|
54
36
|
</Text>
|
|
55
37
|
</Box>
|
|
56
38
|
)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { Box
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box } from "ink";
|
|
3
3
|
import { MessageList } from "./MessageList.js";
|
|
4
4
|
import { BtwDisplay } from "./BtwDisplay.js";
|
|
5
5
|
import { InputBox } from "./InputBox.js";
|
|
@@ -10,15 +10,10 @@ import { ConfirmationDetails } from "./ConfirmationDetails.js";
|
|
|
10
10
|
import { ConfirmationSelector } from "./ConfirmationSelector.js";
|
|
11
11
|
|
|
12
12
|
import { useChat } from "../contexts/useChat.js";
|
|
13
|
-
import type { PermissionDecision } from "wave-agent-sdk";
|
|
14
|
-
|
|
15
|
-
export const ChatInterface: React.FC = () => {
|
|
16
|
-
const { stdout } = useStdout();
|
|
17
|
-
const [detailsHeight, setDetailsHeight] = useState(0);
|
|
18
|
-
const [selectorHeight, setSelectorHeight] = useState(0);
|
|
19
|
-
const [dynamicBlocksHeight, setDynamicBlocksHeight] = useState(0);
|
|
20
|
-
const [isConfirmationTooTall, setIsConfirmationTooTall] = useState(false);
|
|
21
13
|
|
|
14
|
+
export const ChatInterface: React.FC<{ remountKey: string }> = ({
|
|
15
|
+
remountKey,
|
|
16
|
+
}) => {
|
|
22
17
|
const {
|
|
23
18
|
messages,
|
|
24
19
|
isLoading,
|
|
@@ -37,8 +32,7 @@ export const ChatInterface: React.FC = () => {
|
|
|
37
32
|
isConfirmationVisible,
|
|
38
33
|
confirmingTool,
|
|
39
34
|
handleConfirmationDecision,
|
|
40
|
-
handleConfirmationCancel
|
|
41
|
-
setWasLastDetailsTooTall,
|
|
35
|
+
handleConfirmationCancel,
|
|
42
36
|
version,
|
|
43
37
|
workdir,
|
|
44
38
|
btwState,
|
|
@@ -46,83 +40,16 @@ export const ChatInterface: React.FC = () => {
|
|
|
46
40
|
|
|
47
41
|
const displayMessages = messages;
|
|
48
42
|
|
|
49
|
-
const handleDetailsHeightMeasured = useCallback((height: number) => {
|
|
50
|
-
setDetailsHeight(height);
|
|
51
|
-
}, []);
|
|
52
|
-
|
|
53
|
-
const handleSelectorHeightMeasured = useCallback((height: number) => {
|
|
54
|
-
setSelectorHeight(height);
|
|
55
|
-
}, []);
|
|
56
|
-
|
|
57
|
-
const handleDynamicBlocksHeightMeasured = useCallback((height: number) => {
|
|
58
|
-
setDynamicBlocksHeight(height);
|
|
59
|
-
}, []);
|
|
60
|
-
|
|
61
|
-
useLayoutEffect(() => {
|
|
62
|
-
if (!isConfirmationVisible) {
|
|
63
|
-
setIsConfirmationTooTall(false);
|
|
64
|
-
setDetailsHeight(0);
|
|
65
|
-
setSelectorHeight(0);
|
|
66
|
-
setDynamicBlocksHeight(0);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (isConfirmationTooTall) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const terminalHeight = stdout?.rows || 24;
|
|
75
|
-
const totalHeight = detailsHeight + selectorHeight + dynamicBlocksHeight;
|
|
76
|
-
if (totalHeight > terminalHeight - 3) {
|
|
77
|
-
setIsConfirmationTooTall(true);
|
|
78
|
-
}
|
|
79
|
-
}, [
|
|
80
|
-
detailsHeight,
|
|
81
|
-
selectorHeight,
|
|
82
|
-
dynamicBlocksHeight,
|
|
83
|
-
stdout?.rows,
|
|
84
|
-
isConfirmationVisible,
|
|
85
|
-
isConfirmationTooTall,
|
|
86
|
-
]);
|
|
87
|
-
|
|
88
|
-
const handleConfirmationCancel = useCallback(() => {
|
|
89
|
-
if (isConfirmationTooTall) {
|
|
90
|
-
setWasLastDetailsTooTall((prev) => prev + 1);
|
|
91
|
-
setIsConfirmationTooTall(false);
|
|
92
|
-
}
|
|
93
|
-
originalHandleConfirmationCancel();
|
|
94
|
-
}, [
|
|
95
|
-
isConfirmationTooTall,
|
|
96
|
-
originalHandleConfirmationCancel,
|
|
97
|
-
setWasLastDetailsTooTall,
|
|
98
|
-
]);
|
|
99
|
-
|
|
100
|
-
const wrappedHandleConfirmationDecision = useCallback(
|
|
101
|
-
(decision: PermissionDecision) => {
|
|
102
|
-
if (isConfirmationTooTall) {
|
|
103
|
-
setWasLastDetailsTooTall((prev) => prev + 1);
|
|
104
|
-
setIsConfirmationTooTall(false);
|
|
105
|
-
}
|
|
106
|
-
handleConfirmationDecision(decision);
|
|
107
|
-
},
|
|
108
|
-
[
|
|
109
|
-
isConfirmationTooTall,
|
|
110
|
-
handleConfirmationDecision,
|
|
111
|
-
setWasLastDetailsTooTall,
|
|
112
|
-
],
|
|
113
|
-
);
|
|
114
|
-
|
|
115
43
|
if (!sessionId) return null;
|
|
116
44
|
|
|
117
45
|
return (
|
|
118
46
|
<Box flexDirection="column">
|
|
119
47
|
<MessageList
|
|
48
|
+
key={remountKey}
|
|
120
49
|
messages={displayMessages}
|
|
121
50
|
isExpanded={isExpanded}
|
|
122
|
-
forceStatic={isConfirmationVisible && isConfirmationTooTall}
|
|
123
51
|
version={version}
|
|
124
52
|
workdir={workdir}
|
|
125
|
-
onDynamicBlocksHeightMeasured={handleDynamicBlocksHeightMeasured}
|
|
126
53
|
/>
|
|
127
54
|
|
|
128
55
|
{!isConfirmationVisible && !isExpanded && (
|
|
@@ -159,8 +86,6 @@ export const ChatInterface: React.FC = () => {
|
|
|
159
86
|
toolInput={confirmingTool!.input}
|
|
160
87
|
planContent={confirmingTool!.planContent}
|
|
161
88
|
isExpanded={isExpanded}
|
|
162
|
-
onHeightMeasured={handleDetailsHeightMeasured}
|
|
163
|
-
isStatic={isConfirmationTooTall}
|
|
164
89
|
/>
|
|
165
90
|
<ConfirmationSelector
|
|
166
91
|
toolName={confirmingTool!.name}
|
|
@@ -168,10 +93,9 @@ export const ChatInterface: React.FC = () => {
|
|
|
168
93
|
suggestedPrefix={confirmingTool!.suggestedPrefix}
|
|
169
94
|
hidePersistentOption={confirmingTool!.hidePersistentOption}
|
|
170
95
|
isExpanded={isExpanded}
|
|
171
|
-
onDecision={
|
|
96
|
+
onDecision={handleConfirmationDecision}
|
|
172
97
|
onCancel={handleConfirmationCancel}
|
|
173
98
|
onAbort={abortMessage}
|
|
174
|
-
onHeightMeasured={handleSelectorHeightMeasured}
|
|
175
99
|
/>
|
|
176
100
|
</>
|
|
177
101
|
)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { Box, Text
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
3
|
import {
|
|
4
4
|
BASH_TOOL_NAME,
|
|
5
5
|
EDIT_TOOL_NAME,
|
|
@@ -41,8 +41,6 @@ export interface ConfirmationDetailsProps {
|
|
|
41
41
|
toolInput?: Record<string, unknown>;
|
|
42
42
|
planContent?: string;
|
|
43
43
|
isExpanded?: boolean;
|
|
44
|
-
onHeightMeasured?: (height: number) => void;
|
|
45
|
-
isStatic?: boolean;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
|
|
@@ -50,26 +48,13 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
|
|
|
50
48
|
toolInput,
|
|
51
49
|
planContent,
|
|
52
50
|
isExpanded = false,
|
|
53
|
-
onHeightMeasured,
|
|
54
|
-
isStatic = false,
|
|
55
51
|
}) => {
|
|
56
|
-
const { stdout } = useStdout();
|
|
57
|
-
const boxRef = useRef(null);
|
|
58
|
-
|
|
59
52
|
const startLineNumber =
|
|
60
53
|
(toolInput?.startLineNumber as number | undefined) ??
|
|
61
54
|
(toolName === WRITE_TOOL_NAME ? 1 : undefined);
|
|
62
55
|
|
|
63
|
-
useLayoutEffect(() => {
|
|
64
|
-
if (boxRef.current) {
|
|
65
|
-
const { height } = measureElement(boxRef.current);
|
|
66
|
-
onHeightMeasured?.(height);
|
|
67
|
-
}
|
|
68
|
-
}, [stdout?.rows, onHeightMeasured, toolInput, isExpanded]);
|
|
69
|
-
|
|
70
56
|
const content = (
|
|
71
57
|
<Box
|
|
72
|
-
ref={boxRef}
|
|
73
58
|
flexDirection="column"
|
|
74
59
|
borderStyle="single"
|
|
75
60
|
borderColor="yellow"
|
|
@@ -109,14 +94,6 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
|
|
|
109
94
|
</Box>
|
|
110
95
|
);
|
|
111
96
|
|
|
112
|
-
if (isStatic) {
|
|
113
|
-
return (
|
|
114
|
-
<Static items={[1]}>
|
|
115
|
-
{(item) => <React.Fragment key={item}>{content}</React.Fragment>}
|
|
116
|
-
</Static>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
97
|
return content;
|
|
121
98
|
};
|
|
122
99
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect,
|
|
2
|
-
import { Box, Text, useInput
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import type { PermissionDecision, AskUserQuestionInput } from "wave-agent-sdk";
|
|
4
4
|
import {
|
|
5
5
|
BASH_TOOL_NAME,
|
|
@@ -25,7 +25,6 @@ export interface ConfirmationSelectorProps {
|
|
|
25
25
|
onDecision: (decision: PermissionDecision) => void;
|
|
26
26
|
onCancel: () => void;
|
|
27
27
|
onAbort: () => void;
|
|
28
|
-
onHeightMeasured?: (height: number) => void;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
interface ConfirmationState {
|
|
@@ -44,18 +43,7 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
|
|
|
44
43
|
onDecision,
|
|
45
44
|
onCancel,
|
|
46
45
|
onAbort,
|
|
47
|
-
onHeightMeasured,
|
|
48
46
|
}) => {
|
|
49
|
-
const { stdout } = useStdout();
|
|
50
|
-
const boxRef = useRef(null);
|
|
51
|
-
|
|
52
|
-
useLayoutEffect(() => {
|
|
53
|
-
if (boxRef.current) {
|
|
54
|
-
const { height } = measureElement(boxRef.current);
|
|
55
|
-
onHeightMeasured?.(height);
|
|
56
|
-
}
|
|
57
|
-
}, [stdout?.rows, onHeightMeasured, toolName, toolInput, isExpanded]);
|
|
58
|
-
|
|
59
47
|
const [state, setState] = useState<ConfirmationState>({
|
|
60
48
|
selectedOption: toolName === EXIT_PLAN_MODE_TOOL_NAME ? "clear" : "allow",
|
|
61
49
|
alternativeText: "",
|
|
@@ -497,7 +485,7 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
|
|
|
497
485
|
state.selectedOption === "alternative" && !state.hasUserInput;
|
|
498
486
|
|
|
499
487
|
return (
|
|
500
|
-
<Box
|
|
488
|
+
<Box flexDirection="column">
|
|
501
489
|
{toolName === ASK_USER_QUESTION_TOOL_NAME &&
|
|
502
490
|
currentQuestion &&
|
|
503
491
|
!isExpanded && (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import os from "os";
|
|
3
|
-
import { Box, Text, Static
|
|
3
|
+
import { Box, Text, Static } from "ink";
|
|
4
4
|
import type { Message, MessageBlock } from "wave-agent-sdk";
|
|
5
5
|
import { MessageBlockItem } from "./MessageBlockItem.js";
|
|
6
6
|
|
|
@@ -10,18 +10,10 @@ export interface MessageListProps {
|
|
|
10
10
|
forceStatic?: boolean;
|
|
11
11
|
version?: string;
|
|
12
12
|
workdir?: string;
|
|
13
|
-
onDynamicBlocksHeightMeasured?: (height: number) => void;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export const MessageList = React.memo(
|
|
17
|
-
({
|
|
18
|
-
messages,
|
|
19
|
-
isExpanded = false,
|
|
20
|
-
forceStatic = false,
|
|
21
|
-
version,
|
|
22
|
-
workdir,
|
|
23
|
-
onDynamicBlocksHeightMeasured,
|
|
24
|
-
}: MessageListProps) => {
|
|
16
|
+
({ messages, isExpanded = false, version, workdir }: MessageListProps) => {
|
|
25
17
|
const welcomeMessage = (
|
|
26
18
|
<Box flexDirection="column" paddingTop={1}>
|
|
27
19
|
<Text color="gray">WAVE{version ? ` v${version}` : ""}</Text>
|
|
@@ -66,25 +58,13 @@ export const MessageList = React.memo(
|
|
|
66
58
|
const blocksWithStatus = allBlocks.map((item) => {
|
|
67
59
|
const { block } = item;
|
|
68
60
|
const isDynamic =
|
|
69
|
-
|
|
70
|
-
(messagesWithRunningBlocks.has(item.messageIndex) || isRunning(block));
|
|
61
|
+
messagesWithRunningBlocks.has(item.messageIndex) || isRunning(block);
|
|
71
62
|
return { ...item, isDynamic };
|
|
72
63
|
});
|
|
73
64
|
|
|
74
65
|
const staticBlocks = blocksWithStatus.filter((b) => !b.isDynamic);
|
|
75
66
|
const dynamicBlocks = blocksWithStatus.filter((b) => b.isDynamic);
|
|
76
67
|
|
|
77
|
-
const dynamicBlocksRef = useRef(null);
|
|
78
|
-
|
|
79
|
-
useLayoutEffect(() => {
|
|
80
|
-
if (dynamicBlocksRef.current) {
|
|
81
|
-
const { height } = measureElement(dynamicBlocksRef.current);
|
|
82
|
-
onDynamicBlocksHeightMeasured?.(height);
|
|
83
|
-
} else {
|
|
84
|
-
onDynamicBlocksHeightMeasured?.(0);
|
|
85
|
-
}
|
|
86
|
-
}, [dynamicBlocks, isExpanded, onDynamicBlocksHeightMeasured]);
|
|
87
|
-
|
|
88
68
|
const staticItems = [
|
|
89
69
|
{
|
|
90
70
|
isWelcome: true,
|
|
@@ -130,7 +110,7 @@ export const MessageList = React.memo(
|
|
|
130
110
|
|
|
131
111
|
{/* Dynamic blocks */}
|
|
132
112
|
{dynamicBlocks.length > 0 && (
|
|
133
|
-
<Box
|
|
113
|
+
<Box flexDirection="column">
|
|
134
114
|
{dynamicBlocks.map((item) => (
|
|
135
115
|
<MessageBlockItem
|
|
136
116
|
key={item.key}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import type { Message
|
|
3
|
+
import type { Message } from "wave-agent-sdk";
|
|
4
|
+
import { getMessageContent } from "wave-agent-sdk";
|
|
4
5
|
|
|
5
6
|
export interface RewindCommandProps {
|
|
6
7
|
messages: Message[];
|
|
@@ -131,10 +132,7 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
|
|
|
131
132
|
{visibleCheckpoints.map((checkpoint, index) => {
|
|
132
133
|
const actualIndex = startIndex + index;
|
|
133
134
|
const isSelected = actualIndex === selectedIndex;
|
|
134
|
-
const content = checkpoint.msg
|
|
135
|
-
.filter((b): b is TextBlock => b.type === "text")
|
|
136
|
-
.map((b) => b.content)
|
|
137
|
-
.join(" ")
|
|
135
|
+
const content = getMessageContent(checkpoint.msg)
|
|
138
136
|
.replace(/\n/g, "\\n")
|
|
139
137
|
.substring(0, 60);
|
|
140
138
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import type { ToolBlock } from "wave-agent-sdk";
|
|
4
|
+
import { getLastLines } from "wave-agent-sdk";
|
|
4
5
|
import { DiffDisplay } from "./DiffDisplay.js";
|
|
5
6
|
|
|
6
7
|
interface ToolDisplayProps {
|
|
@@ -45,11 +46,7 @@ export const ToolDisplay: React.FC<ToolDisplayProps> = ({
|
|
|
45
46
|
|
|
46
47
|
// If no shortResult but has result, return last 5 lines
|
|
47
48
|
if (block.result) {
|
|
48
|
-
|
|
49
|
-
if (lines.length > 5) {
|
|
50
|
-
return lines.slice(-5).join("\n");
|
|
51
|
-
}
|
|
52
|
-
return block.result;
|
|
49
|
+
return getLastLines(block.result, 5);
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
return null;
|