wave-code 0.14.3 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +224 -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 +50 -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 +17 -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 +275 -141
  104. package/src/managers/inputHandlers.ts +1 -6
  105. package/src/managers/inputReducer.ts +680 -7
  106. package/src/print-cli.ts +15 -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,156 @@ 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
+ break;
123
+ case "ABORT_MESSAGE":
124
+ onAbortMessage?.();
125
+ break;
126
+ case "BACKGROUND_CURRENT_TASK":
127
+ onBackgroundCurrentTask?.();
128
+ break;
129
+ case "ASK_BTW":
130
+ try {
131
+ const answer = await onAskBtw?.(effect.question);
132
+ dispatch({
133
+ type: "SET_BTW_STATE",
134
+ payload: { answer, isLoading: false },
135
+ });
136
+ } catch (error) {
137
+ console.error("Failed to ask side question:", error);
138
+ dispatch({
139
+ type: "SET_BTW_STATE",
140
+ payload: {
141
+ answer:
142
+ "Error: Failed to get an answer for your side question.",
143
+ isLoading: false,
144
+ },
145
+ });
146
+ }
147
+ break;
148
+ case "PERMISSION_MODE_CHANGE":
149
+ onPermissionModeChange?.(effect.mode);
150
+ break;
151
+ case "SAVE_HISTORY":
152
+ PromptHistoryManager.addEntry(
153
+ effect.content,
154
+ sessionId,
155
+ effect.longTextMap,
156
+ workdir,
157
+ ).catch((err: unknown) => {
158
+ logger?.error("Failed to save prompt history", err);
159
+ });
160
+ break;
161
+ case "FETCH_HISTORY": {
162
+ let sessionIds: string[] | undefined = sessionId
163
+ ? [sessionId]
164
+ : undefined;
165
+
166
+ if (getFullMessageThread) {
167
+ try {
168
+ const thread = await getFullMessageThread();
169
+ sessionIds = thread.sessionIds;
170
+ } catch (error) {
171
+ logger?.error("Failed to fetch ancestor session IDs", error);
172
+ }
173
+ }
174
+
175
+ const history = await PromptHistoryManager.getHistory({
176
+ sessionId: sessionIds,
177
+ workdir: workdir,
178
+ });
179
+ dispatch({ type: "SET_HISTORY_ENTRIES", payload: history });
180
+ dispatch({ type: "NAVIGATE_HISTORY", payload: "up" });
181
+ break;
182
+ }
183
+ case "PASTE_IMAGE":
184
+ try {
185
+ await handlers.handlePasteImage(dispatch);
186
+ } catch (error) {
187
+ console.warn("Failed to handle paste image:", error);
188
+ }
189
+ break;
190
+ case "EXECUTE_COMMAND":
191
+ if (onSendMessage && onHasSlashCommand?.(effect.command)) {
192
+ const fullCommand = `/${effect.command}`;
193
+ try {
194
+ await onSendMessage(fullCommand, undefined, {});
195
+ } catch (error) {
196
+ console.error("Failed to execute slash command:", error);
197
+ }
198
+ } else {
199
+ // Internal command handling
200
+ const command = effect.command;
201
+ if (command === "tasks") {
202
+ dispatch({
203
+ type: "SET_SHOW_BACKGROUND_TASK_MANAGER",
204
+ payload: true,
205
+ });
206
+ } else if (command === "mcp") {
207
+ dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
208
+ } else if (command === "rewind") {
209
+ dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
210
+ } else if (command === "help") {
211
+ dispatch({ type: "SET_SHOW_HELP", payload: true });
212
+ } else if (command === "status") {
213
+ dispatch({ type: "SET_SHOW_STATUS_COMMAND", payload: true });
214
+ } else if (command === "plugin") {
215
+ dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
216
+ } else if (command === "model") {
217
+ dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: true });
218
+ } else if (command === "btw") {
219
+ dispatch({
220
+ type: "SET_BTW_STATE",
221
+ payload: { isActive: true, question: "", isLoading: false },
222
+ });
223
+ }
224
+ }
225
+ break;
226
+ }
227
+ } catch (error) {
228
+ console.error("Effect execution error:", error);
229
+ }
230
+ };
231
+
232
+ runEffect();
233
+ }, [
234
+ state.pendingEffect,
235
+ onSendMessage,
236
+ onAbortMessage,
237
+ onBackgroundCurrentTask,
238
+ onAskBtw,
239
+ onPermissionModeChange,
240
+ sessionId,
241
+ workdir,
242
+ getFullMessageThread,
243
+ logger,
244
+ onHasSlashCommand,
245
+ ]);
246
+
247
+ useEffect(() => {
248
+ onFileSelectorStateChange?.(
95
249
  state.showFileSelector,
96
250
  state.filteredFiles,
97
251
  state.fileSearchQuery,
@@ -102,10 +256,11 @@ export const useInputManager = (
102
256
  state.filteredFiles,
103
257
  state.fileSearchQuery,
104
258
  state.atPosition,
259
+ onFileSelectorStateChange,
105
260
  ]);
106
261
 
107
262
  useEffect(() => {
108
- callbacksRef.current.onCommandSelectorStateChange?.(
263
+ onCommandSelectorStateChange?.(
109
264
  state.showCommandSelector,
110
265
  state.commandSearchQuery,
111
266
  state.slashPosition,
@@ -114,48 +269,51 @@ export const useInputManager = (
114
269
  state.showCommandSelector,
115
270
  state.commandSearchQuery,
116
271
  state.slashPosition,
272
+ onCommandSelectorStateChange,
117
273
  ]);
118
274
 
119
275
  useEffect(() => {
120
- callbacksRef.current.onHistorySearchStateChange?.(
276
+ onHistorySearchStateChange?.(
121
277
  state.showHistorySearch,
122
278
  state.historySearchQuery,
123
279
  );
124
- }, [state.showHistorySearch, state.historySearchQuery]);
280
+ }, [
281
+ state.showHistorySearch,
282
+ state.historySearchQuery,
283
+ onHistorySearchStateChange,
284
+ ]);
125
285
 
126
286
  useEffect(() => {
127
- callbacksRef.current.onBackgroundTaskManagerStateChange?.(
128
- state.showBackgroundTaskManager,
129
- );
130
- }, [state.showBackgroundTaskManager]);
287
+ onBackgroundTaskManagerStateChange?.(state.showBackgroundTaskManager);
288
+ }, [state.showBackgroundTaskManager, onBackgroundTaskManagerStateChange]);
131
289
 
132
290
  useEffect(() => {
133
- callbacksRef.current.onMcpManagerStateChange?.(state.showMcpManager);
134
- }, [state.showMcpManager]);
291
+ onMcpManagerStateChange?.(state.showMcpManager);
292
+ }, [state.showMcpManager, onMcpManagerStateChange]);
135
293
 
136
294
  useEffect(() => {
137
- callbacksRef.current.onRewindManagerStateChange?.(state.showRewindManager);
138
- }, [state.showRewindManager]);
295
+ onRewindManagerStateChange?.(state.showRewindManager);
296
+ }, [state.showRewindManager, onRewindManagerStateChange]);
139
297
 
140
298
  useEffect(() => {
141
- callbacksRef.current.onHelpStateChange?.(state.showHelp);
142
- }, [state.showHelp]);
299
+ onHelpStateChange?.(state.showHelp);
300
+ }, [state.showHelp, onHelpStateChange]);
143
301
 
144
302
  useEffect(() => {
145
- callbacksRef.current.onStatusCommandStateChange?.(state.showStatusCommand);
146
- }, [state.showStatusCommand]);
303
+ onStatusCommandStateChange?.(state.showStatusCommand);
304
+ }, [state.showStatusCommand, onStatusCommandStateChange]);
147
305
 
148
306
  useEffect(() => {
149
- callbacksRef.current.onPluginManagerStateChange?.(state.showPluginManager);
150
- }, [state.showPluginManager]);
307
+ onPluginManagerStateChange?.(state.showPluginManager);
308
+ }, [state.showPluginManager, onPluginManagerStateChange]);
151
309
 
152
310
  useEffect(() => {
153
- callbacksRef.current.onModelSelectorStateChange?.(state.showModelSelector);
154
- }, [state.showModelSelector]);
311
+ onModelSelectorStateChange?.(state.showModelSelector);
312
+ }, [state.showModelSelector, onModelSelectorStateChange]);
155
313
 
156
314
  useEffect(() => {
157
- callbacksRef.current.onImagesStateChange?.(state.attachedImages);
158
- }, [state.attachedImages]);
315
+ onImagesStateChange?.(state.attachedImages);
316
+ }, [state.attachedImages, onImagesStateChange]);
159
317
 
160
318
  // Handle /btw side question
161
319
  useEffect(() => {
@@ -166,9 +324,7 @@ export const useInputManager = (
166
324
  ) {
167
325
  const askBtw = async () => {
168
326
  try {
169
- const answer = await callbacksRef.current.onAskBtw?.(
170
- state.btwState.question,
171
- );
327
+ const answer = await onAskBtw?.(state.btwState.question);
172
328
  dispatch({
173
329
  type: "SET_BTW_STATE",
174
330
  payload: { answer, isLoading: false },
@@ -190,6 +346,7 @@ export const useInputManager = (
190
346
  state.btwState.isActive,
191
347
  state.btwState.isLoading,
192
348
  state.btwState.question,
349
+ onAskBtw,
193
350
  ]);
194
351
 
195
352
  // Methods
@@ -218,12 +375,7 @@ export const useInputManager = (
218
375
  }, []);
219
376
 
220
377
  const handleFileSelect = useCallback((filePath: string) => {
221
- return handlers.handleFileSelect(
222
- stateRef.current,
223
- dispatch,
224
- callbacksRef.current,
225
- filePath,
226
- );
378
+ dispatch({ type: "SELECT_FILE", payload: filePath });
227
379
  }, []);
228
380
 
229
381
  const handleCancelFileSelect = useCallback(() => {
@@ -234,51 +386,25 @@ export const useInputManager = (
234
386
  dispatch({ type: "SET_FILE_SEARCH_QUERY", payload: query });
235
387
  }, []);
236
388
 
237
- const checkForAtDeletion = useCallback((cursorPos: number) => {
238
- return handlers.checkForAtDeletion(stateRef.current, dispatch, cursorPos);
239
- }, []);
389
+ const checkForAtDeletion = useCallback(
390
+ (cursorPos: number) => {
391
+ // This is now largely handled inside HANDLE_KEY,
392
+ // but kept for external calls if any.
393
+ return handlers.checkForAtDeletion(state, dispatch, cursorPos);
394
+ },
395
+ [state],
396
+ );
240
397
 
241
398
  const activateCommandSelector = useCallback((position: number) => {
242
399
  dispatch({ type: "ACTIVATE_COMMAND_SELECTOR", payload: position });
243
400
  }, []);
244
401
 
245
402
  const handleCommandSelect = useCallback((command: string) => {
246
- return handlers.handleCommandSelect(
247
- stateRef.current,
248
- dispatch,
249
- callbacksRef.current,
250
- command,
251
- );
403
+ dispatch({ type: "SELECT_COMMAND", payload: command });
252
404
  }, []);
253
405
 
254
406
  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
- };
407
+ dispatch({ type: "INSERT_COMMAND", payload: command });
282
408
  }, []);
283
409
 
284
410
  const handleCancelCommandSelect = useCallback(() => {
@@ -289,13 +415,12 @@ export const useInputManager = (
289
415
  dispatch({ type: "SET_COMMAND_SEARCH_QUERY", payload: query });
290
416
  }, []);
291
417
 
292
- const checkForSlashDeletion = useCallback((cursorPos: number) => {
293
- return handlers.checkForSlashDeletion(
294
- stateRef.current,
295
- dispatch,
296
- cursorPos,
297
- );
298
- }, []);
418
+ const checkForSlashDeletion = useCallback(
419
+ (cursorPos: number) => {
420
+ return handlers.checkForSlashDeletion(state, dispatch, cursorPos);
421
+ },
422
+ [state],
423
+ );
299
424
 
300
425
  const handleHistorySearchSelect = useCallback((entry: PromptEntry) => {
301
426
  dispatch({ type: "SELECT_HISTORY_ENTRY", payload: entry });
@@ -305,9 +430,12 @@ export const useInputManager = (
305
430
  dispatch({ type: "CANCEL_HISTORY_SEARCH" });
306
431
  }, []);
307
432
 
308
- const processSelectorInput = useCallback((char: string) => {
309
- handlers.processSelectorInput(stateRef.current, dispatch, char);
310
- }, []);
433
+ const processSelectorInput = useCallback(
434
+ (char: string) => {
435
+ handlers.processSelectorInput(state, dispatch, char);
436
+ },
437
+ [state],
438
+ );
311
439
 
312
440
  const setInputText = useCallback((text: string) => {
313
441
  dispatch({ type: "SET_INPUT_TEXT", payload: text });
@@ -345,14 +473,13 @@ export const useInputManager = (
345
473
  dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: show });
346
474
  }, []);
347
475
 
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
- }, []);
476
+ const setPermissionMode = useCallback(
477
+ (mode: PermissionMode) => {
478
+ dispatch({ type: "SET_PERMISSION_MODE", payload: mode });
479
+ onPermissionModeChange?.(mode);
480
+ },
481
+ [onPermissionModeChange],
482
+ );
356
483
 
357
484
  const setBtwState = useCallback(
358
485
  (payload: Partial<import("../managers/inputReducer.js").BtwState>) => {
@@ -377,35 +504,37 @@ export const useInputManager = (
377
504
  return await handlers.handlePasteImage(dispatch);
378
505
  }, []);
379
506
 
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
- );
507
+ const handlePasteInput = useCallback(
508
+ (input: string) => {
509
+ dispatch({
510
+ type: "HANDLE_KEY",
511
+ payload: {
512
+ input,
513
+ key: {} as Key,
514
+ hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
515
+ },
516
+ });
399
517
  },
400
- [],
518
+ [onHasSlashCommand],
401
519
  );
402
520
 
403
- const expandLongTextPlaceholders = useCallback((text: string) => {
404
- return handlers.expandLongTextPlaceholders(
405
- text,
406
- stateRef.current.longTextMap,
407
- );
408
- }, []);
521
+ const handleSubmit = useCallback(async () => {
522
+ dispatch({
523
+ type: "HANDLE_KEY",
524
+ payload: {
525
+ input: "",
526
+ key: { return: true } as Key,
527
+ hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
528
+ },
529
+ });
530
+ }, [onHasSlashCommand]);
531
+
532
+ const expandLongTextPlaceholders = useCallback(
533
+ (text: string) => {
534
+ return handlers.expandLongTextPlaceholders(text, state.longTextMap);
535
+ },
536
+ [state.longTextMap],
537
+ );
409
538
 
410
539
  const clearLongTextMap = useCallback(() => {
411
540
  dispatch({ type: "CLEAR_LONG_TEXT_MAP" });
@@ -415,19 +544,25 @@ export const useInputManager = (
415
544
  async (
416
545
  input: string,
417
546
  key: Key,
418
- attachedImages: Array<{ id: number; path: string; mimeType: string }>,
547
+ _attachedImages: Array<{ id: number; path: string; mimeType: string }>,
419
548
  clearImages?: () => void,
420
549
  ) => {
421
- return await handlers.handleInput(
422
- stateRef.current,
423
- dispatch,
424
- callbacksRef.current,
425
- input,
426
- key,
427
- clearImages,
428
- );
550
+ // Clear images side effect if return is pressed
551
+ if (key.return) {
552
+ clearImages?.();
553
+ }
554
+
555
+ dispatch({
556
+ type: "HANDLE_KEY",
557
+ payload: {
558
+ input,
559
+ key,
560
+ hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
561
+ },
562
+ });
563
+ return true;
429
564
  },
430
- [],
565
+ [onHasSlashCommand],
431
566
  );
432
567
 
433
568
  return {
@@ -494,7 +629,6 @@ export const useInputManager = (
494
629
  setShowPluginManager,
495
630
  setShowModelSelector,
496
631
  setPermissionMode,
497
- setAllowBypassInCycle,
498
632
  setBtwState,
499
633
 
500
634
  // 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
  }