wave-code 0.11.7 → 0.12.1
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/README.md +9 -51
- package/dist/acp/agent.js +1 -1
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +2 -3
- package/dist/components/DiffDisplay.d.ts.map +1 -1
- package/dist/components/DiffDisplay.js +2 -23
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +6 -2
- 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 +10 -4
- 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/SlashDisplay.d.ts +9 -0
- package/dist/components/SlashDisplay.d.ts.map +1 -0
- package/dist/components/SlashDisplay.js +20 -0
- package/dist/components/StatusCommand.d.ts.map +1 -1
- package/dist/components/StatusCommand.js +1 -1
- package/dist/components/ToolDisplay.js +1 -1
- package/dist/constants/commands.d.ts.map +1 -1
- package/dist/constants/commands.js +6 -0
- package/dist/contexts/useChat.d.ts +4 -2
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +27 -33
- package/dist/hooks/useInputManager.d.ts +2 -0
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/managers/inputHandlers.d.ts.map +1 -1
- package/dist/managers/inputHandlers.js +6 -0
- package/dist/managers/inputReducer.d.ts +5 -0
- package/dist/managers/inputReducer.d.ts.map +1 -1
- package/dist/managers/inputReducer.js +7 -0
- package/dist/utils/highlightUtils.d.ts.map +1 -1
- package/dist/utils/highlightUtils.js +0 -8
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +10 -5
- package/dist/utils/toolParameterTransforms.d.ts +5 -1
- package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
- package/dist/utils/toolParameterTransforms.js +14 -0
- package/dist/utils/worktree.d.ts +0 -1
- package/dist/utils/worktree.d.ts.map +1 -1
- package/dist/utils/worktree.js +2 -2
- package/package.json +3 -3
- package/src/acp/agent.ts +1 -1
- package/src/components/ChatInterface.tsx +0 -4
- package/src/components/DiffDisplay.tsx +2 -27
- package/src/components/InputBox.tsx +17 -0
- package/src/components/MessageBlockItem.tsx +5 -5
- package/src/components/MessageList.tsx +15 -9
- package/src/components/ModelSelector.tsx +109 -0
- package/src/components/SlashDisplay.tsx +70 -0
- package/src/components/StatusCommand.tsx +7 -0
- package/src/components/ToolDisplay.tsx +1 -1
- package/src/constants/commands.ts +6 -0
- package/src/contexts/useChat.tsx +34 -49
- package/src/hooks/useInputManager.ts +10 -0
- package/src/index.ts +13 -0
- package/src/managers/inputHandlers.ts +5 -0
- package/src/managers/inputReducer.ts +10 -0
- package/src/utils/highlightUtils.ts +0 -8
- package/src/utils/logger.ts +9 -7
- package/src/utils/toolParameterTransforms.ts +23 -1
- package/src/utils/worktree.ts +7 -3
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import type { SlashBlock } from "wave-agent-sdk";
|
|
4
|
+
|
|
5
|
+
interface SlashDisplayProps {
|
|
6
|
+
block: SlashBlock;
|
|
7
|
+
isExpanded?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const SlashDisplay: React.FC<SlashDisplayProps> = ({
|
|
11
|
+
block,
|
|
12
|
+
isExpanded = false,
|
|
13
|
+
}) => {
|
|
14
|
+
const { command, args, stage, shortResult, error, result } = block;
|
|
15
|
+
|
|
16
|
+
const getStatusColor = () => {
|
|
17
|
+
switch (stage) {
|
|
18
|
+
case "running":
|
|
19
|
+
return "yellow";
|
|
20
|
+
case "success":
|
|
21
|
+
return "green";
|
|
22
|
+
case "error":
|
|
23
|
+
return "red";
|
|
24
|
+
case "aborted":
|
|
25
|
+
return "gray";
|
|
26
|
+
default:
|
|
27
|
+
return "white";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Box flexDirection="column">
|
|
33
|
+
<Box>
|
|
34
|
+
<Text color={getStatusColor()}>/ </Text>
|
|
35
|
+
<Text color="white">{command}</Text>
|
|
36
|
+
{args && <Text color="gray"> {args}</Text>}
|
|
37
|
+
</Box>
|
|
38
|
+
|
|
39
|
+
{/* Display shortResult in collapsed state */}
|
|
40
|
+
{!isExpanded && shortResult && !error && (
|
|
41
|
+
<Box paddingLeft={2} flexDirection="column">
|
|
42
|
+
{shortResult.split("\n").map((line, index) => (
|
|
43
|
+
<Text key={index} color="gray">
|
|
44
|
+
{line}
|
|
45
|
+
</Text>
|
|
46
|
+
))}
|
|
47
|
+
</Box>
|
|
48
|
+
)}
|
|
49
|
+
|
|
50
|
+
{/* Display complete result in expanded state */}
|
|
51
|
+
{isExpanded && result && (
|
|
52
|
+
<Box paddingLeft={2} flexDirection="column">
|
|
53
|
+
<Text color="cyan" bold>
|
|
54
|
+
Result:
|
|
55
|
+
</Text>
|
|
56
|
+
<Text color="white">{result}</Text>
|
|
57
|
+
</Box>
|
|
58
|
+
)}
|
|
59
|
+
|
|
60
|
+
{/* Error information always displayed */}
|
|
61
|
+
{error && (
|
|
62
|
+
<Box paddingLeft={2}>
|
|
63
|
+
<Text color="red">
|
|
64
|
+
Error: {typeof error === "string" ? error : String(error)}
|
|
65
|
+
</Text>
|
|
66
|
+
</Box>
|
|
67
|
+
)}
|
|
68
|
+
</Box>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
@@ -86,6 +86,13 @@ export const StatusCommand: React.FC<StatusCommandProps> = ({ onCancel }) => {
|
|
|
86
86
|
<Text color="white">{modelConfig.model}</Text>
|
|
87
87
|
</Box>
|
|
88
88
|
|
|
89
|
+
<Box>
|
|
90
|
+
<Box width={20}>
|
|
91
|
+
<Text color="yellow">Fast model:</Text>
|
|
92
|
+
</Box>
|
|
93
|
+
<Text color="white">{modelConfig.fastModel}</Text>
|
|
94
|
+
</Box>
|
|
95
|
+
|
|
89
96
|
<Box marginTop={1}>
|
|
90
97
|
<Text dimColor>Esc to cancel</Text>
|
|
91
98
|
</Box>
|
|
@@ -44,4 +44,10 @@ export const AVAILABLE_COMMANDS: SlashCommand[] = [
|
|
|
44
44
|
description: "Ask a side question without tool use",
|
|
45
45
|
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
46
46
|
},
|
|
47
|
+
{
|
|
48
|
+
id: "model",
|
|
49
|
+
name: "model",
|
|
50
|
+
description: "Switch between configured AI models",
|
|
51
|
+
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
52
|
+
},
|
|
47
53
|
];
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -54,6 +54,11 @@ export interface ChatContextType {
|
|
|
54
54
|
askBtw: (question: string) => Promise<string>;
|
|
55
55
|
abortMessage: () => void;
|
|
56
56
|
latestTotalTokens: number;
|
|
57
|
+
// Model functionality
|
|
58
|
+
currentModel: string;
|
|
59
|
+
configuredModels: string[];
|
|
60
|
+
getConfiguredModels: () => string[];
|
|
61
|
+
setModel: (model: string) => void;
|
|
57
62
|
// MCP functionality
|
|
58
63
|
mcpServers: McpServerStatus[];
|
|
59
64
|
connectMcpServer: (serverName: string) => Promise<boolean>;
|
|
@@ -72,9 +77,6 @@ export interface ChatContextType {
|
|
|
72
77
|
// Slash Command functionality
|
|
73
78
|
slashCommands: SlashCommand[];
|
|
74
79
|
hasSlashCommand: (commandId: string) => boolean;
|
|
75
|
-
// Subagent messages
|
|
76
|
-
subagentMessages: Record<string, Message[]>;
|
|
77
|
-
subagentLatestTokens: Record<string, number>;
|
|
78
80
|
// Permission functionality
|
|
79
81
|
permissionMode: PermissionMode;
|
|
80
82
|
setPermissionMode: (mode: PermissionMode) => void;
|
|
@@ -176,6 +178,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
176
178
|
const [sessionId, setSessionId] = useState("");
|
|
177
179
|
const [isCommandRunning, setIsCommandRunning] = useState(false);
|
|
178
180
|
const [isCompressing, setIsCompressing] = useState(false);
|
|
181
|
+
const [currentModel, setCurrentModelState] = useState("");
|
|
182
|
+
const [configuredModels, setConfiguredModels] = useState<string[]>([]);
|
|
179
183
|
|
|
180
184
|
// MCP State
|
|
181
185
|
const [mcpServers, setMcpServers] = useState<McpServerStatus[]>([]);
|
|
@@ -188,14 +192,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
188
192
|
// Command state
|
|
189
193
|
const [slashCommands, setSlashCommands] = useState<SlashCommand[]>([]);
|
|
190
194
|
|
|
191
|
-
// Subagent messages state
|
|
192
|
-
const [subagentMessages, setSubagentMessages] = useState<
|
|
193
|
-
Record<string, Message[]>
|
|
194
|
-
>({});
|
|
195
|
-
const [subagentLatestTokens, setSubagentLatestTokens] = useState<
|
|
196
|
-
Record<string, number>
|
|
197
|
-
>({});
|
|
198
|
-
|
|
199
195
|
// Permission state
|
|
200
196
|
const [permissionMode, setPermissionModeState] = useState<PermissionMode>(
|
|
201
197
|
initialPermissionMode ||
|
|
@@ -269,20 +265,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
269
265
|
!!bypassPermissions || initialPermissionMode === "bypassPermissions";
|
|
270
266
|
|
|
271
267
|
const agentRef = useRef<Agent | null>(null);
|
|
272
|
-
const taskUpdateTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
273
|
-
|
|
274
|
-
const debouncedSetTasks = useCallback(
|
|
275
|
-
(newTasks: Task[]) => {
|
|
276
|
-
if (taskUpdateTimerRef.current) {
|
|
277
|
-
clearTimeout(taskUpdateTimerRef.current);
|
|
278
|
-
}
|
|
279
|
-
taskUpdateTimerRef.current = setTimeout(() => {
|
|
280
|
-
setTasks([...newTasks]);
|
|
281
|
-
taskUpdateTimerRef.current = null;
|
|
282
|
-
}, 100);
|
|
283
|
-
},
|
|
284
|
-
[setTasks],
|
|
285
|
-
);
|
|
286
268
|
|
|
287
269
|
// Permission confirmation methods with queue support
|
|
288
270
|
const showConfirmation = useCallback(
|
|
@@ -336,27 +318,17 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
336
318
|
setBackgroundTasks([...tasks]);
|
|
337
319
|
},
|
|
338
320
|
onTasksChange: (tasks) => {
|
|
339
|
-
|
|
340
|
-
},
|
|
341
|
-
onSubagentMessagesChange: (subagentId: string, messages: Message[]) => {
|
|
342
|
-
logger.debug("onSubagentMessagesChange", subagentId, messages.length);
|
|
343
|
-
setSubagentMessages((prev) => ({
|
|
344
|
-
...prev,
|
|
345
|
-
[subagentId]: [...messages],
|
|
346
|
-
}));
|
|
347
|
-
},
|
|
348
|
-
onSubagentLatestTotalTokensChange: (
|
|
349
|
-
subagentId: string,
|
|
350
|
-
tokens: number,
|
|
351
|
-
) => {
|
|
352
|
-
setSubagentLatestTokens((prev) => ({
|
|
353
|
-
...prev,
|
|
354
|
-
[subagentId]: tokens,
|
|
355
|
-
}));
|
|
321
|
+
setTasks([...tasks]);
|
|
356
322
|
},
|
|
357
323
|
onPermissionModeChange: (mode) => {
|
|
358
324
|
setPermissionModeState(mode);
|
|
359
325
|
},
|
|
326
|
+
onModelChange: (model) => {
|
|
327
|
+
setCurrentModelState(model);
|
|
328
|
+
},
|
|
329
|
+
onConfiguredModelsChange: (models) => {
|
|
330
|
+
setConfiguredModels(models);
|
|
331
|
+
},
|
|
360
332
|
};
|
|
361
333
|
|
|
362
334
|
try {
|
|
@@ -414,6 +386,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
414
386
|
setIsCompressing(agent.isCompressing);
|
|
415
387
|
setPermissionModeState(agent.getPermissionMode());
|
|
416
388
|
setWorkingDirectory(agent.workingDirectory);
|
|
389
|
+
setCurrentModelState(agent.getModelConfig().model);
|
|
390
|
+
setConfiguredModels(agent.getConfiguredModels());
|
|
417
391
|
|
|
418
392
|
// Get initial MCP servers state
|
|
419
393
|
const mcpServers = agent.getMcpServers?.() || [];
|
|
@@ -440,17 +414,12 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
440
414
|
workdir,
|
|
441
415
|
worktreeSession,
|
|
442
416
|
model,
|
|
443
|
-
debouncedSetTasks,
|
|
444
417
|
initialPermissionMode,
|
|
445
418
|
]);
|
|
446
419
|
|
|
447
420
|
// Cleanup on unmount
|
|
448
421
|
useEffect(() => {
|
|
449
422
|
return () => {
|
|
450
|
-
if (taskUpdateTimerRef.current) {
|
|
451
|
-
clearTimeout(taskUpdateTimerRef.current);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
423
|
if (agentRef.current) {
|
|
455
424
|
try {
|
|
456
425
|
// Display usage summary before cleanup
|
|
@@ -672,6 +641,20 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
672
641
|
return agentRef.current.getModelConfig();
|
|
673
642
|
}, []);
|
|
674
643
|
|
|
644
|
+
const getConfiguredModels = useCallback(() => {
|
|
645
|
+
if (!agentRef.current) {
|
|
646
|
+
return [];
|
|
647
|
+
}
|
|
648
|
+
return agentRef.current.getConfiguredModels();
|
|
649
|
+
}, []);
|
|
650
|
+
|
|
651
|
+
const setModel = useCallback((model: string) => {
|
|
652
|
+
if (agentRef.current) {
|
|
653
|
+
agentRef.current.setModel(model);
|
|
654
|
+
setCurrentModelState(model);
|
|
655
|
+
}
|
|
656
|
+
}, []);
|
|
657
|
+
|
|
675
658
|
// Listen for Ctrl+O hotkey to toggle collapse/expand state and ESC to cancel confirmation
|
|
676
659
|
useInput((input, key) => {
|
|
677
660
|
if (key.ctrl && input === "o") {
|
|
@@ -729,6 +712,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
729
712
|
askBtw,
|
|
730
713
|
abortMessage,
|
|
731
714
|
latestTotalTokens,
|
|
715
|
+
currentModel,
|
|
716
|
+
configuredModels,
|
|
717
|
+
getConfiguredModels,
|
|
718
|
+
setModel,
|
|
732
719
|
isCompressing,
|
|
733
720
|
mcpServers,
|
|
734
721
|
connectMcpServer,
|
|
@@ -739,8 +726,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
739
726
|
stopBackgroundTask,
|
|
740
727
|
slashCommands,
|
|
741
728
|
hasSlashCommand,
|
|
742
|
-
subagentMessages,
|
|
743
|
-
subagentLatestTokens,
|
|
744
729
|
permissionMode,
|
|
745
730
|
setPermissionMode,
|
|
746
731
|
allowBypassInCycle,
|
|
@@ -149,6 +149,10 @@ export const useInputManager = (
|
|
|
149
149
|
callbacksRef.current.onPluginManagerStateChange?.(state.showPluginManager);
|
|
150
150
|
}, [state.showPluginManager]);
|
|
151
151
|
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
callbacksRef.current.onModelSelectorStateChange?.(state.showModelSelector);
|
|
154
|
+
}, [state.showModelSelector]);
|
|
155
|
+
|
|
152
156
|
useEffect(() => {
|
|
153
157
|
callbacksRef.current.onImagesStateChange?.(state.attachedImages);
|
|
154
158
|
}, [state.attachedImages]);
|
|
@@ -343,6 +347,10 @@ export const useInputManager = (
|
|
|
343
347
|
dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: show });
|
|
344
348
|
}, []);
|
|
345
349
|
|
|
350
|
+
const setShowModelSelector = useCallback((show: boolean) => {
|
|
351
|
+
dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: show });
|
|
352
|
+
}, []);
|
|
353
|
+
|
|
346
354
|
const setPermissionMode = useCallback((mode: PermissionMode) => {
|
|
347
355
|
dispatch({ type: "SET_PERMISSION_MODE", payload: mode });
|
|
348
356
|
callbacksRef.current.onPermissionModeChange?.(mode);
|
|
@@ -452,6 +460,7 @@ export const useInputManager = (
|
|
|
452
460
|
showHelp: state.showHelp,
|
|
453
461
|
showStatusCommand: state.showStatusCommand,
|
|
454
462
|
showPluginManager: state.showPluginManager,
|
|
463
|
+
showModelSelector: state.showModelSelector,
|
|
455
464
|
permissionMode: state.permissionMode,
|
|
456
465
|
attachedImages: state.attachedImages,
|
|
457
466
|
btwState: state.btwState,
|
|
@@ -493,6 +502,7 @@ export const useInputManager = (
|
|
|
493
502
|
setShowHelp,
|
|
494
503
|
setShowStatusCommand,
|
|
495
504
|
setShowPluginManager,
|
|
505
|
+
setShowModelSelector,
|
|
496
506
|
setPermissionMode,
|
|
497
507
|
setAllowBypassInCycle,
|
|
498
508
|
setBtwState,
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import yargs from "yargs";
|
|
2
2
|
import { hideBin } from "yargs/helpers";
|
|
3
3
|
import { startCli } from "./cli.js";
|
|
4
|
+
import { logger } from "./utils/logger.js";
|
|
4
5
|
import { Scope, generateRandomName, type PermissionMode } from "wave-agent-sdk";
|
|
5
6
|
import { createWorktree, type WorktreeSession } from "./utils/worktree.js";
|
|
6
7
|
import path from "path";
|
|
@@ -14,6 +15,18 @@ const version = packageJson.version;
|
|
|
14
15
|
|
|
15
16
|
// Export main function for external use
|
|
16
17
|
export async function main() {
|
|
18
|
+
// Start memory monitoring in debug mode
|
|
19
|
+
if (process.env.LOG_LEVEL === "DEBUG") {
|
|
20
|
+
setInterval(() => {
|
|
21
|
+
const usage = process.memoryUsage();
|
|
22
|
+
logger.debug(
|
|
23
|
+
`[Memory] RSS: ${Math.round(usage.rss / 1024 / 1024)}MB, ` +
|
|
24
|
+
`Heap: ${Math.round(usage.heapUsed / 1024 / 1024)}/${Math.round(usage.heapTotal / 1024 / 1024)}MB, ` +
|
|
25
|
+
`External: ${Math.round(usage.external / 1024 / 1024)}MB`,
|
|
26
|
+
);
|
|
27
|
+
}, 10000).unref();
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
try {
|
|
18
31
|
const originalCwd = process.cwd();
|
|
19
32
|
const argv = await yargs(hideBin(process.argv))
|
|
@@ -370,6 +370,8 @@ export const handleCommandSelect = (
|
|
|
370
370
|
dispatch({ type: "SET_SHOW_STATUS_COMMAND", payload: true });
|
|
371
371
|
} else if (command === "plugin") {
|
|
372
372
|
dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
|
|
373
|
+
} else if (command === "model") {
|
|
374
|
+
dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: true });
|
|
373
375
|
} else if (command === "btw") {
|
|
374
376
|
dispatch({
|
|
375
377
|
type: "SET_BTW_STATE",
|
|
@@ -754,6 +756,7 @@ export const handleInput = async (
|
|
|
754
756
|
state.showHelp ||
|
|
755
757
|
state.showStatusCommand ||
|
|
756
758
|
state.showPluginManager ||
|
|
759
|
+
state.showModelSelector ||
|
|
757
760
|
state.btwState.isActive
|
|
758
761
|
)
|
|
759
762
|
) {
|
|
@@ -782,6 +785,7 @@ export const handleInput = async (
|
|
|
782
785
|
state.showHelp ||
|
|
783
786
|
state.showStatusCommand ||
|
|
784
787
|
state.showPluginManager ||
|
|
788
|
+
state.showModelSelector ||
|
|
785
789
|
state.btwState.isActive
|
|
786
790
|
) {
|
|
787
791
|
if (
|
|
@@ -791,6 +795,7 @@ export const handleInput = async (
|
|
|
791
795
|
state.showHelp ||
|
|
792
796
|
state.showStatusCommand ||
|
|
793
797
|
state.showPluginManager ||
|
|
798
|
+
state.showModelSelector ||
|
|
794
799
|
state.btwState.isActive
|
|
795
800
|
) {
|
|
796
801
|
return true;
|
|
@@ -40,6 +40,7 @@ export interface InputManagerCallbacks {
|
|
|
40
40
|
onHelpStateChange?: (show: boolean) => void;
|
|
41
41
|
onStatusCommandStateChange?: (show: boolean) => void;
|
|
42
42
|
onPluginManagerStateChange?: (show: boolean) => void;
|
|
43
|
+
onModelSelectorStateChange?: (show: boolean) => void;
|
|
43
44
|
onImagesStateChange?: (images: AttachedImage[]) => void;
|
|
44
45
|
onSendMessage?: (
|
|
45
46
|
content: string,
|
|
@@ -84,6 +85,7 @@ export interface InputState {
|
|
|
84
85
|
showHelp: boolean;
|
|
85
86
|
showStatusCommand: boolean;
|
|
86
87
|
showPluginManager: boolean;
|
|
88
|
+
showModelSelector: boolean;
|
|
87
89
|
permissionMode: PermissionMode;
|
|
88
90
|
allowBypassInCycle: boolean;
|
|
89
91
|
selectorJustUsed: boolean;
|
|
@@ -120,6 +122,7 @@ export const initialState: InputState = {
|
|
|
120
122
|
showHelp: false,
|
|
121
123
|
showStatusCommand: false,
|
|
122
124
|
showPluginManager: false,
|
|
125
|
+
showModelSelector: false,
|
|
123
126
|
permissionMode: "default",
|
|
124
127
|
allowBypassInCycle: false,
|
|
125
128
|
selectorJustUsed: false,
|
|
@@ -163,6 +166,7 @@ export type InputAction =
|
|
|
163
166
|
| { type: "SET_SHOW_HELP"; payload: boolean }
|
|
164
167
|
| { type: "SET_SHOW_STATUS_COMMAND"; payload: boolean }
|
|
165
168
|
| { type: "SET_SHOW_PLUGIN_MANAGER"; payload: boolean }
|
|
169
|
+
| { type: "SET_SHOW_MODEL_SELECTOR"; payload: boolean }
|
|
166
170
|
| { type: "SET_PERMISSION_MODE"; payload: PermissionMode }
|
|
167
171
|
| { type: "SET_ALLOW_BYPASS_IN_CYCLE"; payload: boolean }
|
|
168
172
|
| { type: "SET_SELECTOR_JUST_USED"; payload: boolean }
|
|
@@ -358,6 +362,12 @@ export function inputReducer(
|
|
|
358
362
|
showPluginManager: action.payload,
|
|
359
363
|
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
360
364
|
};
|
|
365
|
+
case "SET_SHOW_MODEL_SELECTOR":
|
|
366
|
+
return {
|
|
367
|
+
...state,
|
|
368
|
+
showModelSelector: action.payload,
|
|
369
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
370
|
+
};
|
|
361
371
|
case "SET_PERMISSION_MODE":
|
|
362
372
|
return { ...state, permissionMode: action.payload };
|
|
363
373
|
case "SET_ALLOW_BYPASS_IN_CYCLE":
|
|
@@ -49,14 +49,6 @@ function nodeToAnsi(node: Node): string {
|
|
|
49
49
|
|
|
50
50
|
for (const className of classes) {
|
|
51
51
|
if (theme[className]) {
|
|
52
|
-
// If content has newlines, split it and apply style to each line
|
|
53
|
-
// to ensure ANSI codes are correctly applied when splitting the final string by lines.
|
|
54
|
-
if (content.includes("\n")) {
|
|
55
|
-
return content
|
|
56
|
-
.split("\n")
|
|
57
|
-
.map((line) => theme[className](line))
|
|
58
|
-
.join("\n");
|
|
59
|
-
}
|
|
60
52
|
return theme[className](content);
|
|
61
53
|
}
|
|
62
54
|
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -124,21 +124,23 @@ const formatArg = (arg: unknown): string => {
|
|
|
124
124
|
if (arg === null) return "null";
|
|
125
125
|
if (arg === undefined) return "undefined";
|
|
126
126
|
|
|
127
|
+
let result: string;
|
|
127
128
|
if (arg instanceof Error) {
|
|
128
129
|
// Special handling for Error objects, display stack or message
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (typeof arg === "object") {
|
|
130
|
+
result = arg.stack || arg.message || String(arg);
|
|
131
|
+
} else if (typeof arg === "object") {
|
|
133
132
|
try {
|
|
134
|
-
|
|
133
|
+
result = JSON.stringify(arg);
|
|
135
134
|
} catch {
|
|
136
135
|
// If JSON.stringify fails, fallback to String()
|
|
137
|
-
|
|
136
|
+
result = String(arg);
|
|
138
137
|
}
|
|
138
|
+
} else {
|
|
139
|
+
result = String(arg);
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
|
|
142
|
+
// Ensure no newlines in the result to keep log entries single-line
|
|
143
|
+
return result.replace(/[\n\r]+/g, " ");
|
|
142
144
|
};
|
|
143
145
|
|
|
144
146
|
/**
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
* Forces type judgment based on tool name using type assertions
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
type Change,
|
|
8
|
+
type WriteToolParameters,
|
|
9
|
+
type EditToolParameters,
|
|
10
|
+
} from "wave-agent-sdk";
|
|
7
11
|
import { logger } from "./logger.js";
|
|
8
12
|
|
|
9
13
|
/**
|
|
@@ -22,6 +26,20 @@ function parseToolParameters(parameters: string): unknown {
|
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Transform Write tool parameters to changes
|
|
31
|
+
*/
|
|
32
|
+
export function transformWriteParameters(
|
|
33
|
+
parameters: WriteToolParameters,
|
|
34
|
+
): Change[] {
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
oldContent: "", // No previous content for write operations
|
|
38
|
+
newContent: parameters.content,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
/**
|
|
26
44
|
* Transform Edit tool parameters to changes
|
|
27
45
|
*/
|
|
@@ -54,6 +72,10 @@ export function transformToolBlockToChanges(
|
|
|
54
72
|
|
|
55
73
|
let changes: Change[] = [];
|
|
56
74
|
switch (toolName) {
|
|
75
|
+
case "Write":
|
|
76
|
+
changes = transformWriteParameters(parsedParams as WriteToolParameters);
|
|
77
|
+
break;
|
|
78
|
+
|
|
57
79
|
case "Edit":
|
|
58
80
|
changes = transformEditParameters(parsedParams as EditToolParameters);
|
|
59
81
|
break;
|
package/src/utils/worktree.ts
CHANGED
|
@@ -13,8 +13,6 @@ export interface WorktreeSession {
|
|
|
13
13
|
isNew: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export const WORKTREE_DIR = ".wave/worktrees";
|
|
17
|
-
|
|
18
16
|
/**
|
|
19
17
|
* Create a new git worktree
|
|
20
18
|
* @param name Worktree name
|
|
@@ -23,7 +21,13 @@ export const WORKTREE_DIR = ".wave/worktrees";
|
|
|
23
21
|
*/
|
|
24
22
|
export function createWorktree(name: string, cwd: string): WorktreeSession {
|
|
25
23
|
const repoRoot = getGitMainRepoRoot(cwd);
|
|
26
|
-
const
|
|
24
|
+
const projectName = path.basename(repoRoot);
|
|
25
|
+
const worktreePath = path.join(
|
|
26
|
+
repoRoot,
|
|
27
|
+
"..",
|
|
28
|
+
`${projectName}.worktrees`,
|
|
29
|
+
name,
|
|
30
|
+
);
|
|
27
31
|
const branchName = `worktree-${name}`;
|
|
28
32
|
const baseBranch = getDefaultRemoteBranch(cwd);
|
|
29
33
|
|