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
@@ -5,6 +5,14 @@ import {
5
5
  PromptEntry,
6
6
  Message,
7
7
  } from "wave-agent-sdk";
8
+ import { Key } from "ink";
9
+ import {
10
+ getAtSelectorPosition,
11
+ getSlashSelectorPosition,
12
+ getWordEnd,
13
+ SELECTOR_TRIGGERS,
14
+ getProjectedState,
15
+ } from "../utils/inputUtils.js";
8
16
 
9
17
  export interface AttachedImage {
10
18
  id: number;
@@ -19,6 +27,21 @@ export interface BtwState {
19
27
  isLoading: boolean;
20
28
  }
21
29
 
30
+ export type PendingEffect =
31
+ | {
32
+ type: "SEND_MESSAGE";
33
+ content: string;
34
+ images?: Array<{ path: string; mimeType: string }>;
35
+ longTextMap: Record<string, string>;
36
+ }
37
+ | { type: "ABORT_MESSAGE" }
38
+ | { type: "BACKGROUND_CURRENT_TASK" }
39
+ | { type: "ASK_BTW"; question: string }
40
+ | { type: "PERMISSION_MODE_CHANGE"; mode: PermissionMode }
41
+ | { type: "FETCH_HISTORY" }
42
+ | { type: "PASTE_IMAGE" }
43
+ | { type: "EXECUTE_COMMAND"; command: string };
44
+
22
45
  export interface InputManagerCallbacks {
23
46
  onInputTextChange?: (text: string) => void;
24
47
  onCursorPositionChange?: (position: number) => void;
@@ -85,7 +108,6 @@ export interface InputState {
85
108
  showPluginManager: boolean;
86
109
  showModelSelector: boolean;
87
110
  permissionMode: PermissionMode;
88
- allowBypassInCycle: boolean;
89
111
  selectorJustUsed: boolean;
90
112
  isPasting: boolean;
91
113
  pasteBuffer: string;
@@ -96,6 +118,7 @@ export interface InputState {
96
118
  originalLongTextMap: Record<string, string>;
97
119
  isFileSearching: boolean;
98
120
  btwState: BtwState;
121
+ pendingEffect: PendingEffect | null;
99
122
  }
100
123
 
101
124
  export const initialState: InputState = {
@@ -122,7 +145,6 @@ export const initialState: InputState = {
122
145
  showPluginManager: false,
123
146
  showModelSelector: false,
124
147
  permissionMode: "default",
125
- allowBypassInCycle: false,
126
148
  selectorJustUsed: false,
127
149
  isPasting: false,
128
150
  pasteBuffer: "",
@@ -137,6 +159,7 @@ export const initialState: InputState = {
137
159
  question: "",
138
160
  isLoading: false,
139
161
  },
162
+ pendingEffect: null,
140
163
  };
141
164
 
142
165
  export type InputAction =
@@ -166,7 +189,6 @@ export type InputAction =
166
189
  | { type: "SET_SHOW_PLUGIN_MANAGER"; payload: boolean }
167
190
  | { type: "SET_SHOW_MODEL_SELECTOR"; payload: boolean }
168
191
  | { type: "SET_PERMISSION_MODE"; payload: PermissionMode }
169
- | { type: "SET_ALLOW_BYPASS_IN_CYCLE"; payload: boolean }
170
192
  | { type: "SET_SELECTOR_JUST_USED"; payload: boolean }
171
193
  | { type: "INSERT_TEXT_WITH_PLACEHOLDER"; payload: string }
172
194
  | { type: "CLEAR_LONG_TEXT_MAP" }
@@ -186,7 +208,19 @@ export type InputAction =
186
208
  | { type: "NAVIGATE_HISTORY"; payload: "up" | "down" }
187
209
  | { type: "RESET_HISTORY_NAVIGATION" }
188
210
  | { type: "SELECT_HISTORY_ENTRY"; payload: PromptEntry }
189
- | { type: "SET_BTW_STATE"; payload: Partial<BtwState> };
211
+ | { type: "SELECT_COMMAND"; payload: string }
212
+ | { type: "INSERT_COMMAND"; payload: string }
213
+ | { type: "SELECT_FILE"; payload: string }
214
+ | { type: "SET_BTW_STATE"; payload: Partial<BtwState> }
215
+ | { type: "CLEAR_PENDING_EFFECT" }
216
+ | {
217
+ type: "HANDLE_KEY";
218
+ payload: {
219
+ input: string;
220
+ key: Key;
221
+ hasSlashCommand: (cmd: string) => boolean;
222
+ };
223
+ };
190
224
 
191
225
  export function inputReducer(
192
226
  state: InputState,
@@ -372,8 +406,6 @@ export function inputReducer(
372
406
  };
373
407
  case "SET_PERMISSION_MODE":
374
408
  return { ...state, permissionMode: action.payload };
375
- case "SET_ALLOW_BYPASS_IN_CYCLE":
376
- return { ...state, allowBypassInCycle: action.payload };
377
409
  case "SET_SELECTOR_JUST_USED":
378
410
  return { ...state, selectorJustUsed: action.payload };
379
411
  case "INSERT_TEXT_WITH_PLACEHOLDER": {
@@ -393,7 +425,7 @@ export function inputReducer(
393
425
  const newText = beforeCursor + textToInsert + afterCursor;
394
426
  const newCursorPosition = state.cursorPosition + textToInsert.length;
395
427
 
396
- return {
428
+ const newState: InputState = {
397
429
  ...state,
398
430
  inputText: newText,
399
431
  cursorPosition: newCursorPosition,
@@ -401,6 +433,34 @@ export function inputReducer(
401
433
  longTextMap: newLongTextMap,
402
434
  historyIndex: -1,
403
435
  };
436
+
437
+ // Sync selectors
438
+ const atPos = getAtSelectorPosition(newText, newCursorPosition);
439
+ if (atPos !== -1 && !newState.showFileSelector) {
440
+ newState.showFileSelector = true;
441
+ newState.atPosition = atPos;
442
+ newState.isFileSearching = true;
443
+ }
444
+
445
+ const slashPos = getSlashSelectorPosition(newText, newCursorPosition);
446
+ if (slashPos !== -1 && !newState.showCommandSelector) {
447
+ newState.showCommandSelector = true;
448
+ newState.slashPosition = slashPos;
449
+ }
450
+
451
+ if (newState.showFileSelector && newState.atPosition >= 0) {
452
+ newState.fileSearchQuery = newText.substring(
453
+ newState.atPosition + 1,
454
+ newCursorPosition,
455
+ );
456
+ } else if (newState.showCommandSelector && newState.slashPosition >= 0) {
457
+ newState.commandSearchQuery = newText.substring(
458
+ newState.slashPosition + 1,
459
+ newCursorPosition,
460
+ );
461
+ }
462
+
463
+ return newState;
404
464
  }
405
465
  case "CLEAR_LONG_TEXT_MAP":
406
466
  return { ...state, longTextMap: {} };
@@ -524,6 +584,72 @@ export function inputReducer(
524
584
  selectorJustUsed: true,
525
585
  };
526
586
  }
587
+ case "SELECT_COMMAND": {
588
+ const command = action.payload;
589
+ if (state.slashPosition >= 0) {
590
+ const wordEnd = getWordEnd(state.inputText, state.slashPosition);
591
+ const beforeSlash = state.inputText.substring(0, state.slashPosition);
592
+ const afterWord = state.inputText.substring(wordEnd);
593
+ const newInput = beforeSlash + afterWord;
594
+ const newCursorPosition = beforeSlash.length;
595
+
596
+ return {
597
+ ...state,
598
+ inputText: newInput,
599
+ cursorPosition: newCursorPosition,
600
+ showCommandSelector: false,
601
+ slashPosition: -1,
602
+ commandSearchQuery: "",
603
+ selectorJustUsed: true,
604
+ pendingEffect: { type: "EXECUTE_COMMAND", command },
605
+ };
606
+ }
607
+ return state;
608
+ }
609
+ case "INSERT_COMMAND": {
610
+ const command = action.payload;
611
+ if (state.slashPosition >= 0) {
612
+ const wordEnd = getWordEnd(state.inputText, state.slashPosition);
613
+ const beforeSlash = state.inputText.substring(0, state.slashPosition);
614
+ const afterWord = state.inputText.substring(wordEnd);
615
+ const newInput = beforeSlash + `/${command} ` + afterWord;
616
+ const newCursorPosition = beforeSlash.length + command.length + 2;
617
+
618
+ return {
619
+ ...state,
620
+ inputText: newInput,
621
+ cursorPosition: newCursorPosition,
622
+ showCommandSelector: false,
623
+ slashPosition: -1,
624
+ commandSearchQuery: "",
625
+ selectorJustUsed: true,
626
+ };
627
+ }
628
+ return state;
629
+ }
630
+ case "SELECT_FILE": {
631
+ const filePath = action.payload;
632
+ if (state.atPosition >= 0) {
633
+ const wordEnd = getWordEnd(state.inputText, state.atPosition);
634
+ const beforeAt = state.inputText.substring(0, state.atPosition);
635
+ const afterWord = state.inputText.substring(wordEnd);
636
+ const newInput = beforeAt + `@${filePath} ` + afterWord;
637
+ const newCursorPosition = beforeAt.length + filePath.length + 2;
638
+
639
+ return {
640
+ ...state,
641
+ inputText: newInput,
642
+ cursorPosition: newCursorPosition,
643
+ showFileSelector: false,
644
+ atPosition: -1,
645
+ fileSearchQuery: "",
646
+ filteredFiles: [],
647
+ selectorJustUsed: true,
648
+ isFileSearching: false,
649
+ };
650
+ }
651
+ return state;
652
+ }
527
653
  case "RESET_HISTORY_NAVIGATION":
528
654
  return {
529
655
  ...state,
@@ -532,6 +658,548 @@ export function inputReducer(
532
658
  originalInputText: "",
533
659
  originalLongTextMap: {},
534
660
  };
661
+ case "CLEAR_PENDING_EFFECT":
662
+ return { ...state, pendingEffect: null };
663
+ case "HANDLE_KEY": {
664
+ const { input, key } = action.payload;
665
+
666
+ if (state.selectorJustUsed) {
667
+ return state;
668
+ }
669
+
670
+ // 1. BTW State Handling
671
+ if (state.btwState.isActive) {
672
+ if (key.escape) {
673
+ return {
674
+ ...state,
675
+ btwState: {
676
+ isActive: false,
677
+ question: "",
678
+ answer: undefined,
679
+ isLoading: false,
680
+ },
681
+ };
682
+ }
683
+
684
+ if (key.return) {
685
+ const question = state.inputText.trim();
686
+ if (question && !state.btwState.isLoading) {
687
+ return {
688
+ ...state,
689
+ inputText: "",
690
+ cursorPosition: 0,
691
+ btwState: {
692
+ ...state.btwState,
693
+ question,
694
+ isLoading: true,
695
+ answer: undefined,
696
+ },
697
+ pendingEffect: { type: "ASK_BTW", question },
698
+ };
699
+ }
700
+ return state;
701
+ }
702
+ }
703
+
704
+ // 2. Escape Handling
705
+ if (key.escape) {
706
+ if (state.showFileSelector) {
707
+ return {
708
+ ...state,
709
+ showFileSelector: false,
710
+ atPosition: -1,
711
+ fileSearchQuery: "",
712
+ filteredFiles: [],
713
+ selectorJustUsed: true,
714
+ isFileSearching: false,
715
+ };
716
+ }
717
+ if (state.showCommandSelector) {
718
+ return {
719
+ ...state,
720
+ showCommandSelector: false,
721
+ slashPosition: -1,
722
+ commandSearchQuery: "",
723
+ selectorJustUsed: true,
724
+ };
725
+ }
726
+ if (state.showHistorySearch) {
727
+ return {
728
+ ...state,
729
+ showHistorySearch: false,
730
+ historySearchQuery: "",
731
+ selectorJustUsed: true,
732
+ };
733
+ }
734
+ if (state.historyIndex !== -1) {
735
+ return {
736
+ ...state,
737
+ historyIndex: -1,
738
+ inputText: state.originalInputText,
739
+ longTextMap: state.originalLongTextMap,
740
+ cursorPosition: state.originalInputText.length,
741
+ originalInputText: "",
742
+ originalLongTextMap: {},
743
+ };
744
+ }
745
+ if (
746
+ !(
747
+ state.showBackgroundTaskManager ||
748
+ state.showMcpManager ||
749
+ state.showRewindManager ||
750
+ state.showHelp ||
751
+ state.showStatusCommand ||
752
+ state.showPluginManager ||
753
+ state.showModelSelector
754
+ )
755
+ ) {
756
+ return { ...state, pendingEffect: { type: "ABORT_MESSAGE" } };
757
+ }
758
+ return state;
759
+ }
760
+
761
+ // 3. Special Shortcuts
762
+ if (key.tab && key.shift) {
763
+ const modes: PermissionMode[] = [
764
+ "default",
765
+ "acceptEdits",
766
+ "plan",
767
+ "bypassPermissions",
768
+ ];
769
+ const currentIndex = modes.indexOf(state.permissionMode);
770
+ const nextIndex =
771
+ currentIndex === -1 ? 0 : (currentIndex + 1) % modes.length;
772
+ const nextMode = modes[nextIndex];
773
+ return {
774
+ ...state,
775
+ permissionMode: nextMode,
776
+ pendingEffect: { type: "PERMISSION_MODE_CHANGE", mode: nextMode },
777
+ };
778
+ }
779
+
780
+ if (key.ctrl && input === "v") {
781
+ return { ...state, pendingEffect: { type: "PASTE_IMAGE" } };
782
+ }
783
+
784
+ if (key.ctrl && input === "r") {
785
+ return {
786
+ ...state,
787
+ showHistorySearch: true,
788
+ historySearchQuery: "",
789
+ };
790
+ }
791
+
792
+ if (key.ctrl && input === "b") {
793
+ return {
794
+ ...state,
795
+ pendingEffect: { type: "BACKGROUND_CURRENT_TASK" },
796
+ };
797
+ }
798
+
799
+ // 4. History Navigation
800
+ if (
801
+ key.upArrow &&
802
+ !state.showFileSelector &&
803
+ !state.showCommandSelector
804
+ ) {
805
+ if (state.history.length === 0) {
806
+ return { ...state, pendingEffect: { type: "FETCH_HISTORY" } };
807
+ }
808
+ // If history is already loaded, NAVIGATE_HISTORY logic follows
809
+ let newIndex = state.historyIndex;
810
+ let newOriginalInputText = state.originalInputText;
811
+ let newOriginalLongTextMap = state.originalLongTextMap;
812
+
813
+ if (newIndex === -1) {
814
+ newOriginalInputText = state.inputText;
815
+ newOriginalLongTextMap = state.longTextMap;
816
+ }
817
+ newIndex = Math.min(state.history.length - 1, newIndex + 1);
818
+ const entry = state.history[newIndex];
819
+ return {
820
+ ...state,
821
+ historyIndex: newIndex,
822
+ inputText: entry.prompt,
823
+ longTextMap: entry.longTextMap || {},
824
+ cursorPosition: entry.prompt.length,
825
+ originalInputText: newOriginalInputText,
826
+ originalLongTextMap: newOriginalLongTextMap,
827
+ };
828
+ }
829
+
830
+ if (
831
+ key.downArrow &&
832
+ !state.showFileSelector &&
833
+ !state.showCommandSelector
834
+ ) {
835
+ if (state.historyIndex === -1) return state;
836
+ const newIndex = state.historyIndex - 1;
837
+
838
+ if (newIndex === -1) {
839
+ return {
840
+ ...state,
841
+ historyIndex: -1,
842
+ inputText: state.originalInputText,
843
+ longTextMap: state.originalLongTextMap,
844
+ cursorPosition: state.originalInputText.length,
845
+ originalInputText: "",
846
+ originalLongTextMap: {},
847
+ };
848
+ } else {
849
+ const entry = state.history[newIndex];
850
+ return {
851
+ ...state,
852
+ historyIndex: newIndex,
853
+ inputText: entry.prompt,
854
+ longTextMap: entry.longTextMap || {},
855
+ cursorPosition: entry.prompt.length,
856
+ };
857
+ }
858
+ }
859
+
860
+ // 5. Active Selector Handling (History Search, File, Command)
861
+ if (state.showHistorySearch) {
862
+ if (key.backspace || key.delete) {
863
+ return {
864
+ ...state,
865
+ historySearchQuery: state.historySearchQuery.slice(0, -1),
866
+ };
867
+ }
868
+ if (input && !key.ctrl && !key.meta && !key.return && !key.tab) {
869
+ return {
870
+ ...state,
871
+ historySearchQuery: state.historySearchQuery + input,
872
+ };
873
+ }
874
+ return state;
875
+ }
876
+
877
+ if (state.showFileSelector || state.showCommandSelector) {
878
+ if (key.backspace || key.delete) {
879
+ if (state.cursorPosition > 0) {
880
+ const newCursorPosition = state.cursorPosition - 1;
881
+ const beforeCursor = state.inputText.substring(
882
+ 0,
883
+ state.cursorPosition - 1,
884
+ );
885
+ const afterCursor = state.inputText.substring(state.cursorPosition);
886
+ const newInputText = beforeCursor + afterCursor;
887
+
888
+ const newState = {
889
+ ...state,
890
+ inputText: newInputText,
891
+ cursorPosition: newCursorPosition,
892
+ historyIndex: -1,
893
+ };
894
+
895
+ // checkForAtDeletion
896
+ if (
897
+ newState.showFileSelector &&
898
+ newCursorPosition <= newState.atPosition
899
+ ) {
900
+ newState.showFileSelector = false;
901
+ newState.atPosition = -1;
902
+ newState.fileSearchQuery = "";
903
+ }
904
+ // checkForSlashDeletion
905
+ if (
906
+ newState.showCommandSelector &&
907
+ newCursorPosition <= newState.slashPosition
908
+ ) {
909
+ newState.showCommandSelector = false;
910
+ newState.slashPosition = -1;
911
+ newState.commandSearchQuery = "";
912
+ }
913
+
914
+ // Update queries
915
+ if (newState.showFileSelector && newState.atPosition >= 0) {
916
+ newState.fileSearchQuery = newInputText.substring(
917
+ newState.atPosition + 1,
918
+ newCursorPosition,
919
+ );
920
+ }
921
+ if (newState.showCommandSelector && newState.slashPosition >= 0) {
922
+ newState.commandSearchQuery = newInputText.substring(
923
+ newState.slashPosition + 1,
924
+ newCursorPosition,
925
+ );
926
+ }
927
+ return newState;
928
+ }
929
+ }
930
+ if (key.leftArrow || key.rightArrow) {
931
+ const delta = key.leftArrow ? -1 : 1;
932
+ const newCursorPosition = Math.max(
933
+ 0,
934
+ Math.min(state.inputText.length, state.cursorPosition + delta),
935
+ );
936
+ const newState = { ...state, cursorPosition: newCursorPosition };
937
+ if (
938
+ newState.showFileSelector &&
939
+ newCursorPosition <= newState.atPosition
940
+ ) {
941
+ newState.showFileSelector = false;
942
+ newState.atPosition = -1;
943
+ }
944
+ if (
945
+ newState.showCommandSelector &&
946
+ newCursorPosition <= newState.slashPosition
947
+ ) {
948
+ newState.showCommandSelector = false;
949
+ newState.slashPosition = -1;
950
+ }
951
+ return newState;
952
+ }
953
+ if (input === " ") {
954
+ return {
955
+ ...state,
956
+ showFileSelector: false,
957
+ atPosition: -1,
958
+ showCommandSelector: false,
959
+ slashPosition: -1,
960
+ inputText:
961
+ state.inputText.substring(0, state.cursorPosition) +
962
+ " " +
963
+ state.inputText.substring(state.cursorPosition),
964
+ cursorPosition: state.cursorPosition + 1,
965
+ };
966
+ }
967
+
968
+ if (key.return || key.tab || key.upArrow || key.downArrow) {
969
+ return state;
970
+ }
971
+ }
972
+
973
+ // 6. Return / Submit
974
+ if (key.return) {
975
+ if (state.inputText.trim()) {
976
+ const imageRegex = /\[Image #(\d+)\]/g;
977
+ const matches = [...state.inputText.matchAll(imageRegex)];
978
+ const referencedImages = matches
979
+ .map((match) => {
980
+ const imageId = parseInt(match[1], 10);
981
+ return state.attachedImages.find((img) => img.id === imageId);
982
+ })
983
+ .filter((img): img is AttachedImage => img !== undefined)
984
+ .map((img) => ({ path: img.path, mimeType: img.mimeType }));
985
+
986
+ const contentWithPlaceholders = state.inputText
987
+ .replace(imageRegex, "")
988
+ .trim();
989
+
990
+ if (
991
+ contentWithPlaceholders === "/btw" ||
992
+ contentWithPlaceholders.startsWith("/btw ")
993
+ ) {
994
+ const question = contentWithPlaceholders.startsWith("/btw ")
995
+ ? contentWithPlaceholders.substring(5).trim()
996
+ : "";
997
+
998
+ return {
999
+ ...state,
1000
+ inputText: "",
1001
+ cursorPosition: 0,
1002
+ historyIndex: -1,
1003
+ longTextMap: {},
1004
+ btwState: {
1005
+ isActive: true,
1006
+ question,
1007
+ isLoading: question !== "",
1008
+ answer: undefined,
1009
+ },
1010
+ pendingEffect: question ? { type: "ASK_BTW", question } : null,
1011
+ };
1012
+ }
1013
+
1014
+ return {
1015
+ ...state,
1016
+ inputText: "",
1017
+ cursorPosition: 0,
1018
+ historyIndex: -1,
1019
+ longTextMap: {},
1020
+ pendingEffect: {
1021
+ type: "SEND_MESSAGE",
1022
+ content: contentWithPlaceholders,
1023
+ images:
1024
+ referencedImages.length > 0 ? referencedImages : undefined,
1025
+ longTextMap: state.longTextMap,
1026
+ },
1027
+ };
1028
+ }
1029
+ return state;
1030
+ }
1031
+
1032
+ // 7. Regular Input
1033
+ if (
1034
+ input &&
1035
+ !key.ctrl &&
1036
+ !("alt" in key && key.alt) &&
1037
+ !key.meta &&
1038
+ !key.return &&
1039
+ !key.escape &&
1040
+ !key.leftArrow &&
1041
+ !key.rightArrow &&
1042
+ !("home" in key && key.home) &&
1043
+ !("end" in key && key.end)
1044
+ ) {
1045
+ const isPasteOperation =
1046
+ input.length > 1 || input.includes("\n") || input.includes("\r");
1047
+
1048
+ if (isPasteOperation) {
1049
+ const isNewPaste = !state.pasteBuffer;
1050
+ return {
1051
+ ...state,
1052
+ isPasting: true,
1053
+ pasteBuffer: state.pasteBuffer + input,
1054
+ initialPasteCursorPosition: isNewPaste
1055
+ ? state.cursorPosition
1056
+ : state.initialPasteCursorPosition,
1057
+ };
1058
+ } else {
1059
+ let char = input;
1060
+ if (char === "!" && state.cursorPosition === 0) {
1061
+ char = "!";
1062
+ }
1063
+
1064
+ const { newInputText, newCursorPosition } = getProjectedState(
1065
+ state.inputText,
1066
+ state.cursorPosition,
1067
+ char,
1068
+ );
1069
+
1070
+ const newState = {
1071
+ ...state,
1072
+ inputText: newInputText,
1073
+ cursorPosition: newCursorPosition,
1074
+ historyIndex: -1,
1075
+ };
1076
+
1077
+ // Selector Activation
1078
+ const trigger = SELECTOR_TRIGGERS.find((t) =>
1079
+ t.shouldActivate(
1080
+ char,
1081
+ newCursorPosition,
1082
+ newInputText,
1083
+ state.showFileSelector,
1084
+ ),
1085
+ );
1086
+
1087
+ if (trigger) {
1088
+ if (trigger.type === "ACTIVATE_FILE_SELECTOR") {
1089
+ newState.showFileSelector = true;
1090
+ newState.atPosition = newCursorPosition - 1;
1091
+ newState.fileSearchQuery = "";
1092
+ newState.isFileSearching = true;
1093
+ } else if (trigger.type === "ACTIVATE_COMMAND_SELECTOR") {
1094
+ newState.showCommandSelector = true;
1095
+ newState.slashPosition = newCursorPosition - 1;
1096
+ newState.commandSearchQuery = "";
1097
+ }
1098
+ } else {
1099
+ const atPos = getAtSelectorPosition(
1100
+ newInputText,
1101
+ newCursorPosition,
1102
+ );
1103
+ if (atPos !== -1 && !state.showFileSelector) {
1104
+ newState.showFileSelector = true;
1105
+ newState.atPosition = atPos;
1106
+ newState.fileSearchQuery = "";
1107
+ newState.isFileSearching = true;
1108
+ }
1109
+
1110
+ const slashPos = getSlashSelectorPosition(
1111
+ newInputText,
1112
+ newCursorPosition,
1113
+ );
1114
+ if (slashPos !== -1 && !state.showCommandSelector) {
1115
+ newState.showCommandSelector = true;
1116
+ newState.slashPosition = slashPos;
1117
+ newState.commandSearchQuery = "";
1118
+ }
1119
+ }
1120
+
1121
+ // Update queries
1122
+ if (newState.showFileSelector && newState.atPosition >= 0) {
1123
+ newState.fileSearchQuery = newInputText.substring(
1124
+ newState.atPosition + 1,
1125
+ newCursorPosition,
1126
+ );
1127
+ }
1128
+ if (newState.showCommandSelector && newState.slashPosition >= 0) {
1129
+ newState.commandSearchQuery = newInputText.substring(
1130
+ newState.slashPosition + 1,
1131
+ newCursorPosition,
1132
+ );
1133
+ }
1134
+
1135
+ return newState;
1136
+ }
1137
+ }
1138
+
1139
+ // 8. Backspace / Delete (Normal Mode)
1140
+ if (key.backspace || key.delete) {
1141
+ if (state.cursorPosition > 0) {
1142
+ const newCursorPosition = state.cursorPosition - 1;
1143
+ const beforeCursor = state.inputText.substring(
1144
+ 0,
1145
+ state.cursorPosition - 1,
1146
+ );
1147
+ const afterCursor = state.inputText.substring(state.cursorPosition);
1148
+ const newInputText = beforeCursor + afterCursor;
1149
+
1150
+ const newState = {
1151
+ ...state,
1152
+ inputText: newInputText,
1153
+ cursorPosition: newCursorPosition,
1154
+ historyIndex: -1,
1155
+ };
1156
+
1157
+ // Reactivate selectors if cursor is within word
1158
+ const atPos = getAtSelectorPosition(newInputText, newCursorPosition);
1159
+ if (atPos !== -1 && !state.showFileSelector) {
1160
+ newState.showFileSelector = true;
1161
+ newState.atPosition = atPos;
1162
+ newState.isFileSearching = true;
1163
+ }
1164
+
1165
+ const slashPos = getSlashSelectorPosition(
1166
+ newInputText,
1167
+ newCursorPosition,
1168
+ );
1169
+ if (slashPos !== -1 && !state.showCommandSelector) {
1170
+ newState.showCommandSelector = true;
1171
+ newState.slashPosition = slashPos;
1172
+ }
1173
+
1174
+ // Update queries
1175
+ if (newState.showFileSelector && newState.atPosition >= 0) {
1176
+ newState.fileSearchQuery = newInputText.substring(
1177
+ newState.atPosition + 1,
1178
+ newCursorPosition,
1179
+ );
1180
+ }
1181
+ if (newState.showCommandSelector && newState.slashPosition >= 0) {
1182
+ newState.commandSearchQuery = newInputText.substring(
1183
+ newState.slashPosition + 1,
1184
+ newCursorPosition,
1185
+ );
1186
+ }
1187
+ return newState;
1188
+ }
1189
+ }
1190
+
1191
+ // 9. Cursor Movement (Normal Mode)
1192
+ if (key.leftArrow || key.rightArrow) {
1193
+ const delta = key.leftArrow ? -1 : 1;
1194
+ const newCursorPosition = Math.max(
1195
+ 0,
1196
+ Math.min(state.inputText.length, state.cursorPosition + delta),
1197
+ );
1198
+ return { ...state, cursorPosition: newCursorPosition };
1199
+ }
1200
+
1201
+ return state;
1202
+ }
535
1203
  case "SET_BTW_STATE":
536
1204
  return {
537
1205
  ...state,