wave-code 0.14.4 → 0.15.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 (116) hide show
  1. package/dist/acp/agent.d.ts.map +1 -1
  2. package/dist/acp/agent.js +10 -0
  3. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  4. package/dist/components/BackgroundTaskManager.js +17 -53
  5. package/dist/components/CommandSelector.d.ts.map +1 -1
  6. package/dist/components/CommandSelector.js +38 -28
  7. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  8. package/dist/components/ConfirmationSelector.js +18 -196
  9. package/dist/components/DiscoverView.d.ts.map +1 -1
  10. package/dist/components/DiscoverView.js +19 -11
  11. package/dist/components/FileSelector.d.ts.map +1 -1
  12. package/dist/components/FileSelector.js +29 -25
  13. package/dist/components/HelpView.d.ts.map +1 -1
  14. package/dist/components/HelpView.js +24 -28
  15. package/dist/components/HistorySearch.d.ts.map +1 -1
  16. package/dist/components/HistorySearch.js +26 -28
  17. package/dist/components/InputBox.d.ts.map +1 -1
  18. package/dist/components/InputBox.js +14 -6
  19. package/dist/components/InstalledView.d.ts.map +1 -1
  20. package/dist/components/InstalledView.js +19 -11
  21. package/dist/components/MarketplaceDetail.d.ts.map +1 -1
  22. package/dist/components/MarketplaceDetail.js +35 -16
  23. package/dist/components/MarketplaceView.d.ts.map +1 -1
  24. package/dist/components/MarketplaceView.js +21 -12
  25. package/dist/components/McpManager.d.ts.map +1 -1
  26. package/dist/components/McpManager.js +27 -46
  27. package/dist/components/ModelSelector.d.ts.map +1 -1
  28. package/dist/components/ModelSelector.js +27 -17
  29. package/dist/components/PluginDetail.d.ts.map +1 -1
  30. package/dist/components/PluginDetail.js +39 -26
  31. package/dist/components/RewindCommand.d.ts.map +1 -1
  32. package/dist/components/RewindCommand.js +27 -21
  33. package/dist/components/SessionSelector.d.ts.map +1 -1
  34. package/dist/components/SessionSelector.js +21 -16
  35. package/dist/components/StatusLine.d.ts.map +1 -1
  36. package/dist/components/StatusLine.js +5 -1
  37. package/dist/components/WorktreeExitPrompt.d.ts.map +1 -1
  38. package/dist/components/WorktreeExitPrompt.js +21 -12
  39. package/dist/contexts/useChat.d.ts +0 -1
  40. package/dist/contexts/useChat.d.ts.map +1 -1
  41. package/dist/contexts/useChat.js +13 -20
  42. package/dist/hooks/useInputManager.d.ts +5 -19
  43. package/dist/hooks/useInputManager.d.ts.map +1 -1
  44. package/dist/hooks/useInputManager.js +222 -79
  45. package/dist/managers/inputHandlers.d.ts +1 -1
  46. package/dist/managers/inputHandlers.d.ts.map +1 -1
  47. package/dist/managers/inputHandlers.js +3 -6
  48. package/dist/managers/inputReducer.d.ts +46 -4
  49. package/dist/managers/inputReducer.d.ts.map +1 -1
  50. package/dist/managers/inputReducer.js +519 -4
  51. package/dist/print-cli.d.ts.map +1 -1
  52. package/dist/print-cli.js +21 -9
  53. package/dist/reducers/backgroundTaskManagerReducer.d.ts +20 -0
  54. package/dist/reducers/backgroundTaskManagerReducer.d.ts.map +1 -1
  55. package/dist/reducers/backgroundTaskManagerReducer.js +83 -0
  56. package/dist/reducers/confirmationReducer.d.ts +11 -0
  57. package/dist/reducers/confirmationReducer.d.ts.map +1 -1
  58. package/dist/reducers/confirmationReducer.js +146 -0
  59. package/dist/reducers/helpSelectorReducer.d.ts +26 -0
  60. package/dist/reducers/helpSelectorReducer.d.ts.map +1 -0
  61. package/dist/reducers/helpSelectorReducer.js +52 -0
  62. package/dist/reducers/mcpManagerReducer.d.ts +22 -0
  63. package/dist/reducers/mcpManagerReducer.d.ts.map +1 -1
  64. package/dist/reducers/mcpManagerReducer.js +57 -0
  65. package/dist/reducers/pluginDetailReducer.d.ts +8 -0
  66. package/dist/reducers/pluginDetailReducer.d.ts.map +1 -1
  67. package/dist/reducers/pluginDetailReducer.js +32 -0
  68. package/dist/reducers/questionReducer.d.ts +21 -0
  69. package/dist/reducers/questionReducer.d.ts.map +1 -1
  70. package/dist/reducers/questionReducer.js +177 -0
  71. package/dist/reducers/rewindSelectorReducer.d.ts +22 -0
  72. package/dist/reducers/rewindSelectorReducer.d.ts.map +1 -0
  73. package/dist/reducers/rewindSelectorReducer.js +37 -0
  74. package/dist/reducers/selectorReducer.d.ts +22 -0
  75. package/dist/reducers/selectorReducer.d.ts.map +1 -0
  76. package/dist/reducers/selectorReducer.js +40 -0
  77. package/dist/utils/inputUtils.d.ts +18 -0
  78. package/dist/utils/inputUtils.d.ts.map +1 -0
  79. package/dist/utils/inputUtils.js +67 -0
  80. package/dist/utils/worktree.d.ts.map +1 -1
  81. package/dist/utils/worktree.js +1 -2
  82. package/package.json +2 -2
  83. package/src/acp/agent.ts +10 -0
  84. package/src/components/BackgroundTaskManager.tsx +19 -58
  85. package/src/components/CommandSelector.tsx +53 -44
  86. package/src/components/ConfirmationSelector.tsx +18 -202
  87. package/src/components/DiscoverView.tsx +21 -11
  88. package/src/components/FileSelector.tsx +40 -35
  89. package/src/components/HelpView.tsx +37 -43
  90. package/src/components/HistorySearch.tsx +29 -36
  91. package/src/components/InputBox.tsx +14 -7
  92. package/src/components/InstalledView.tsx +21 -11
  93. package/src/components/MarketplaceDetail.tsx +48 -24
  94. package/src/components/MarketplaceView.tsx +25 -10
  95. package/src/components/McpManager.tsx +29 -54
  96. package/src/components/ModelSelector.tsx +30 -23
  97. package/src/components/PluginDetail.tsx +50 -27
  98. package/src/components/RewindCommand.tsx +38 -32
  99. package/src/components/SessionSelector.tsx +23 -20
  100. package/src/components/StatusLine.tsx +10 -1
  101. package/src/components/WorktreeExitPrompt.tsx +23 -13
  102. package/src/contexts/useChat.tsx +13 -31
  103. package/src/hooks/useInputManager.ts +273 -141
  104. package/src/managers/inputHandlers.ts +1 -6
  105. package/src/managers/inputReducer.ts +675 -7
  106. package/src/print-cli.ts +20 -11
  107. package/src/reducers/backgroundTaskManagerReducer.ts +110 -1
  108. package/src/reducers/confirmationReducer.ts +173 -1
  109. package/src/reducers/helpSelectorReducer.ts +77 -0
  110. package/src/reducers/mcpManagerReducer.ts +82 -1
  111. package/src/reducers/pluginDetailReducer.ts +42 -1
  112. package/src/reducers/questionReducer.ts +204 -0
  113. package/src/reducers/rewindSelectorReducer.ts +54 -0
  114. package/src/reducers/selectorReducer.ts +57 -0
  115. package/src/utils/inputUtils.ts +94 -0
  116. package/src/utils/worktree.ts +1 -7
@@ -1,4 +1,4 @@
1
- import { useEffect, useReducer, useCallback, useRef } from "react";
1
+ import { useEffect, useReducer, useCallback } from "react";
2
2
  import { Key } from "ink";
3
3
  import {
4
4
  inputReducer,
@@ -9,6 +9,7 @@ import {
9
9
  searchFiles as searchFilesUtil,
10
10
  PermissionMode,
11
11
  PromptEntry,
12
+ PromptHistoryManager,
12
13
  } from "wave-agent-sdk";
13
14
  import * as handlers from "../managers/inputHandlers.js";
14
15
 
@@ -16,17 +17,32 @@ export const useInputManager = (
16
17
  callbacks: Partial<InputManagerCallbacks> = {},
17
18
  ) => {
18
19
  const [state, dispatch] = useReducer(inputReducer, initialState);
19
- const callbacksRef = useRef(callbacks);
20
- const stateRef = useRef(state);
21
20
 
22
- // Update refs when they change
23
- useEffect(() => {
24
- callbacksRef.current = callbacks;
25
- }, [callbacks]);
26
-
27
- useEffect(() => {
28
- stateRef.current = state;
29
- }, [state]);
21
+ const {
22
+ onInputTextChange,
23
+ onCursorPositionChange,
24
+ onFileSelectorStateChange,
25
+ onCommandSelectorStateChange,
26
+ onHistorySearchStateChange,
27
+ onBackgroundTaskManagerStateChange,
28
+ onMcpManagerStateChange,
29
+ onRewindManagerStateChange,
30
+ onHelpStateChange,
31
+ onStatusCommandStateChange,
32
+ onPluginManagerStateChange,
33
+ onModelSelectorStateChange,
34
+ onImagesStateChange,
35
+ onSendMessage,
36
+ onHasSlashCommand,
37
+ onAbortMessage,
38
+ onBackgroundCurrentTask,
39
+ onPermissionModeChange,
40
+ onAskBtw,
41
+ sessionId,
42
+ workdir,
43
+ getFullMessageThread,
44
+ logger,
45
+ } = callbacks;
30
46
 
31
47
  // Handle selectorJustUsed reset
32
48
  useEffect(() => {
@@ -66,10 +82,7 @@ export const useInputManager = (
66
82
  10,
67
83
  );
68
84
  const timer = setTimeout(() => {
69
- const processedInput = stateRef.current.pasteBuffer.replace(
70
- /\r/g,
71
- "\n",
72
- );
85
+ const processedInput = state.pasteBuffer.replace(/\r/g, "\n");
73
86
  dispatch({
74
87
  type: "INSERT_TEXT_WITH_PLACEHOLDER",
75
88
  payload: processedInput,
@@ -83,15 +96,154 @@ export const useInputManager = (
83
96
 
84
97
  // Sync state changes with callbacks
85
98
  useEffect(() => {
86
- callbacksRef.current.onInputTextChange?.(state.inputText);
87
- }, [state.inputText]);
99
+ onInputTextChange?.(state.inputText);
100
+ }, [state.inputText, onInputTextChange]);
88
101
 
89
102
  useEffect(() => {
90
- callbacksRef.current.onCursorPositionChange?.(state.cursorPosition);
91
- }, [state.cursorPosition]);
103
+ onCursorPositionChange?.(state.cursorPosition);
104
+ }, [state.cursorPosition, onCursorPositionChange]);
92
105
 
106
+ // Handle pending effects
93
107
  useEffect(() => {
94
- callbacksRef.current.onFileSelectorStateChange?.(
108
+ if (!state.pendingEffect) return;
109
+
110
+ const effect = state.pendingEffect;
111
+ dispatch({ type: "CLEAR_PENDING_EFFECT" });
112
+
113
+ const runEffect = async () => {
114
+ try {
115
+ switch (effect.type) {
116
+ case "SEND_MESSAGE":
117
+ await onSendMessage?.(
118
+ effect.content,
119
+ effect.images,
120
+ effect.longTextMap,
121
+ );
122
+ PromptHistoryManager.addEntry(
123
+ effect.content,
124
+ sessionId,
125
+ effect.longTextMap,
126
+ workdir,
127
+ ).catch((err: unknown) => {
128
+ logger?.error("Failed to save prompt history", err);
129
+ });
130
+ break;
131
+ case "ABORT_MESSAGE":
132
+ onAbortMessage?.();
133
+ break;
134
+ case "BACKGROUND_CURRENT_TASK":
135
+ onBackgroundCurrentTask?.();
136
+ break;
137
+ case "ASK_BTW":
138
+ try {
139
+ const answer = await onAskBtw?.(effect.question);
140
+ dispatch({
141
+ type: "SET_BTW_STATE",
142
+ payload: { answer, isLoading: false },
143
+ });
144
+ } catch (error) {
145
+ console.error("Failed to ask side question:", error);
146
+ dispatch({
147
+ type: "SET_BTW_STATE",
148
+ payload: {
149
+ answer:
150
+ "Error: Failed to get an answer for your side question.",
151
+ isLoading: false,
152
+ },
153
+ });
154
+ }
155
+ break;
156
+ case "PERMISSION_MODE_CHANGE":
157
+ onPermissionModeChange?.(effect.mode);
158
+ break;
159
+ case "FETCH_HISTORY": {
160
+ let sessionIds: string[] | undefined = sessionId
161
+ ? [sessionId]
162
+ : undefined;
163
+
164
+ if (getFullMessageThread) {
165
+ try {
166
+ const thread = await getFullMessageThread();
167
+ sessionIds = thread.sessionIds;
168
+ } catch (error) {
169
+ logger?.error("Failed to fetch ancestor session IDs", error);
170
+ }
171
+ }
172
+
173
+ const history = await PromptHistoryManager.getHistory({
174
+ sessionId: sessionIds,
175
+ workdir: workdir,
176
+ });
177
+ dispatch({ type: "SET_HISTORY_ENTRIES", payload: history });
178
+ dispatch({ type: "NAVIGATE_HISTORY", payload: "up" });
179
+ break;
180
+ }
181
+ case "PASTE_IMAGE":
182
+ try {
183
+ await handlers.handlePasteImage(dispatch);
184
+ } catch (error) {
185
+ console.warn("Failed to handle paste image:", error);
186
+ }
187
+ break;
188
+ case "EXECUTE_COMMAND":
189
+ if (onSendMessage && onHasSlashCommand?.(effect.command)) {
190
+ const fullCommand = `/${effect.command}`;
191
+ try {
192
+ await onSendMessage(fullCommand, undefined, {});
193
+ } catch (error) {
194
+ console.error("Failed to execute slash command:", error);
195
+ }
196
+ } else {
197
+ // Internal command handling
198
+ const command = effect.command;
199
+ if (command === "tasks") {
200
+ dispatch({
201
+ type: "SET_SHOW_BACKGROUND_TASK_MANAGER",
202
+ payload: true,
203
+ });
204
+ } else if (command === "mcp") {
205
+ dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
206
+ } else if (command === "rewind") {
207
+ dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
208
+ } else if (command === "help") {
209
+ dispatch({ type: "SET_SHOW_HELP", payload: true });
210
+ } else if (command === "status") {
211
+ dispatch({ type: "SET_SHOW_STATUS_COMMAND", payload: true });
212
+ } else if (command === "plugin") {
213
+ dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
214
+ } else if (command === "model") {
215
+ dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: true });
216
+ } else if (command === "btw") {
217
+ dispatch({
218
+ type: "SET_BTW_STATE",
219
+ payload: { isActive: true, question: "", isLoading: false },
220
+ });
221
+ }
222
+ }
223
+ break;
224
+ }
225
+ } catch (error) {
226
+ console.error("Effect execution error:", error);
227
+ }
228
+ };
229
+
230
+ runEffect();
231
+ }, [
232
+ state.pendingEffect,
233
+ onSendMessage,
234
+ onAbortMessage,
235
+ onBackgroundCurrentTask,
236
+ onAskBtw,
237
+ onPermissionModeChange,
238
+ sessionId,
239
+ workdir,
240
+ getFullMessageThread,
241
+ logger,
242
+ onHasSlashCommand,
243
+ ]);
244
+
245
+ useEffect(() => {
246
+ onFileSelectorStateChange?.(
95
247
  state.showFileSelector,
96
248
  state.filteredFiles,
97
249
  state.fileSearchQuery,
@@ -102,10 +254,11 @@ export const useInputManager = (
102
254
  state.filteredFiles,
103
255
  state.fileSearchQuery,
104
256
  state.atPosition,
257
+ onFileSelectorStateChange,
105
258
  ]);
106
259
 
107
260
  useEffect(() => {
108
- callbacksRef.current.onCommandSelectorStateChange?.(
261
+ onCommandSelectorStateChange?.(
109
262
  state.showCommandSelector,
110
263
  state.commandSearchQuery,
111
264
  state.slashPosition,
@@ -114,48 +267,51 @@ export const useInputManager = (
114
267
  state.showCommandSelector,
115
268
  state.commandSearchQuery,
116
269
  state.slashPosition,
270
+ onCommandSelectorStateChange,
117
271
  ]);
118
272
 
119
273
  useEffect(() => {
120
- callbacksRef.current.onHistorySearchStateChange?.(
274
+ onHistorySearchStateChange?.(
121
275
  state.showHistorySearch,
122
276
  state.historySearchQuery,
123
277
  );
124
- }, [state.showHistorySearch, state.historySearchQuery]);
278
+ }, [
279
+ state.showHistorySearch,
280
+ state.historySearchQuery,
281
+ onHistorySearchStateChange,
282
+ ]);
125
283
 
126
284
  useEffect(() => {
127
- callbacksRef.current.onBackgroundTaskManagerStateChange?.(
128
- state.showBackgroundTaskManager,
129
- );
130
- }, [state.showBackgroundTaskManager]);
285
+ onBackgroundTaskManagerStateChange?.(state.showBackgroundTaskManager);
286
+ }, [state.showBackgroundTaskManager, onBackgroundTaskManagerStateChange]);
131
287
 
132
288
  useEffect(() => {
133
- callbacksRef.current.onMcpManagerStateChange?.(state.showMcpManager);
134
- }, [state.showMcpManager]);
289
+ onMcpManagerStateChange?.(state.showMcpManager);
290
+ }, [state.showMcpManager, onMcpManagerStateChange]);
135
291
 
136
292
  useEffect(() => {
137
- callbacksRef.current.onRewindManagerStateChange?.(state.showRewindManager);
138
- }, [state.showRewindManager]);
293
+ onRewindManagerStateChange?.(state.showRewindManager);
294
+ }, [state.showRewindManager, onRewindManagerStateChange]);
139
295
 
140
296
  useEffect(() => {
141
- callbacksRef.current.onHelpStateChange?.(state.showHelp);
142
- }, [state.showHelp]);
297
+ onHelpStateChange?.(state.showHelp);
298
+ }, [state.showHelp, onHelpStateChange]);
143
299
 
144
300
  useEffect(() => {
145
- callbacksRef.current.onStatusCommandStateChange?.(state.showStatusCommand);
146
- }, [state.showStatusCommand]);
301
+ onStatusCommandStateChange?.(state.showStatusCommand);
302
+ }, [state.showStatusCommand, onStatusCommandStateChange]);
147
303
 
148
304
  useEffect(() => {
149
- callbacksRef.current.onPluginManagerStateChange?.(state.showPluginManager);
150
- }, [state.showPluginManager]);
305
+ onPluginManagerStateChange?.(state.showPluginManager);
306
+ }, [state.showPluginManager, onPluginManagerStateChange]);
151
307
 
152
308
  useEffect(() => {
153
- callbacksRef.current.onModelSelectorStateChange?.(state.showModelSelector);
154
- }, [state.showModelSelector]);
309
+ onModelSelectorStateChange?.(state.showModelSelector);
310
+ }, [state.showModelSelector, onModelSelectorStateChange]);
155
311
 
156
312
  useEffect(() => {
157
- callbacksRef.current.onImagesStateChange?.(state.attachedImages);
158
- }, [state.attachedImages]);
313
+ onImagesStateChange?.(state.attachedImages);
314
+ }, [state.attachedImages, onImagesStateChange]);
159
315
 
160
316
  // Handle /btw side question
161
317
  useEffect(() => {
@@ -166,9 +322,7 @@ export const useInputManager = (
166
322
  ) {
167
323
  const askBtw = async () => {
168
324
  try {
169
- const answer = await callbacksRef.current.onAskBtw?.(
170
- state.btwState.question,
171
- );
325
+ const answer = await onAskBtw?.(state.btwState.question);
172
326
  dispatch({
173
327
  type: "SET_BTW_STATE",
174
328
  payload: { answer, isLoading: false },
@@ -190,6 +344,7 @@ export const useInputManager = (
190
344
  state.btwState.isActive,
191
345
  state.btwState.isLoading,
192
346
  state.btwState.question,
347
+ onAskBtw,
193
348
  ]);
194
349
 
195
350
  // Methods
@@ -218,12 +373,7 @@ export const useInputManager = (
218
373
  }, []);
219
374
 
220
375
  const handleFileSelect = useCallback((filePath: string) => {
221
- return handlers.handleFileSelect(
222
- stateRef.current,
223
- dispatch,
224
- callbacksRef.current,
225
- filePath,
226
- );
376
+ dispatch({ type: "SELECT_FILE", payload: filePath });
227
377
  }, []);
228
378
 
229
379
  const handleCancelFileSelect = useCallback(() => {
@@ -234,51 +384,25 @@ export const useInputManager = (
234
384
  dispatch({ type: "SET_FILE_SEARCH_QUERY", payload: query });
235
385
  }, []);
236
386
 
237
- const checkForAtDeletion = useCallback((cursorPos: number) => {
238
- return handlers.checkForAtDeletion(stateRef.current, dispatch, cursorPos);
239
- }, []);
387
+ const checkForAtDeletion = useCallback(
388
+ (cursorPos: number) => {
389
+ // This is now largely handled inside HANDLE_KEY,
390
+ // but kept for external calls if any.
391
+ return handlers.checkForAtDeletion(state, dispatch, cursorPos);
392
+ },
393
+ [state],
394
+ );
240
395
 
241
396
  const activateCommandSelector = useCallback((position: number) => {
242
397
  dispatch({ type: "ACTIVATE_COMMAND_SELECTOR", payload: position });
243
398
  }, []);
244
399
 
245
400
  const handleCommandSelect = useCallback((command: string) => {
246
- return handlers.handleCommandSelect(
247
- stateRef.current,
248
- dispatch,
249
- callbacksRef.current,
250
- command,
251
- );
401
+ dispatch({ type: "SELECT_COMMAND", payload: command });
252
402
  }, []);
253
403
 
254
404
  const handleCommandInsert = useCallback((command: string) => {
255
- const currentState = stateRef.current;
256
- if (currentState.slashPosition >= 0) {
257
- const wordEnd = handlers.getWordEnd(
258
- currentState.inputText,
259
- currentState.slashPosition,
260
- );
261
- const beforeSlash = currentState.inputText.substring(
262
- 0,
263
- currentState.slashPosition,
264
- );
265
- const afterWord = currentState.inputText.substring(wordEnd);
266
- const newInput = beforeSlash + `/${command} ` + afterWord;
267
- const newCursorPosition = beforeSlash.length + command.length + 2;
268
-
269
- dispatch({ type: "SET_INPUT_TEXT", payload: newInput });
270
- dispatch({ type: "SET_CURSOR_POSITION", payload: newCursorPosition });
271
- dispatch({ type: "CANCEL_COMMAND_SELECTOR" });
272
-
273
- callbacksRef.current.onInputTextChange?.(newInput);
274
- callbacksRef.current.onCursorPositionChange?.(newCursorPosition);
275
-
276
- return { newInput, newCursorPosition };
277
- }
278
- return {
279
- newInput: currentState.inputText,
280
- newCursorPosition: currentState.cursorPosition,
281
- };
405
+ dispatch({ type: "INSERT_COMMAND", payload: command });
282
406
  }, []);
283
407
 
284
408
  const handleCancelCommandSelect = useCallback(() => {
@@ -289,13 +413,12 @@ export const useInputManager = (
289
413
  dispatch({ type: "SET_COMMAND_SEARCH_QUERY", payload: query });
290
414
  }, []);
291
415
 
292
- const checkForSlashDeletion = useCallback((cursorPos: number) => {
293
- return handlers.checkForSlashDeletion(
294
- stateRef.current,
295
- dispatch,
296
- cursorPos,
297
- );
298
- }, []);
416
+ const checkForSlashDeletion = useCallback(
417
+ (cursorPos: number) => {
418
+ return handlers.checkForSlashDeletion(state, dispatch, cursorPos);
419
+ },
420
+ [state],
421
+ );
299
422
 
300
423
  const handleHistorySearchSelect = useCallback((entry: PromptEntry) => {
301
424
  dispatch({ type: "SELECT_HISTORY_ENTRY", payload: entry });
@@ -305,9 +428,12 @@ export const useInputManager = (
305
428
  dispatch({ type: "CANCEL_HISTORY_SEARCH" });
306
429
  }, []);
307
430
 
308
- const processSelectorInput = useCallback((char: string) => {
309
- handlers.processSelectorInput(stateRef.current, dispatch, char);
310
- }, []);
431
+ const processSelectorInput = useCallback(
432
+ (char: string) => {
433
+ handlers.processSelectorInput(state, dispatch, char);
434
+ },
435
+ [state],
436
+ );
311
437
 
312
438
  const setInputText = useCallback((text: string) => {
313
439
  dispatch({ type: "SET_INPUT_TEXT", payload: text });
@@ -345,14 +471,13 @@ export const useInputManager = (
345
471
  dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: show });
346
472
  }, []);
347
473
 
348
- const setPermissionMode = useCallback((mode: PermissionMode) => {
349
- dispatch({ type: "SET_PERMISSION_MODE", payload: mode });
350
- callbacksRef.current.onPermissionModeChange?.(mode);
351
- }, []);
352
-
353
- const setAllowBypassInCycle = useCallback((allow: boolean) => {
354
- dispatch({ type: "SET_ALLOW_BYPASS_IN_CYCLE", payload: allow });
355
- }, []);
474
+ const setPermissionMode = useCallback(
475
+ (mode: PermissionMode) => {
476
+ dispatch({ type: "SET_PERMISSION_MODE", payload: mode });
477
+ onPermissionModeChange?.(mode);
478
+ },
479
+ [onPermissionModeChange],
480
+ );
356
481
 
357
482
  const setBtwState = useCallback(
358
483
  (payload: Partial<import("../managers/inputReducer.js").BtwState>) => {
@@ -377,35 +502,37 @@ export const useInputManager = (
377
502
  return await handlers.handlePasteImage(dispatch);
378
503
  }, []);
379
504
 
380
- const handlePasteInput = useCallback((input: string) => {
381
- handlers.handlePasteInput(
382
- stateRef.current,
383
- dispatch,
384
- callbacksRef.current,
385
- input,
386
- );
387
- }, []);
388
-
389
- const handleSubmit = useCallback(
390
- async (
391
- attachedImages: Array<{ id: number; path: string; mimeType: string }>,
392
- ) => {
393
- await handlers.handleSubmit(
394
- stateRef.current,
395
- dispatch,
396
- callbacksRef.current,
397
- attachedImages,
398
- );
505
+ const handlePasteInput = useCallback(
506
+ (input: string) => {
507
+ dispatch({
508
+ type: "HANDLE_KEY",
509
+ payload: {
510
+ input,
511
+ key: {} as Key,
512
+ hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
513
+ },
514
+ });
399
515
  },
400
- [],
516
+ [onHasSlashCommand],
401
517
  );
402
518
 
403
- const expandLongTextPlaceholders = useCallback((text: string) => {
404
- return handlers.expandLongTextPlaceholders(
405
- text,
406
- stateRef.current.longTextMap,
407
- );
408
- }, []);
519
+ const handleSubmit = useCallback(async () => {
520
+ dispatch({
521
+ type: "HANDLE_KEY",
522
+ payload: {
523
+ input: "",
524
+ key: { return: true } as Key,
525
+ hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
526
+ },
527
+ });
528
+ }, [onHasSlashCommand]);
529
+
530
+ const expandLongTextPlaceholders = useCallback(
531
+ (text: string) => {
532
+ return handlers.expandLongTextPlaceholders(text, state.longTextMap);
533
+ },
534
+ [state.longTextMap],
535
+ );
409
536
 
410
537
  const clearLongTextMap = useCallback(() => {
411
538
  dispatch({ type: "CLEAR_LONG_TEXT_MAP" });
@@ -415,19 +542,25 @@ export const useInputManager = (
415
542
  async (
416
543
  input: string,
417
544
  key: Key,
418
- attachedImages: Array<{ id: number; path: string; mimeType: string }>,
545
+ _attachedImages: Array<{ id: number; path: string; mimeType: string }>,
419
546
  clearImages?: () => void,
420
547
  ) => {
421
- return await handlers.handleInput(
422
- stateRef.current,
423
- dispatch,
424
- callbacksRef.current,
425
- input,
426
- key,
427
- clearImages,
428
- );
548
+ // Clear images side effect if return is pressed
549
+ if (key.return) {
550
+ clearImages?.();
551
+ }
552
+
553
+ dispatch({
554
+ type: "HANDLE_KEY",
555
+ payload: {
556
+ input,
557
+ key,
558
+ hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
559
+ },
560
+ });
561
+ return true;
429
562
  },
430
- [],
563
+ [onHasSlashCommand],
431
564
  );
432
565
 
433
566
  return {
@@ -494,7 +627,6 @@ export const useInputManager = (
494
627
  setShowPluginManager,
495
628
  setShowModelSelector,
496
629
  setPermissionMode,
497
- setAllowBypassInCycle,
498
630
  setBtwState,
499
631
 
500
632
  // Image management
@@ -124,12 +124,8 @@ export const cyclePermissionMode = (
124
124
  currentMode: PermissionMode,
125
125
  dispatch: React.Dispatch<InputAction>,
126
126
  callbacks: Partial<InputManagerCallbacks>,
127
- allowBypassInCycle: boolean = false,
128
127
  ) => {
129
- const modes: PermissionMode[] = ["default", "acceptEdits", "plan"];
130
- if (allowBypassInCycle) {
131
- modes.push("bypassPermissions");
132
- }
128
+ const modes: PermissionMode[] = ["default", "acceptEdits", "plan", "bypassPermissions"];
133
129
  const currentIndex = modes.indexOf(currentMode);
134
130
  const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % modes.length;
135
131
  const nextMode = modes[nextIndex];
@@ -768,7 +764,6 @@ export const handleInput = async (
768
764
  state.permissionMode,
769
765
  dispatch,
770
766
  callbacks,
771
- state.allowBypassInCycle,
772
767
  );
773
768
  return true;
774
769
  }