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.
Files changed (68) hide show
  1. package/README.md +9 -51
  2. package/dist/acp/agent.js +1 -1
  3. package/dist/components/ChatInterface.d.ts.map +1 -1
  4. package/dist/components/ChatInterface.js +2 -3
  5. package/dist/components/DiffDisplay.d.ts.map +1 -1
  6. package/dist/components/DiffDisplay.js +2 -23
  7. package/dist/components/InputBox.d.ts.map +1 -1
  8. package/dist/components/InputBox.js +6 -2
  9. package/dist/components/MessageBlockItem.d.ts.map +1 -1
  10. package/dist/components/MessageBlockItem.js +2 -1
  11. package/dist/components/MessageList.d.ts +1 -2
  12. package/dist/components/MessageList.d.ts.map +1 -1
  13. package/dist/components/MessageList.js +10 -4
  14. package/dist/components/ModelSelector.d.ts +9 -0
  15. package/dist/components/ModelSelector.d.ts.map +1 -0
  16. package/dist/components/ModelSelector.js +34 -0
  17. package/dist/components/SlashDisplay.d.ts +9 -0
  18. package/dist/components/SlashDisplay.d.ts.map +1 -0
  19. package/dist/components/SlashDisplay.js +20 -0
  20. package/dist/components/StatusCommand.d.ts.map +1 -1
  21. package/dist/components/StatusCommand.js +1 -1
  22. package/dist/components/ToolDisplay.js +1 -1
  23. package/dist/constants/commands.d.ts.map +1 -1
  24. package/dist/constants/commands.js +6 -0
  25. package/dist/contexts/useChat.d.ts +4 -2
  26. package/dist/contexts/useChat.d.ts.map +1 -1
  27. package/dist/contexts/useChat.js +27 -33
  28. package/dist/hooks/useInputManager.d.ts +2 -0
  29. package/dist/hooks/useInputManager.d.ts.map +1 -1
  30. package/dist/hooks/useInputManager.js +8 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +10 -0
  33. package/dist/managers/inputHandlers.d.ts.map +1 -1
  34. package/dist/managers/inputHandlers.js +6 -0
  35. package/dist/managers/inputReducer.d.ts +5 -0
  36. package/dist/managers/inputReducer.d.ts.map +1 -1
  37. package/dist/managers/inputReducer.js +7 -0
  38. package/dist/utils/highlightUtils.d.ts.map +1 -1
  39. package/dist/utils/highlightUtils.js +0 -8
  40. package/dist/utils/logger.d.ts.map +1 -1
  41. package/dist/utils/logger.js +10 -5
  42. package/dist/utils/toolParameterTransforms.d.ts +5 -1
  43. package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
  44. package/dist/utils/toolParameterTransforms.js +14 -0
  45. package/dist/utils/worktree.d.ts +0 -1
  46. package/dist/utils/worktree.d.ts.map +1 -1
  47. package/dist/utils/worktree.js +2 -2
  48. package/package.json +3 -3
  49. package/src/acp/agent.ts +1 -1
  50. package/src/components/ChatInterface.tsx +0 -4
  51. package/src/components/DiffDisplay.tsx +2 -27
  52. package/src/components/InputBox.tsx +17 -0
  53. package/src/components/MessageBlockItem.tsx +5 -5
  54. package/src/components/MessageList.tsx +15 -9
  55. package/src/components/ModelSelector.tsx +109 -0
  56. package/src/components/SlashDisplay.tsx +70 -0
  57. package/src/components/StatusCommand.tsx +7 -0
  58. package/src/components/ToolDisplay.tsx +1 -1
  59. package/src/constants/commands.ts +6 -0
  60. package/src/contexts/useChat.tsx +34 -49
  61. package/src/hooks/useInputManager.ts +10 -0
  62. package/src/index.ts +13 -0
  63. package/src/managers/inputHandlers.ts +5 -0
  64. package/src/managers/inputReducer.ts +10 -0
  65. package/src/utils/highlightUtils.ts +0 -8
  66. package/src/utils/logger.ts +9 -7
  67. package/src/utils/toolParameterTransforms.ts +23 -1
  68. 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>
@@ -81,7 +81,7 @@ export const ToolDisplay: React.FC<ToolDisplayProps> = ({
81
81
  flexDirection="column"
82
82
  >
83
83
  {shortResult.split("\n").map((line, index) => (
84
- <Text key={index} color="gray">
84
+ <Text key={index} color="gray" wrap="truncate-end">
85
85
  {line}
86
86
  </Text>
87
87
  ))}
@@ -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
  ];
@@ -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
- debouncedSetTasks(tasks);
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
  }
@@ -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
- return arg.stack || arg.message || String(arg);
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
- return JSON.stringify(arg, null, 2);
133
+ result = JSON.stringify(arg);
135
134
  } catch {
136
135
  // If JSON.stringify fails, fallback to String()
137
- return String(arg);
136
+ result = String(arg);
138
137
  }
138
+ } else {
139
+ result = String(arg);
139
140
  }
140
141
 
141
- return String(arg);
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 { type Change, type EditToolParameters } from "wave-agent-sdk";
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;
@@ -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 worktreePath = path.join(repoRoot, WORKTREE_DIR, name);
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