wave-code 0.11.6 → 0.12.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/acp/agent.d.ts.map +1 -1
- package/dist/acp/agent.js +9 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +97 -0
- package/dist/components/App.js +1 -1
- package/dist/components/BtwDisplay.d.ts +8 -0
- package/dist/components/BtwDisplay.d.ts.map +1 -0
- package/dist/components/BtwDisplay.js +9 -0
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +3 -5
- package/dist/components/ConfirmationDetails.d.ts.map +1 -1
- package/dist/components/ConfirmationDetails.js +7 -1
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +18 -4
- package/dist/components/MessageBlockItem.d.ts.map +1 -1
- package/dist/components/MessageBlockItem.js +2 -1
- package/dist/components/MessageList.d.ts +1 -2
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +9 -6
- package/dist/components/ModelSelector.d.ts +9 -0
- package/dist/components/ModelSelector.d.ts.map +1 -0
- package/dist/components/ModelSelector.js +34 -0
- package/dist/components/RewindCommand.js +2 -2
- package/dist/components/SlashDisplay.d.ts +9 -0
- package/dist/components/SlashDisplay.d.ts.map +1 -0
- package/dist/components/SlashDisplay.js +20 -0
- package/dist/components/StatusLine.d.ts +8 -0
- package/dist/components/StatusLine.d.ts.map +1 -0
- package/dist/components/StatusLine.js +5 -0
- package/dist/constants/commands.d.ts.map +1 -1
- package/dist/constants/commands.js +12 -0
- package/dist/contexts/useChat.d.ts +8 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +61 -1
- package/dist/hooks/useInputManager.d.ts +5 -0
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +58 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/managers/inputHandlers.d.ts +1 -1
- package/dist/managers/inputHandlers.d.ts.map +1 -1
- package/dist/managers/inputHandlers.js +74 -5
- package/dist/managers/inputReducer.d.ts +22 -0
- package/dist/managers/inputReducer.d.ts.map +1 -1
- package/dist/managers/inputReducer.js +23 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +4 -0
- package/package.json +4 -2
- package/src/acp/agent.ts +10 -0
- package/src/commands/update.ts +116 -0
- package/src/components/App.tsx +1 -1
- package/src/components/BtwDisplay.tsx +33 -0
- package/src/components/ChatInterface.tsx +25 -29
- package/src/components/ConfirmationDetails.tsx +14 -0
- package/src/components/InputBox.tsx +47 -17
- package/src/components/MessageBlockItem.tsx +5 -5
- package/src/components/MessageList.tsx +9 -10
- package/src/components/ModelSelector.tsx +109 -0
- package/src/components/RewindCommand.tsx +2 -2
- package/src/components/SlashDisplay.tsx +70 -0
- package/src/components/StatusLine.tsx +36 -0
- package/src/constants/commands.ts +12 -0
- package/src/contexts/useChat.tsx +82 -1
- package/src/hooks/useInputManager.ts +69 -0
- package/src/index.ts +9 -0
- package/src/managers/inputHandlers.ts +92 -4
- package/src/managers/inputReducer.ts +41 -1
- package/src/utils/version.ts +8 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import https from "https";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { isUpdateAvailable } from "../utils/version.js";
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const packageJsonPath = path.resolve(__dirname, "../../package.json");
|
|
11
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
12
|
+
const currentVersion = packageJson.version;
|
|
13
|
+
|
|
14
|
+
async function getLatestVersion(): Promise<string> {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
https
|
|
17
|
+
.get("https://registry.npmjs.org/wave-code/latest", (res) => {
|
|
18
|
+
let data = "";
|
|
19
|
+
res.on("data", (chunk) => {
|
|
20
|
+
data += chunk;
|
|
21
|
+
});
|
|
22
|
+
res.on("end", () => {
|
|
23
|
+
try {
|
|
24
|
+
const json = JSON.parse(data);
|
|
25
|
+
resolve(json.version);
|
|
26
|
+
} catch {
|
|
27
|
+
reject(new Error("Failed to parse npm registry response"));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
})
|
|
31
|
+
.on("error", (err) => {
|
|
32
|
+
reject(err);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function detectPackageManager(): "npm" | "pnpm" | "yarn" {
|
|
38
|
+
// Check if wave-code is installed globally with pnpm
|
|
39
|
+
const pnpmList = spawnSync("pnpm", ["list", "-g", "wave-code"], {
|
|
40
|
+
encoding: "utf-8",
|
|
41
|
+
});
|
|
42
|
+
if (pnpmList.status === 0 && pnpmList.stdout?.includes("wave-code")) {
|
|
43
|
+
return "pnpm";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check if wave-code is installed globally with yarn
|
|
47
|
+
const yarnList = spawnSync("yarn", ["global", "list"], { encoding: "utf-8" });
|
|
48
|
+
if (yarnList.status === 0 && yarnList.stdout?.includes("wave-code")) {
|
|
49
|
+
return "yarn";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Default to npm
|
|
53
|
+
return "npm";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function updateCommand() {
|
|
57
|
+
console.log(chalk.blue(`Checking for updates...`));
|
|
58
|
+
console.log(chalk.dim(`Current version: ${currentVersion}`));
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const latestVersion = await getLatestVersion();
|
|
62
|
+
console.log(chalk.dim(`Latest version: ${latestVersion}`));
|
|
63
|
+
|
|
64
|
+
if (!isUpdateAvailable(currentVersion, latestVersion)) {
|
|
65
|
+
console.log(chalk.green("WAVE Code is already up to date!"));
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(
|
|
70
|
+
chalk.yellow(`A new version of WAVE Code is available: ${latestVersion}`),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const packageManager = detectPackageManager();
|
|
74
|
+
let updateCmd: string;
|
|
75
|
+
let args: string[];
|
|
76
|
+
|
|
77
|
+
if (packageManager === "pnpm") {
|
|
78
|
+
updateCmd = "pnpm";
|
|
79
|
+
args = ["add", "-g", "wave-code@latest"];
|
|
80
|
+
} else if (packageManager === "yarn") {
|
|
81
|
+
updateCmd = "yarn";
|
|
82
|
+
args = ["global", "add", "wave-code@latest"];
|
|
83
|
+
} else {
|
|
84
|
+
updateCmd = "npm";
|
|
85
|
+
args = ["install", "-g", "wave-code@latest"];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(chalk.blue(`Updating WAVE Code using ${packageManager}...`));
|
|
89
|
+
console.log(chalk.dim(`Running: ${updateCmd} ${args.join(" ")}`));
|
|
90
|
+
|
|
91
|
+
const result = spawnSync(updateCmd, args, { stdio: "inherit" });
|
|
92
|
+
|
|
93
|
+
if (result.status === 0) {
|
|
94
|
+
console.log(chalk.green("WAVE Code updated successfully!"));
|
|
95
|
+
process.exit(0);
|
|
96
|
+
} else {
|
|
97
|
+
console.log(chalk.red("Failed to update WAVE Code."));
|
|
98
|
+
console.log(
|
|
99
|
+
chalk.yellow(
|
|
100
|
+
`Please try running the update command manually: ${updateCmd} ${args.join(" ")}`,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
if (process.platform !== "win32") {
|
|
104
|
+
console.log(
|
|
105
|
+
chalk.yellow(
|
|
106
|
+
"You might need to run it with sudo if you encounter permission issues.",
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(chalk.red("Error checking for updates:"), error);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
package/src/components/App.tsx
CHANGED
|
@@ -133,7 +133,7 @@ const ChatInterfaceWithRemount: React.FC = () => {
|
|
|
133
133
|
|
|
134
134
|
if (newKey !== remountKey) {
|
|
135
135
|
const timeout = setTimeout(() => {
|
|
136
|
-
stdout?.write("\u001b[2J\u001b[0;0H", (err?: Error | null) => {
|
|
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
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { Markdown } from "./Markdown.js";
|
|
4
|
+
import { BtwState } from "../managers/inputReducer.js";
|
|
5
|
+
|
|
6
|
+
interface BtwDisplayProps {
|
|
7
|
+
btwState: BtwState;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const BtwDisplay: React.FC<BtwDisplayProps> = ({ btwState }) => {
|
|
11
|
+
if (!btwState.isActive) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Box flexDirection="column" marginTop={0} marginBottom={1}>
|
|
17
|
+
{btwState.question && (
|
|
18
|
+
<Box>
|
|
19
|
+
<Text color={btwState.isLoading ? "yellow" : "green"}>● </Text>
|
|
20
|
+
<Text italic color="gray">
|
|
21
|
+
/btw {btwState.question}
|
|
22
|
+
</Text>
|
|
23
|
+
</Box>
|
|
24
|
+
)}
|
|
25
|
+
|
|
26
|
+
{btwState.answer && (
|
|
27
|
+
<Box flexDirection="column">
|
|
28
|
+
<Markdown>{btwState.answer}</Markdown>
|
|
29
|
+
</Box>
|
|
30
|
+
)}
|
|
31
|
+
</Box>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useCallback, useLayoutEffect } from "react";
|
|
2
2
|
import { Box, useStdout } from "ink";
|
|
3
3
|
import { MessageList } from "./MessageList.js";
|
|
4
|
+
import { BtwDisplay } from "./BtwDisplay.js";
|
|
4
5
|
import { InputBox } from "./InputBox.js";
|
|
5
6
|
import { LoadingIndicator } from "./LoadingIndicator.js";
|
|
6
7
|
import { TaskList } from "./TaskList.js";
|
|
@@ -40,11 +41,9 @@ export const ChatInterface: React.FC = () => {
|
|
|
40
41
|
setWasLastDetailsTooTall,
|
|
41
42
|
version,
|
|
42
43
|
workdir,
|
|
43
|
-
|
|
44
|
+
btwState,
|
|
44
45
|
} = useChat();
|
|
45
46
|
|
|
46
|
-
const model = getModelConfig().model;
|
|
47
|
-
|
|
48
47
|
const displayMessages = messages;
|
|
49
48
|
|
|
50
49
|
const handleDetailsHeightMeasured = useCallback((height: number) => {
|
|
@@ -123,21 +122,35 @@ export const ChatInterface: React.FC = () => {
|
|
|
123
122
|
forceStatic={isConfirmationVisible && isConfirmationTooTall}
|
|
124
123
|
version={version}
|
|
125
124
|
workdir={workdir}
|
|
126
|
-
model={model}
|
|
127
125
|
onDynamicBlocksHeightMeasured={handleDynamicBlocksHeightMeasured}
|
|
128
126
|
/>
|
|
129
127
|
|
|
130
|
-
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
{!isConfirmationVisible && !isExpanded && (
|
|
129
|
+
<>
|
|
130
|
+
<BtwDisplay btwState={btwState} />
|
|
131
|
+
{(isLoading || isCommandRunning || isCompressing) && (
|
|
132
|
+
<LoadingIndicator
|
|
133
|
+
isLoading={isLoading}
|
|
134
|
+
isCommandRunning={isCommandRunning}
|
|
135
|
+
isCompressing={isCompressing}
|
|
136
|
+
latestTotalTokens={latestTotalTokens}
|
|
137
|
+
/>
|
|
138
|
+
)}
|
|
139
|
+
<TaskList />
|
|
140
|
+
<QueuedMessageList />
|
|
141
|
+
<InputBox
|
|
134
142
|
isLoading={isLoading}
|
|
135
143
|
isCommandRunning={isCommandRunning}
|
|
136
|
-
|
|
137
|
-
|
|
144
|
+
sendMessage={sendMessage}
|
|
145
|
+
abortMessage={abortMessage}
|
|
146
|
+
mcpServers={mcpServers}
|
|
147
|
+
connectMcpServer={connectMcpServer}
|
|
148
|
+
disconnectMcpServer={disconnectMcpServer}
|
|
149
|
+
slashCommands={slashCommands}
|
|
150
|
+
hasSlashCommand={hasSlashCommand}
|
|
138
151
|
/>
|
|
139
|
-
|
|
140
|
-
|
|
152
|
+
</>
|
|
153
|
+
)}
|
|
141
154
|
|
|
142
155
|
{isConfirmationVisible && (
|
|
143
156
|
<>
|
|
@@ -162,23 +175,6 @@ export const ChatInterface: React.FC = () => {
|
|
|
162
175
|
/>
|
|
163
176
|
</>
|
|
164
177
|
)}
|
|
165
|
-
|
|
166
|
-
{!isConfirmationVisible && !isExpanded && (
|
|
167
|
-
<>
|
|
168
|
-
<QueuedMessageList />
|
|
169
|
-
<InputBox
|
|
170
|
-
isLoading={isLoading}
|
|
171
|
-
isCommandRunning={isCommandRunning}
|
|
172
|
-
sendMessage={sendMessage}
|
|
173
|
-
abortMessage={abortMessage}
|
|
174
|
-
mcpServers={mcpServers}
|
|
175
|
-
connectMcpServer={connectMcpServer}
|
|
176
|
-
disconnectMcpServer={disconnectMcpServer}
|
|
177
|
-
slashCommands={slashCommands}
|
|
178
|
-
hasSlashCommand={hasSlashCommand}
|
|
179
|
-
/>
|
|
180
|
-
</>
|
|
181
|
-
)}
|
|
182
178
|
</Box>
|
|
183
179
|
);
|
|
184
180
|
};
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "wave-agent-sdk";
|
|
10
10
|
import { DiffDisplay } from "./DiffDisplay.js";
|
|
11
11
|
import { PlanDisplay } from "./PlanDisplay.js";
|
|
12
|
+
import { highlightToAnsi } from "../utils/highlightUtils.js";
|
|
12
13
|
|
|
13
14
|
// Helper function to generate descriptive action text
|
|
14
15
|
const getActionDescription = (
|
|
@@ -87,6 +88,19 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
|
|
|
87
88
|
startLineNumber={startLineNumber}
|
|
88
89
|
/>
|
|
89
90
|
|
|
91
|
+
{toolName !== WRITE_TOOL_NAME &&
|
|
92
|
+
toolName !== EDIT_TOOL_NAME &&
|
|
93
|
+
toolName !== EXIT_PLAN_MODE_TOOL_NAME &&
|
|
94
|
+
toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
95
|
+
toolName !== BASH_TOOL_NAME &&
|
|
96
|
+
!!toolInput && (
|
|
97
|
+
<Box paddingLeft={2} borderLeft borderColor="cyan">
|
|
98
|
+
<Text>
|
|
99
|
+
{highlightToAnsi(JSON.stringify(toolInput, null, 2), "json")}
|
|
100
|
+
</Text>
|
|
101
|
+
</Box>
|
|
102
|
+
)}
|
|
103
|
+
|
|
90
104
|
{toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
91
105
|
toolName === EXIT_PLAN_MODE_TOOL_NAME &&
|
|
92
106
|
!!planContent && (
|
|
@@ -10,6 +10,8 @@ import { RewindCommand } from "./RewindCommand.js";
|
|
|
10
10
|
import { HelpView } from "./HelpView.js";
|
|
11
11
|
import { StatusCommand } from "./StatusCommand.js";
|
|
12
12
|
import { PluginManagerShell } from "./PluginManagerShell.js";
|
|
13
|
+
import { ModelSelector } from "./ModelSelector.js";
|
|
14
|
+
import { StatusLine } from "./StatusLine.js";
|
|
13
15
|
import { useInputManager } from "../hooks/useInputManager.js";
|
|
14
16
|
import { useChat } from "../contexts/useChat.js";
|
|
15
17
|
|
|
@@ -54,12 +56,19 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
54
56
|
const {
|
|
55
57
|
permissionMode: chatPermissionMode,
|
|
56
58
|
setPermissionMode: setChatPermissionMode,
|
|
59
|
+
allowBypassInCycle: chatAllowBypassInCycle,
|
|
57
60
|
handleRewindSelect,
|
|
58
61
|
backgroundCurrentTask,
|
|
59
62
|
messages,
|
|
60
63
|
getFullMessageThread,
|
|
61
64
|
sessionId,
|
|
62
65
|
workingDirectory,
|
|
66
|
+
askBtw,
|
|
67
|
+
btwState: chatBtwState,
|
|
68
|
+
setBtwState: setChatBtwState,
|
|
69
|
+
currentModel,
|
|
70
|
+
configuredModels,
|
|
71
|
+
setModel,
|
|
63
72
|
} = useChat();
|
|
64
73
|
|
|
65
74
|
// Input manager with all input state and functionality (including images)
|
|
@@ -94,21 +103,29 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
94
103
|
showHelp,
|
|
95
104
|
showStatusCommand,
|
|
96
105
|
showPluginManager,
|
|
106
|
+
showModelSelector,
|
|
97
107
|
setShowBackgroundTaskManager,
|
|
98
108
|
setShowMcpManager,
|
|
99
109
|
setShowRewindManager,
|
|
100
110
|
setShowHelp,
|
|
101
111
|
setShowStatusCommand,
|
|
102
112
|
setShowPluginManager,
|
|
113
|
+
setShowModelSelector,
|
|
103
114
|
// Permission mode
|
|
104
115
|
permissionMode,
|
|
105
116
|
setPermissionMode,
|
|
117
|
+
setAllowBypassInCycle,
|
|
118
|
+
// BTW state
|
|
119
|
+
btwState,
|
|
106
120
|
// Main handler
|
|
107
121
|
handleInput,
|
|
108
122
|
// Manager ready state
|
|
109
123
|
isManagerReady,
|
|
110
124
|
} = useInputManager({
|
|
111
125
|
onSendMessage: sendMessage,
|
|
126
|
+
onAskBtw: askBtw,
|
|
127
|
+
btwState: chatBtwState,
|
|
128
|
+
onBtwStateChange: setChatBtwState,
|
|
112
129
|
onHasSlashCommand: hasSlashCommand,
|
|
113
130
|
onAbortMessage: abortMessage,
|
|
114
131
|
onBackgroundCurrentTask: backgroundCurrentTask,
|
|
@@ -123,6 +140,11 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
123
140
|
setPermissionMode(chatPermissionMode);
|
|
124
141
|
}, [chatPermissionMode, setPermissionMode]);
|
|
125
142
|
|
|
143
|
+
// Sync allowBypassInCycle from useChat to InputManager
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
setAllowBypassInCycle(chatAllowBypassInCycle);
|
|
146
|
+
}, [chatAllowBypassInCycle, setAllowBypassInCycle]);
|
|
147
|
+
|
|
126
148
|
// Use the InputManager's unified input handler
|
|
127
149
|
useInput(async (input, key) => {
|
|
128
150
|
await handleInput(input, key, attachedImages, clearImages);
|
|
@@ -189,6 +211,17 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
189
211
|
return <PluginManagerShell onCancel={() => setShowPluginManager(false)} />;
|
|
190
212
|
}
|
|
191
213
|
|
|
214
|
+
if (showModelSelector) {
|
|
215
|
+
return (
|
|
216
|
+
<ModelSelector
|
|
217
|
+
onCancel={() => setShowModelSelector(false)}
|
|
218
|
+
currentModel={currentModel}
|
|
219
|
+
configuredModels={configuredModels}
|
|
220
|
+
onSelectModel={setModel}
|
|
221
|
+
/>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
192
225
|
return (
|
|
193
226
|
<Box flexDirection="column">
|
|
194
227
|
{showFileSelector && (
|
|
@@ -243,12 +276,19 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
243
276
|
<Box flexDirection="column">
|
|
244
277
|
<Box
|
|
245
278
|
borderStyle="single"
|
|
246
|
-
borderColor="gray"
|
|
279
|
+
borderColor={btwState.isActive ? "cyan" : "gray"}
|
|
247
280
|
borderLeft={false}
|
|
248
281
|
borderRight={false}
|
|
249
282
|
>
|
|
250
283
|
<Text color={isPlaceholder ? "gray" : "white"}>
|
|
251
|
-
{
|
|
284
|
+
{btwState.isActive && isPlaceholder ? (
|
|
285
|
+
<>
|
|
286
|
+
<Text backgroundColor="white" color="black">
|
|
287
|
+
{" "}
|
|
288
|
+
</Text>
|
|
289
|
+
<Text color="cyan">Type your side question...</Text>
|
|
290
|
+
</>
|
|
291
|
+
) : shouldShowCursor ? (
|
|
252
292
|
<>
|
|
253
293
|
{beforeCursor}
|
|
254
294
|
<Text backgroundColor="white" color="black">
|
|
@@ -261,21 +301,11 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
261
301
|
)}
|
|
262
302
|
</Text>
|
|
263
303
|
</Box>
|
|
264
|
-
<
|
|
265
|
-
{
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
) : (
|
|
270
|
-
<Text color="gray">
|
|
271
|
-
Mode:{" "}
|
|
272
|
-
<Text color={permissionMode === "plan" ? "yellow" : "cyan"}>
|
|
273
|
-
{permissionMode}
|
|
274
|
-
</Text>{" "}
|
|
275
|
-
(Shift+Tab to cycle)
|
|
276
|
-
</Text>
|
|
277
|
-
)}
|
|
278
|
-
</Box>
|
|
304
|
+
<StatusLine
|
|
305
|
+
permissionMode={permissionMode}
|
|
306
|
+
isShellCommand={isShellCommand}
|
|
307
|
+
isBtwActive={btwState.isActive}
|
|
308
|
+
/>
|
|
279
309
|
</Box>
|
|
280
310
|
)}
|
|
281
311
|
</Box>
|
|
@@ -3,6 +3,7 @@ import { Box, Text } from "ink";
|
|
|
3
3
|
import type { Message, MessageBlock } from "wave-agent-sdk";
|
|
4
4
|
import { MessageSource } from "wave-agent-sdk";
|
|
5
5
|
import { BangDisplay } from "./BangDisplay.js";
|
|
6
|
+
import { SlashDisplay } from "./SlashDisplay.js";
|
|
6
7
|
import { ToolDisplay } from "./ToolDisplay.js";
|
|
7
8
|
import { CompressDisplay } from "./CompressDisplay.js";
|
|
8
9
|
import { ReasoningDisplay } from "./ReasoningDisplay.js";
|
|
@@ -25,11 +26,6 @@ export const MessageBlockItem = ({
|
|
|
25
26
|
<Box flexDirection="column" paddingTop={paddingTop}>
|
|
26
27
|
{block.type === "text" && block.content.trim() && (
|
|
27
28
|
<Box>
|
|
28
|
-
{block.customCommandContent && (
|
|
29
|
-
<Text color="cyan" bold>
|
|
30
|
-
${" "}
|
|
31
|
-
</Text>
|
|
32
|
-
)}
|
|
33
29
|
{block.source === MessageSource.HOOK && (
|
|
34
30
|
<Text color="magenta" bold>
|
|
35
31
|
~{" "}
|
|
@@ -48,6 +44,10 @@ export const MessageBlockItem = ({
|
|
|
48
44
|
</Box>
|
|
49
45
|
)}
|
|
50
46
|
|
|
47
|
+
{block.type === "slash" && (
|
|
48
|
+
<SlashDisplay block={block} isExpanded={isExpanded} />
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
51
|
{block.type === "error" && (
|
|
52
52
|
<Box>
|
|
53
53
|
<Text color="red">Error: {block.content}</Text>
|
|
@@ -10,7 +10,6 @@ export interface MessageListProps {
|
|
|
10
10
|
forceStatic?: boolean;
|
|
11
11
|
version?: string;
|
|
12
12
|
workdir?: string;
|
|
13
|
-
model?: string;
|
|
14
13
|
onDynamicBlocksHeightMeasured?: (height: number) => void;
|
|
15
14
|
}
|
|
16
15
|
|
|
@@ -21,15 +20,11 @@ export const MessageList = React.memo(
|
|
|
21
20
|
forceStatic = false,
|
|
22
21
|
version,
|
|
23
22
|
workdir,
|
|
24
|
-
model,
|
|
25
23
|
onDynamicBlocksHeightMeasured,
|
|
26
24
|
}: MessageListProps) => {
|
|
27
25
|
const welcomeMessage = (
|
|
28
26
|
<Box flexDirection="column" paddingTop={1}>
|
|
29
|
-
<Text color="gray">
|
|
30
|
-
WAVE{version ? ` v${version}` : ""}
|
|
31
|
-
{model ? ` • ${model}` : ""}
|
|
32
|
-
</Text>
|
|
27
|
+
<Text color="gray">WAVE{version ? ` v${version}` : ""}</Text>
|
|
33
28
|
{workdir && (
|
|
34
29
|
<Text color="gray" wrap="truncate-middle">
|
|
35
30
|
{workdir.replace(os.homedir(), "~")}
|
|
@@ -41,8 +36,11 @@ export const MessageList = React.memo(
|
|
|
41
36
|
// Limit messages to prevent long rendering times
|
|
42
37
|
const maxMessages = 10;
|
|
43
38
|
|
|
39
|
+
// Filter out meta messages
|
|
40
|
+
const visibleMessages = messages.filter((m) => !m.isMeta);
|
|
41
|
+
|
|
44
42
|
// Flatten messages into blocks with metadata
|
|
45
|
-
const allBlocks =
|
|
43
|
+
const allBlocks = visibleMessages.flatMap((message, messageIndex) => {
|
|
46
44
|
return message.blocks.map((block, blockIndex) => ({
|
|
47
45
|
block,
|
|
48
46
|
message,
|
|
@@ -58,7 +56,8 @@ export const MessageList = React.memo(
|
|
|
58
56
|
const isDynamic =
|
|
59
57
|
!forceStatic &&
|
|
60
58
|
((block.type === "tool" && block.stage === "running") ||
|
|
61
|
-
(block.type === "bang" && block.isRunning)
|
|
59
|
+
(block.type === "bang" && block.isRunning) ||
|
|
60
|
+
(block.type === "slash" && block.stage === "running"));
|
|
62
61
|
return { ...item, isDynamic };
|
|
63
62
|
});
|
|
64
63
|
|
|
@@ -101,8 +100,8 @@ export const MessageList = React.memo(
|
|
|
101
100
|
);
|
|
102
101
|
}
|
|
103
102
|
if (
|
|
104
|
-
|
|
105
|
-
item.messageIndex <
|
|
103
|
+
visibleMessages.length > maxMessages &&
|
|
104
|
+
item.messageIndex < visibleMessages.length - maxMessages
|
|
106
105
|
) {
|
|
107
106
|
return null;
|
|
108
107
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
|
|
4
|
+
export interface ModelSelectorProps {
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
currentModel: string;
|
|
7
|
+
configuredModels: string[];
|
|
8
|
+
onSelectModel: (model: string) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
12
|
+
onCancel,
|
|
13
|
+
currentModel,
|
|
14
|
+
configuredModels,
|
|
15
|
+
onSelectModel,
|
|
16
|
+
}) => {
|
|
17
|
+
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
18
|
+
const index = configuredModels.indexOf(currentModel);
|
|
19
|
+
return index !== -1 ? index : 0;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
useInput((_input, key) => {
|
|
23
|
+
if (key.return) {
|
|
24
|
+
if (configuredModels.length > 0) {
|
|
25
|
+
onSelectModel(configuredModels[selectedIndex]);
|
|
26
|
+
}
|
|
27
|
+
onCancel();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (key.escape) {
|
|
32
|
+
onCancel();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (key.upArrow) {
|
|
37
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (key.downArrow) {
|
|
42
|
+
setSelectedIndex((prev) =>
|
|
43
|
+
Math.min(configuredModels.length - 1, prev + 1),
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (configuredModels.length === 0) {
|
|
50
|
+
return (
|
|
51
|
+
<Box
|
|
52
|
+
flexDirection="column"
|
|
53
|
+
borderStyle="single"
|
|
54
|
+
borderColor="cyan"
|
|
55
|
+
borderBottom={false}
|
|
56
|
+
borderLeft={false}
|
|
57
|
+
borderRight={false}
|
|
58
|
+
paddingTop={1}
|
|
59
|
+
>
|
|
60
|
+
<Text color="cyan" bold>
|
|
61
|
+
Select AI Model
|
|
62
|
+
</Text>
|
|
63
|
+
<Text>No models configured</Text>
|
|
64
|
+
<Text dimColor>Press Escape to close</Text>
|
|
65
|
+
</Box>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Box
|
|
71
|
+
flexDirection="column"
|
|
72
|
+
borderStyle="single"
|
|
73
|
+
borderColor="cyan"
|
|
74
|
+
borderBottom={false}
|
|
75
|
+
borderLeft={false}
|
|
76
|
+
borderRight={false}
|
|
77
|
+
paddingTop={1}
|
|
78
|
+
gap={1}
|
|
79
|
+
>
|
|
80
|
+
<Box>
|
|
81
|
+
<Text color="cyan" bold>
|
|
82
|
+
Select AI Model
|
|
83
|
+
</Text>
|
|
84
|
+
</Box>
|
|
85
|
+
<Text dimColor>Select a model to use for the current session</Text>
|
|
86
|
+
|
|
87
|
+
{configuredModels.map((model, index) => (
|
|
88
|
+
<Box key={model}>
|
|
89
|
+
<Text
|
|
90
|
+
color={index === selectedIndex ? "black" : "white"}
|
|
91
|
+
backgroundColor={index === selectedIndex ? "cyan" : undefined}
|
|
92
|
+
>
|
|
93
|
+
{index === selectedIndex ? "▶ " : " "}
|
|
94
|
+
{model}
|
|
95
|
+
{model === currentModel ? (
|
|
96
|
+
<Text color="green"> (current)</Text>
|
|
97
|
+
) : (
|
|
98
|
+
""
|
|
99
|
+
)}
|
|
100
|
+
</Text>
|
|
101
|
+
</Box>
|
|
102
|
+
))}
|
|
103
|
+
|
|
104
|
+
<Box marginTop={1}>
|
|
105
|
+
<Text dimColor>↑/↓ to select · Enter to confirm · Esc to cancel</Text>
|
|
106
|
+
</Box>
|
|
107
|
+
</Box>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
@@ -30,10 +30,10 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
|
|
|
30
30
|
}
|
|
31
31
|
}, [getFullMessageThread]);
|
|
32
32
|
|
|
33
|
-
// Filter user messages as checkpoints
|
|
33
|
+
// Filter user messages as checkpoints, excluding meta messages
|
|
34
34
|
const checkpoints = messages
|
|
35
35
|
.map((msg, index) => ({ msg, index }))
|
|
36
|
-
.filter(({ msg }) => msg.role === "user");
|
|
36
|
+
.filter(({ msg }) => msg.role === "user" && !msg.isMeta);
|
|
37
37
|
|
|
38
38
|
const MAX_VISIBLE_ITEMS = 3;
|
|
39
39
|
const [selectedIndex, setSelectedIndex] = useState(checkpoints.length - 1);
|