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