wave-code 0.8.4 → 0.9.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 (54) hide show
  1. package/dist/components/ChatInterface.d.ts.map +1 -1
  2. package/dist/components/ChatInterface.js +26 -7
  3. package/dist/components/FileSelector.d.ts +1 -0
  4. package/dist/components/FileSelector.d.ts.map +1 -1
  5. package/dist/components/FileSelector.js +3 -3
  6. package/dist/components/HelpView.d.ts.map +1 -1
  7. package/dist/components/HelpView.js +1 -0
  8. package/dist/components/HistorySearch.d.ts +2 -1
  9. package/dist/components/HistorySearch.d.ts.map +1 -1
  10. package/dist/components/HistorySearch.js +1 -1
  11. package/dist/components/InputBox.d.ts.map +1 -1
  12. package/dist/components/InputBox.js +8 -6
  13. package/dist/components/MessageBlockItem.d.ts.map +1 -1
  14. package/dist/components/MessageBlockItem.js +1 -1
  15. package/dist/components/MessageList.d.ts +2 -1
  16. package/dist/components/MessageList.d.ts.map +1 -1
  17. package/dist/components/MessageList.js +14 -4
  18. package/dist/components/QueuedMessageList.d.ts +3 -0
  19. package/dist/components/QueuedMessageList.d.ts.map +1 -0
  20. package/dist/components/QueuedMessageList.js +17 -0
  21. package/dist/components/ReasoningDisplay.d.ts +1 -0
  22. package/dist/components/ReasoningDisplay.d.ts.map +1 -1
  23. package/dist/components/ReasoningDisplay.js +3 -3
  24. package/dist/components/ToolDisplay.js +1 -1
  25. package/dist/contexts/useChat.d.ts +7 -0
  26. package/dist/contexts/useChat.d.ts.map +1 -1
  27. package/dist/contexts/useChat.js +17 -1
  28. package/dist/hooks/useInputManager.d.ts +6 -5
  29. package/dist/hooks/useInputManager.d.ts.map +1 -1
  30. package/dist/hooks/useInputManager.js +18 -16
  31. package/dist/managers/inputHandlers.d.ts +7 -4
  32. package/dist/managers/inputHandlers.d.ts.map +1 -1
  33. package/dist/managers/inputHandlers.js +184 -46
  34. package/dist/managers/inputReducer.d.ts +18 -2
  35. package/dist/managers/inputReducer.d.ts.map +1 -1
  36. package/dist/managers/inputReducer.js +92 -3
  37. package/dist/utils/logger.d.ts.map +1 -1
  38. package/dist/utils/logger.js +13 -1
  39. package/package.json +2 -2
  40. package/src/components/ChatInterface.tsx +42 -15
  41. package/src/components/FileSelector.tsx +13 -3
  42. package/src/components/HelpView.tsx +1 -0
  43. package/src/components/HistorySearch.tsx +2 -2
  44. package/src/components/InputBox.tsx +21 -17
  45. package/src/components/MessageBlockItem.tsx +8 -3
  46. package/src/components/MessageList.tsx +16 -3
  47. package/src/components/QueuedMessageList.tsx +31 -0
  48. package/src/components/ReasoningDisplay.tsx +8 -2
  49. package/src/components/ToolDisplay.tsx +2 -2
  50. package/src/contexts/useChat.tsx +29 -1
  51. package/src/hooks/useInputManager.ts +22 -31
  52. package/src/managers/inputHandlers.ts +223 -60
  53. package/src/managers/inputReducer.ts +104 -6
  54. package/src/utils/logger.ts +15 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.8.4",
3
+ "version": "0.9.1",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,7 +39,7 @@
39
39
  "react": "^19.2.4",
40
40
  "react-dom": "19.2.4",
41
41
  "yargs": "^17.7.2",
42
- "wave-agent-sdk": "0.8.4"
42
+ "wave-agent-sdk": "0.9.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/react": "^19.1.8",
@@ -4,6 +4,7 @@ import { MessageList } from "./MessageList.js";
4
4
  import { InputBox } from "./InputBox.js";
5
5
  import { LoadingIndicator } from "./LoadingIndicator.js";
6
6
  import { TaskList } from "./TaskList.js";
7
+ import { QueuedMessageList } from "./QueuedMessageList.js";
7
8
  import { ConfirmationDetails } from "./ConfirmationDetails.js";
8
9
  import { ConfirmationSelector } from "./ConfirmationSelector.js";
9
10
 
@@ -14,6 +15,7 @@ export const ChatInterface: React.FC = () => {
14
15
  const { stdout } = useStdout();
15
16
  const [detailsHeight, setDetailsHeight] = useState(0);
16
17
  const [selectorHeight, setSelectorHeight] = useState(0);
18
+ const [dynamicBlocksHeight, setDynamicBlocksHeight] = useState(0);
17
19
  const [isConfirmationTooTall, setIsConfirmationTooTall] = useState(false);
18
20
 
19
21
  const {
@@ -51,15 +53,36 @@ export const ChatInterface: React.FC = () => {
51
53
  setSelectorHeight(height);
52
54
  }, []);
53
55
 
56
+ const handleDynamicBlocksHeightMeasured = useCallback((height: number) => {
57
+ setDynamicBlocksHeight(height);
58
+ }, []);
59
+
54
60
  useLayoutEffect(() => {
61
+ if (!isConfirmationVisible) {
62
+ setIsConfirmationTooTall(false);
63
+ setDetailsHeight(0);
64
+ setSelectorHeight(0);
65
+ setDynamicBlocksHeight(0);
66
+ return;
67
+ }
68
+
69
+ if (isConfirmationTooTall) {
70
+ return;
71
+ }
72
+
55
73
  const terminalHeight = stdout?.rows || 24;
56
- const totalHeight = detailsHeight + selectorHeight;
74
+ const totalHeight = detailsHeight + selectorHeight + dynamicBlocksHeight;
57
75
  if (totalHeight > terminalHeight) {
58
76
  setIsConfirmationTooTall(true);
59
- } else {
60
- setIsConfirmationTooTall(false);
61
77
  }
62
- }, [detailsHeight, selectorHeight, stdout?.rows]);
78
+ }, [
79
+ detailsHeight,
80
+ selectorHeight,
81
+ dynamicBlocksHeight,
82
+ stdout?.rows,
83
+ isConfirmationVisible,
84
+ isConfirmationTooTall,
85
+ ]);
63
86
 
64
87
  const handleConfirmationCancel = useCallback(() => {
65
88
  if (isConfirmationTooTall) {
@@ -99,6 +122,7 @@ export const ChatInterface: React.FC = () => {
99
122
  version={version}
100
123
  workdir={workdir}
101
124
  model={model}
125
+ onDynamicBlocksHeightMeasured={handleDynamicBlocksHeightMeasured}
102
126
  />
103
127
 
104
128
  {(isLoading || isCommandRunning || isCompressing) &&
@@ -137,17 +161,20 @@ export const ChatInterface: React.FC = () => {
137
161
  )}
138
162
 
139
163
  {!isConfirmationVisible && !isExpanded && (
140
- <InputBox
141
- isLoading={isLoading}
142
- isCommandRunning={isCommandRunning}
143
- sendMessage={sendMessage}
144
- abortMessage={abortMessage}
145
- mcpServers={mcpServers}
146
- connectMcpServer={connectMcpServer}
147
- disconnectMcpServer={disconnectMcpServer}
148
- slashCommands={slashCommands}
149
- hasSlashCommand={hasSlashCommand}
150
- />
164
+ <>
165
+ <QueuedMessageList />
166
+ <InputBox
167
+ isLoading={isLoading}
168
+ isCommandRunning={isCommandRunning}
169
+ sendMessage={sendMessage}
170
+ abortMessage={abortMessage}
171
+ mcpServers={mcpServers}
172
+ connectMcpServer={connectMcpServer}
173
+ disconnectMcpServer={disconnectMcpServer}
174
+ slashCommands={slashCommands}
175
+ hasSlashCommand={hasSlashCommand}
176
+ />
177
+ </>
151
178
  )}
152
179
  </Box>
153
180
  );
@@ -7,6 +7,7 @@ export { type FileItem } from "wave-agent-sdk";
7
7
  export interface FileSelectorProps {
8
8
  files: FileItem[];
9
9
  searchQuery: string;
10
+ isLoading?: boolean;
10
11
  onSelect: (filePath: string) => void;
11
12
  onCancel: () => void;
12
13
  }
@@ -14,6 +15,7 @@ export interface FileSelectorProps {
14
15
  export const FileSelector: React.FC<FileSelectorProps> = ({
15
16
  files,
16
17
  searchQuery,
18
+ isLoading = false,
17
19
  onSelect,
18
20
  onCancel,
19
21
  }) => {
@@ -48,13 +50,21 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
48
50
  <Box
49
51
  flexDirection="column"
50
52
  borderStyle="single"
51
- borderColor="yellow"
53
+ borderColor={isLoading ? "cyan" : "yellow"}
52
54
  borderBottom={false}
53
55
  borderLeft={false}
54
56
  borderRight={false}
55
57
  >
56
- <Text color="yellow">No files found for "{searchQuery}"</Text>
57
- <Text dimColor>Press Escape to cancel</Text>
58
+ {isLoading ? (
59
+ <Text color="cyan" bold>
60
+ Select File/Directory...
61
+ </Text>
62
+ ) : (
63
+ <>
64
+ <Text color="yellow">No files found for "{searchQuery}"</Text>
65
+ <Text dimColor>Press Escape to cancel</Text>
66
+ </>
67
+ )}
58
68
  </Box>
59
69
  );
60
70
  }
@@ -64,6 +64,7 @@ export const HelpView: React.FC<HelpViewProps> = ({
64
64
  { key: "Ctrl+T", description: "Toggle task list" },
65
65
  { key: "Ctrl+B", description: "Background current task" },
66
66
  { key: "Ctrl+V", description: "Paste image" },
67
+ { key: "Ctrl+J", description: "Newline" },
67
68
  { key: "Shift+Tab", description: "Cycle permission mode" },
68
69
  {
69
70
  key: "Esc",
@@ -4,7 +4,7 @@ import { PromptHistoryManager, type PromptEntry } from "wave-agent-sdk";
4
4
 
5
5
  export interface HistorySearchProps {
6
6
  searchQuery: string;
7
- onSelect: (prompt: string) => void;
7
+ onSelect: (entry: PromptEntry) => void;
8
8
  onCancel: () => void;
9
9
  }
10
10
 
@@ -44,7 +44,7 @@ export const HistorySearch: React.FC<HistorySearchProps> = ({
44
44
  entriesRef.current.length > 0 &&
45
45
  selectedIndexRef.current < entriesRef.current.length
46
46
  ) {
47
- onSelect(entriesRef.current[selectedIndexRef.current].prompt);
47
+ onSelect(entriesRef.current[selectedIndexRef.current]);
48
48
  }
49
49
  return;
50
50
  }
@@ -41,8 +41,6 @@ export interface InputBoxProps {
41
41
  }
42
42
 
43
43
  export const InputBox: React.FC<InputBoxProps> = ({
44
- isLoading = false,
45
- isCommandRunning = false,
46
44
  sendMessage = () => {},
47
45
  abortMessage = () => {},
48
46
  mcpServers = [],
@@ -59,6 +57,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
59
57
  messages,
60
58
  getFullMessageThread,
61
59
  clearMessages,
60
+ sessionId,
62
61
  } = useChat();
63
62
 
64
63
  // Input manager with all input state and functionality (including images)
@@ -72,6 +71,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
72
71
  showFileSelector,
73
72
  filteredFiles,
74
73
  fileSearchQuery: searchQuery,
74
+ isFileSearching,
75
75
  handleFileSelect,
76
76
  handleCancelFileSelect,
77
77
  // Command selector
@@ -110,6 +110,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
110
110
  onBackgroundCurrentTask: backgroundCurrentTask,
111
111
  onPermissionModeChange: setChatPermissionMode,
112
112
  onClearMessages: clearMessages,
113
+ sessionId,
113
114
  });
114
115
 
115
116
  // Sync permission mode from useChat to InputManager
@@ -119,14 +120,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
119
120
 
120
121
  // Use the InputManager's unified input handler
121
122
  useInput(async (input, key) => {
122
- await handleInput(
123
- input,
124
- key,
125
- attachedImages,
126
- isLoading,
127
- isCommandRunning,
128
- clearImages,
129
- );
123
+ await handleInput(input, key, attachedImages, clearImages);
130
124
  });
131
125
 
132
126
  const handleRewindCancel = () => {
@@ -138,6 +132,9 @@ export const InputBox: React.FC<InputBoxProps> = ({
138
132
  const isPlaceholder = !inputText;
139
133
  const placeholderText = INPUT_PLACEHOLDER_TEXT;
140
134
 
135
+ const isShellCommand =
136
+ inputText?.startsWith("!") && !inputText.includes("\n");
137
+
141
138
  // handleCommandSelectorInsert is already memoized in useInputManager, no need to wrap again
142
139
 
143
140
  // Split text into three parts: before cursor, cursor position, after cursor
@@ -189,6 +186,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
189
186
  <FileSelector
190
187
  files={filteredFiles}
191
188
  searchQuery={searchQuery}
189
+ isLoading={isFileSearching}
192
190
  onSelect={handleFileSelect}
193
191
  onCancel={handleCancelFileSelect}
194
192
  />
@@ -254,13 +252,19 @@ export const InputBox: React.FC<InputBoxProps> = ({
254
252
  </Text>
255
253
  </Box>
256
254
  <Box paddingRight={1} justifyContent="space-between" width="100%">
257
- <Text color="gray">
258
- Mode:{" "}
259
- <Text color={permissionMode === "plan" ? "yellow" : "cyan"}>
260
- {permissionMode}
261
- </Text>{" "}
262
- (Shift+Tab to cycle)
263
- </Text>
255
+ {isShellCommand ? (
256
+ <Text color="gray">
257
+ Shell: <Text color="yellow">Run shell command</Text>
258
+ </Text>
259
+ ) : (
260
+ <Text color="gray">
261
+ Mode:{" "}
262
+ <Text color={permissionMode === "plan" ? "yellow" : "cyan"}>
263
+ {permissionMode}
264
+ </Text>{" "}
265
+ (Shift+Tab to cycle)
266
+ </Text>
267
+ )}
264
268
  </Box>
265
269
  </Box>
266
270
  )}
@@ -35,8 +35,11 @@ export const MessageBlockItem = ({
35
35
  ~{" "}
36
36
  </Text>
37
37
  )}
38
- {message.role === "user" ? (
39
- <Text backgroundColor="gray" color="white">
38
+ {message.role === "user" || isExpanded ? (
39
+ <Text
40
+ backgroundColor={message.role === "user" ? "gray" : undefined}
41
+ color="white"
42
+ >
40
43
  {block.content}
41
44
  </Text>
42
45
  ) : (
@@ -77,7 +80,9 @@ export const MessageBlockItem = ({
77
80
  <CompressDisplay block={block} isExpanded={isExpanded} />
78
81
  )}
79
82
 
80
- {block.type === "reasoning" && <ReasoningDisplay block={block} />}
83
+ {block.type === "reasoning" && (
84
+ <ReasoningDisplay block={block} isExpanded={isExpanded} />
85
+ )}
81
86
  </Box>
82
87
  );
83
88
  };
@@ -1,6 +1,6 @@
1
- import React from "react";
1
+ import React, { useLayoutEffect, useRef } from "react";
2
2
  import os from "os";
3
- import { Box, Text, Static } from "ink";
3
+ import { Box, Text, Static, measureElement } from "ink";
4
4
  import type { Message } from "wave-agent-sdk";
5
5
  import { MessageBlockItem } from "./MessageBlockItem.js";
6
6
 
@@ -11,6 +11,7 @@ export interface MessageListProps {
11
11
  version?: string;
12
12
  workdir?: string;
13
13
  model?: string;
14
+ onDynamicBlocksHeightMeasured?: (height: number) => void;
14
15
  }
15
16
 
16
17
  export const MessageList = React.memo(
@@ -21,6 +22,7 @@ export const MessageList = React.memo(
21
22
  version,
22
23
  workdir,
23
24
  model,
25
+ onDynamicBlocksHeightMeasured,
24
26
  }: MessageListProps) => {
25
27
  const welcomeMessage = (
26
28
  <Box flexDirection="column" paddingTop={1}>
@@ -72,6 +74,17 @@ export const MessageList = React.memo(
72
74
  const staticBlocks = blocksWithStatus.filter((b) => !b.isDynamic);
73
75
  const dynamicBlocks = blocksWithStatus.filter((b) => b.isDynamic);
74
76
 
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
+
75
88
  const staticItems = [
76
89
  { isWelcome: true, key: "welcome", block: undefined, message: undefined },
77
90
  ...staticBlocks.map((b) => ({ ...b, isWelcome: false })),
@@ -105,7 +118,7 @@ export const MessageList = React.memo(
105
118
 
106
119
  {/* Dynamic blocks */}
107
120
  {dynamicBlocks.length > 0 && (
108
- <Box flexDirection="column">
121
+ <Box ref={dynamicBlocksRef} flexDirection="column">
109
122
  {dynamicBlocks.map((item) => (
110
123
  <MessageBlockItem
111
124
  key={item.key}
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import { useChat } from "../contexts/useChat.js";
3
+ import { Box, Text } from "ink";
4
+
5
+ export const QueuedMessageList: React.FC = () => {
6
+ const { queuedMessages = [] } = useChat();
7
+
8
+ if (queuedMessages.length === 0) {
9
+ return null;
10
+ }
11
+
12
+ return (
13
+ <Box flexDirection="column">
14
+ {queuedMessages.map((msg, index) => {
15
+ const content = msg.content.trim();
16
+ const hasImages = msg.images && msg.images.length > 0;
17
+ const displayText = content || (hasImages ? "[Images]" : "");
18
+
19
+ return (
20
+ <Box key={index}>
21
+ <Text color="gray" italic>
22
+ {displayText.length > 60
23
+ ? `${displayText.substring(0, 57)}...`
24
+ : displayText}
25
+ </Text>
26
+ </Box>
27
+ );
28
+ })}
29
+ </Box>
30
+ );
31
+ };
@@ -1,14 +1,16 @@
1
1
  import React from "react";
2
- import { Box } from "ink";
2
+ import { Box, Text } from "ink";
3
3
  import type { ReasoningBlock } from "wave-agent-sdk";
4
4
  import { Markdown } from "./Markdown.js";
5
5
 
6
6
  interface ReasoningDisplayProps {
7
7
  block: ReasoningBlock;
8
+ isExpanded?: boolean;
8
9
  }
9
10
 
10
11
  export const ReasoningDisplay: React.FC<ReasoningDisplayProps> = ({
11
12
  block,
13
+ isExpanded = false,
12
14
  }) => {
13
15
  const { content } = block;
14
16
 
@@ -26,7 +28,11 @@ export const ReasoningDisplay: React.FC<ReasoningDisplayProps> = ({
26
28
  paddingLeft={1}
27
29
  >
28
30
  <Box flexDirection="column">
29
- <Markdown>{content}</Markdown>
31
+ {isExpanded ? (
32
+ <Text color="white">{content}</Text>
33
+ ) : (
34
+ <Markdown>{content}</Markdown>
35
+ )}
30
36
  </Box>
31
37
  </Box>
32
38
  );
@@ -129,8 +129,8 @@ export const ToolDisplay: React.FC<ToolDisplayProps> = ({
129
129
  </Box>
130
130
  )}
131
131
 
132
- {/* Diff display - only show after tool execution completes and was successful */}
133
- {stage === "end" && success && (
132
+ {/* Diff display - only show after tool execution completes and was successful, and NOT in expanded mode */}
133
+ {!isExpanded && stage === "end" && success && (
134
134
  <DiffDisplay
135
135
  toolName={name}
136
136
  parameters={parameters}
@@ -37,6 +37,10 @@ export interface ChatContextType {
37
37
  isExpanded: boolean;
38
38
  isTaskListVisible: boolean;
39
39
  setIsTaskListVisible: (visible: boolean) => void;
40
+ queuedMessages: Array<{
41
+ content: string;
42
+ images?: Array<{ path: string; mimeType: string }>;
43
+ }>;
40
44
  // AI functionality
41
45
  sessionId: string;
42
46
  sendMessage: (
@@ -139,6 +143,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
139
143
 
140
144
  const [isTaskListVisible, setIsTaskListVisible] = useState(true);
141
145
 
146
+ const [queuedMessages, setQueuedMessages] = useState<
147
+ Array<{
148
+ content: string;
149
+ images?: Array<{ path: string; mimeType: string }>;
150
+ }>
151
+ >([]);
152
+
142
153
  // AI State
143
154
  const [messages, setMessages] = useState<Message[]>([]);
144
155
 
@@ -393,6 +404,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
393
404
 
394
405
  if (!hasTextContent && !hasImageAttachments) return;
395
406
 
407
+ if (isLoading || isCommandRunning) {
408
+ setQueuedMessages((prev) => [...prev, { content, images }]);
409
+ return;
410
+ }
411
+
396
412
  try {
397
413
  // Handle bash mode - check if it's a bash command (starts with ! and only one line)
398
414
  if (content.startsWith("!") && !content.includes("\n")) {
@@ -432,15 +448,26 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
432
448
  // Loading state will be automatically updated by the useEffect that watches messages
433
449
  }
434
450
  },
435
- [],
451
+ [isLoading, isCommandRunning],
436
452
  );
437
453
 
454
+ // Process queued messages when idle
455
+ useEffect(() => {
456
+ if (!isLoading && !isCommandRunning && queuedMessages.length > 0) {
457
+ const nextMessage = queuedMessages[0];
458
+ setQueuedMessages((prev) => prev.slice(1));
459
+ sendMessage(nextMessage.content, nextMessage.images);
460
+ }
461
+ }, [isLoading, isCommandRunning, queuedMessages, sendMessage]);
462
+
438
463
  // Unified interrupt method, interrupt both AI messages and command execution
439
464
  const abortMessage = useCallback(() => {
465
+ setQueuedMessages([]);
440
466
  agentRef.current?.abortMessage();
441
467
  }, []);
442
468
 
443
469
  const clearMessages = useCallback(() => {
470
+ setQueuedMessages([]);
444
471
  agentRef.current?.clearMessages();
445
472
  }, []);
446
473
 
@@ -604,6 +631,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
604
631
  isExpanded,
605
632
  isTaskListVisible,
606
633
  setIsTaskListVisible,
634
+ queuedMessages,
607
635
  sessionId,
608
636
  sendMessage,
609
637
  abortMessage,
@@ -5,7 +5,11 @@ import {
5
5
  initialState,
6
6
  InputManagerCallbacks,
7
7
  } from "../managers/inputReducer.js";
8
- import { searchFiles as searchFilesUtil, PermissionMode } from "wave-agent-sdk";
8
+ import {
9
+ searchFiles as searchFilesUtil,
10
+ PermissionMode,
11
+ PromptEntry,
12
+ } from "wave-agent-sdk";
9
13
  import * as handlers from "../managers/inputHandlers.js";
10
14
 
11
15
  export const useInputManager = (
@@ -37,10 +41,10 @@ export const useInputManager = (
37
41
  // Handle debounced file search
38
42
  useEffect(() => {
39
43
  if (state.showFileSelector) {
40
- const debounceDelay = parseInt(
41
- process.env.FILE_SELECTOR_DEBOUNCE_MS || "300",
42
- 10,
43
- );
44
+ const debounceDelay =
45
+ state.fileSearchQuery === ""
46
+ ? 0
47
+ : parseInt(process.env.FILE_SELECTOR_DEBOUNCE_MS || "300", 10);
44
48
  const timer = setTimeout(async () => {
45
49
  try {
46
50
  const fileItems = await searchFilesUtil(state.fileSearchQuery);
@@ -68,7 +72,7 @@ export const useInputManager = (
68
72
  );
69
73
  dispatch({ type: "COMPRESS_AND_INSERT_TEXT", payload: processedInput });
70
74
  dispatch({ type: "END_PASTE" });
71
- callbacksRef.current.onResetHistoryNavigation?.();
75
+ dispatch({ type: "RESET_HISTORY_NAVIGATION" });
72
76
  }, pasteDebounceDelay);
73
77
  return () => clearTimeout(timer);
74
78
  }
@@ -204,14 +208,16 @@ export const useInputManager = (
204
208
  const handleCommandInsert = useCallback((command: string) => {
205
209
  const currentState = stateRef.current;
206
210
  if (currentState.slashPosition >= 0) {
211
+ const wordEnd = handlers.getWordEnd(
212
+ currentState.inputText,
213
+ currentState.slashPosition,
214
+ );
207
215
  const beforeSlash = currentState.inputText.substring(
208
216
  0,
209
217
  currentState.slashPosition,
210
218
  );
211
- const afterQuery = currentState.inputText.substring(
212
- currentState.cursorPosition,
213
- );
214
- const newInput = beforeSlash + `/${command} ` + afterQuery;
219
+ const afterWord = currentState.inputText.substring(wordEnd);
220
+ const newInput = beforeSlash + `/${command} ` + afterWord;
215
221
  const newCursorPosition = beforeSlash.length + command.length + 2;
216
222
 
217
223
  dispatch({ type: "SET_INPUT_TEXT", payload: newInput });
@@ -245,24 +251,16 @@ export const useInputManager = (
245
251
  );
246
252
  }, []);
247
253
 
248
- const handleHistorySearchSelect = useCallback((prompt: string) => {
249
- dispatch({ type: "SET_INPUT_TEXT", payload: prompt });
250
- dispatch({ type: "SET_CURSOR_POSITION", payload: prompt.length });
251
- dispatch({ type: "CANCEL_HISTORY_SEARCH" });
254
+ const handleHistorySearchSelect = useCallback((entry: PromptEntry) => {
255
+ dispatch({ type: "SELECT_HISTORY_ENTRY", payload: entry });
252
256
  }, []);
253
257
 
254
258
  const handleCancelHistorySearch = useCallback(() => {
255
259
  dispatch({ type: "CANCEL_HISTORY_SEARCH" });
256
260
  }, []);
257
261
 
258
- const handleSpecialCharInput = useCallback((char: string) => {
259
- handlers.handleSpecialCharInput(
260
- stateRef.current,
261
- dispatch,
262
- char,
263
- stateRef.current.cursorPosition,
264
- stateRef.current.inputText,
265
- );
262
+ const processSelectorInput = useCallback((char: string) => {
263
+ handlers.processSelectorInput(stateRef.current, dispatch, char);
266
264
  }, []);
267
265
 
268
266
  const setInputText = useCallback((text: string) => {
@@ -326,15 +324,11 @@ export const useInputManager = (
326
324
  const handleSubmit = useCallback(
327
325
  async (
328
326
  attachedImages: Array<{ id: number; path: string; mimeType: string }>,
329
- isLoading: boolean = false,
330
- isCommandRunning: boolean = false,
331
327
  ) => {
332
328
  await handlers.handleSubmit(
333
329
  stateRef.current,
334
330
  dispatch,
335
331
  callbacksRef.current,
336
- isLoading,
337
- isCommandRunning,
338
332
  attachedImages,
339
333
  );
340
334
  },
@@ -357,8 +351,6 @@ export const useInputManager = (
357
351
  input: string,
358
352
  key: Key,
359
353
  attachedImages: Array<{ id: number; path: string; mimeType: string }>,
360
- isLoading: boolean = false,
361
- isCommandRunning: boolean = false,
362
354
  clearImages?: () => void,
363
355
  ) => {
364
356
  return await handlers.handleInput(
@@ -367,8 +359,6 @@ export const useInputManager = (
367
359
  callbacksRef.current,
368
360
  input,
369
361
  key,
370
- isLoading,
371
- isCommandRunning,
372
362
  clearImages,
373
363
  );
374
364
  },
@@ -382,6 +372,7 @@ export const useInputManager = (
382
372
  showFileSelector: state.showFileSelector,
383
373
  filteredFiles: state.filteredFiles,
384
374
  fileSearchQuery: state.fileSearchQuery,
375
+ isFileSearching: state.isFileSearching,
385
376
  atPosition: state.atPosition,
386
377
  showCommandSelector: state.showCommandSelector,
387
378
  commandSearchQuery: state.commandSearchQuery,
@@ -424,7 +415,7 @@ export const useInputManager = (
424
415
  handleCancelHistorySearch,
425
416
 
426
417
  // Special handling
427
- handleSpecialCharInput,
418
+ processSelectorInput,
428
419
 
429
420
  // Bash/MCP Manager
430
421
  setShowBackgroundTaskManager,