wave-code 0.17.5 → 0.17.7

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.
@@ -8,7 +8,7 @@ interface BtwDisplayProps {
8
8
  }
9
9
 
10
10
  export const BtwDisplay: React.FC<BtwDisplayProps> = ({ btwState }) => {
11
- if (!btwState.isActive) {
11
+ if (!btwState.question) {
12
12
  return null;
13
13
  }
14
14
 
@@ -26,6 +26,9 @@ export const BtwDisplay: React.FC<BtwDisplayProps> = ({ btwState }) => {
26
26
  {btwState.answer && (
27
27
  <Box flexDirection="column">
28
28
  <Markdown>{btwState.answer}</Markdown>
29
+ <Text color="gray" dimColor>
30
+ ESC to dismiss
31
+ </Text>
29
32
  </Box>
30
33
  )}
31
34
  </Box>
@@ -76,6 +76,9 @@ export const InputBox: React.FC<InputBoxProps> = ({
76
76
  sessionId,
77
77
  workingDirectory,
78
78
  askBtw,
79
+ clearMessages,
80
+ compact,
81
+ goalCommand,
79
82
  currentModel,
80
83
  configuredModels,
81
84
  setModel,
@@ -155,6 +158,9 @@ export const InputBox: React.FC<InputBoxProps> = ({
155
158
  } = useInputManager({
156
159
  onSendMessage: sendMessage,
157
160
  onAskBtw: askBtw,
161
+ onClearMessages: clearMessages,
162
+ onCompact: compact,
163
+ onGoalCommand: goalCommand,
158
164
  onHasSlashCommand: hasSlashCommand,
159
165
  onAbortMessage: abortMessage,
160
166
  onBackgroundCurrentTask: backgroundCurrentTask,
@@ -328,53 +334,47 @@ export const InputBox: React.FC<InputBoxProps> = ({
328
334
  <WorkflowManager onCancel={() => setShowWorkflowManager(false)} />
329
335
  )}
330
336
 
331
- {showBackgroundTaskManager ||
332
- showMcpManager ||
333
- showRewindManager ||
334
- showHelp ||
335
- showStatusCommand ||
336
- showLoginCommand ||
337
- showPluginManager ||
338
- showWorkflowManager || (
339
- <Box flexDirection="column">
340
- <Box
341
- borderStyle="single"
342
- borderColor={btwState.isActive ? "cyan" : "gray"}
343
- borderLeft={false}
344
- borderRight={false}
345
- >
346
- <Text color={isPlaceholder ? "gray" : "white"}>
347
- {btwState.isActive && isPlaceholder ? (
348
- <>
349
- <Text backgroundColor="white" color="black">
350
- {" "}
351
- </Text>
352
- <Text color="cyan">Type your side question...</Text>
353
- </>
354
- ) : shouldShowCursor ? (
355
- <>
356
- {beforeCursor}
357
- <Text backgroundColor="white" color="black">
358
- {atCursor}
359
- </Text>
360
- {afterCursor}
361
- </>
362
- ) : (
363
- displayText
364
- )}
365
- </Text>
337
+ {btwState.question
338
+ ? null
339
+ : showBackgroundTaskManager ||
340
+ showMcpManager ||
341
+ showRewindManager ||
342
+ showHelp ||
343
+ showStatusCommand ||
344
+ showLoginCommand ||
345
+ showPluginManager ||
346
+ showWorkflowManager || (
347
+ <Box flexDirection="column">
348
+ <Box
349
+ borderStyle="single"
350
+ borderColor="gray"
351
+ borderLeft={false}
352
+ borderRight={false}
353
+ >
354
+ <Text color={isPlaceholder ? "gray" : "white"}>
355
+ {shouldShowCursor ? (
356
+ <>
357
+ {beforeCursor}
358
+ <Text backgroundColor="white" color="black">
359
+ {atCursor}
360
+ </Text>
361
+ {afterCursor}
362
+ </>
363
+ ) : (
364
+ displayText
365
+ )}
366
+ </Text>
367
+ </Box>
368
+ <StatusLine
369
+ permissionMode={permissionMode}
370
+ isShellCommand={isShellCommand}
371
+ isGoalActive={isGoalActive}
372
+ goalElapsed={goalElapsed}
373
+ latestTotalTokens={latestTotalTokens}
374
+ maxInputTokens={maxInputTokens}
375
+ />
366
376
  </Box>
367
- <StatusLine
368
- permissionMode={permissionMode}
369
- isShellCommand={isShellCommand}
370
- isBtwActive={btwState.isActive}
371
- isGoalActive={isGoalActive}
372
- goalElapsed={goalElapsed}
373
- latestTotalTokens={latestTotalTokens}
374
- maxInputTokens={maxInputTokens}
375
- />
376
- </Box>
377
- )}
377
+ )}
378
378
  </Box>
379
379
  );
380
380
  };
@@ -4,7 +4,6 @@ import { Box, Text } from "ink";
4
4
  export interface StatusLineProps {
5
5
  permissionMode: string;
6
6
  isShellCommand: boolean;
7
- isBtwActive: boolean;
8
7
  isGoalActive?: boolean;
9
8
  goalElapsed?: string;
10
9
  latestTotalTokens?: number;
@@ -14,7 +13,6 @@ export interface StatusLineProps {
14
13
  export const StatusLine: React.FC<StatusLineProps> = ({
15
14
  permissionMode,
16
15
  isShellCommand,
17
- isBtwActive,
18
16
  isGoalActive,
19
17
  goalElapsed,
20
18
  latestTotalTokens = 0,
@@ -30,11 +28,7 @@ export const StatusLine: React.FC<StatusLineProps> = ({
30
28
 
31
29
  return (
32
30
  <Box paddingRight={1} justifyContent="space-between" width="100%">
33
- {isBtwActive ? (
34
- <Text color="gray">
35
- Mode: <Text color="cyan">BTW</Text> (ESC to dismiss)
36
- </Text>
37
- ) : isShellCommand ? (
31
+ {isShellCommand ? (
38
32
  <Text color="gray">
39
33
  Shell: <Text color="yellow">Run shell command</Text>
40
34
  </Text>
@@ -68,4 +68,22 @@ export const AVAILABLE_COMMANDS: SlashCommand[] = [
68
68
  description: "View and manage workflow runs",
69
69
  handler: () => {}, // Handler here won't be used, actual processing is in the hook
70
70
  },
71
+ {
72
+ id: "clear",
73
+ name: "clear",
74
+ description: "Clear conversation history and reset session",
75
+ handler: () => {},
76
+ },
77
+ {
78
+ id: "compact",
79
+ name: "compact",
80
+ description: "Compact conversation history to reduce context usage",
81
+ handler: () => {},
82
+ },
83
+ {
84
+ id: "goal",
85
+ name: "goal",
86
+ description: "Set, check, or clear an autonomous goal for the session",
87
+ handler: () => {},
88
+ },
71
89
  ];
@@ -53,6 +53,9 @@ export interface ChatContextType {
53
53
  longTextMap?: Record<string, string>,
54
54
  ) => Promise<void>;
55
55
  askBtw: (question: string) => Promise<string>;
56
+ clearMessages: () => Promise<void>;
57
+ compact: (instructions?: string) => Promise<void>;
58
+ goalCommand: (args?: string) => Promise<void>;
56
59
  abortMessage: () => void;
57
60
  recallQueuedMessage: () => QueuedMessage | null;
58
61
  removeQueuedMessageById: (id: string) => boolean;
@@ -623,6 +626,27 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
623
626
  return await agentRef.current.askBtw(question);
624
627
  }, []);
625
628
 
629
+ const clearMessages = useCallback(async () => {
630
+ await agentRef.current?.clearMessages();
631
+ }, []);
632
+
633
+ const compact = useCallback(async (instructions?: string) => {
634
+ await agentRef.current?.compact(instructions);
635
+ }, []);
636
+
637
+ const goalCommand = useCallback(async (args?: string) => {
638
+ const trimmed = args?.trim() ?? "";
639
+ if (!trimmed) {
640
+ await agentRef.current?.showGoalStatus();
641
+ } else if (
642
+ ["clear", "stop", "off", "reset", "none", "cancel"].includes(trimmed)
643
+ ) {
644
+ await agentRef.current?.clearGoal();
645
+ } else {
646
+ await agentRef.current?.setGoal(trimmed);
647
+ }
648
+ }, []);
649
+
626
650
  // Unified interrupt method, interrupt both AI messages and command execution
627
651
  const abortMessage = useCallback(() => {
628
652
  agentRef.current?.abortMessage();
@@ -824,6 +848,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
824
848
  sessionId,
825
849
  sendMessage,
826
850
  askBtw,
851
+ clearMessages,
852
+ compact,
853
+ goalCommand,
827
854
  abortMessage,
828
855
  recallQueuedMessage,
829
856
  removeQueuedMessageById,
@@ -45,6 +45,9 @@ export const useInputManager = (
45
45
  logger,
46
46
  hasQueuedMessages: hasQueuedMessagesProp,
47
47
  onRecallQueuedMessage,
48
+ onClearMessages,
49
+ onCompact,
50
+ onGoalCommand,
48
51
  } = callbacks;
49
52
 
50
53
  // Handle debounced file search
@@ -212,11 +215,12 @@ export const useInputManager = (
212
215
  dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: true });
213
216
  } else if (command === "workflows") {
214
217
  dispatch({ type: "SET_SHOW_WORKFLOW_MANAGER", payload: true });
215
- } else if (command === "btw") {
216
- dispatch({
217
- type: "SET_BTW_STATE",
218
- payload: { isActive: true, question: "", isLoading: false },
219
- });
218
+ } else if (command === "clear") {
219
+ await onClearMessages?.();
220
+ } else if (command === "compact") {
221
+ await onCompact?.(effect.args);
222
+ } else if (command === "goal") {
223
+ await onGoalCommand?.(effect.args);
220
224
  }
221
225
  }
222
226
  break;
@@ -243,6 +247,9 @@ export const useInputManager = (
243
247
  logger,
244
248
  onHasSlashCommand,
245
249
  onRecallQueuedMessage,
250
+ onClearMessages,
251
+ onCompact,
252
+ onGoalCommand,
246
253
  ]);
247
254
 
248
255
  useEffect(() => {
@@ -320,39 +327,7 @@ export const useInputManager = (
320
327
  onImagesStateChange?.(state.attachedImages);
321
328
  }, [state.attachedImages, onImagesStateChange]);
322
329
 
323
- // Handle /btw side question
324
- useEffect(() => {
325
- if (
326
- state.btwState.isActive &&
327
- state.btwState.isLoading &&
328
- state.btwState.question
329
- ) {
330
- const askBtw = async () => {
331
- try {
332
- const answer = await onAskBtw?.(state.btwState.question);
333
- dispatch({
334
- type: "SET_BTW_STATE",
335
- payload: { answer, isLoading: false },
336
- });
337
- } catch (error) {
338
- console.error("Failed to ask side question:", error);
339
- dispatch({
340
- type: "SET_BTW_STATE",
341
- payload: {
342
- answer: "Error: Failed to get an answer for your side question.",
343
- isLoading: false,
344
- },
345
- });
346
- }
347
- };
348
- askBtw();
349
- }
350
- }, [
351
- state.btwState.isActive,
352
- state.btwState.isLoading,
353
- state.btwState.question,
354
- onAskBtw,
355
- ]);
330
+ // /btw side question is handled via pendingEffect "ASK_BTW" above
356
331
 
357
332
  // Methods
358
333
  const insertTextAtCursor = useCallback((text: string) => {
@@ -373,11 +373,12 @@ export const handleCommandSelect = (
373
373
  dispatch({ type: "SET_SHOW_MODEL_SELECTOR", payload: true });
374
374
  } else if (command === "workflows") {
375
375
  dispatch({ type: "SET_SHOW_WORKFLOW_MANAGER", payload: true });
376
- } else if (command === "btw") {
377
- dispatch({
378
- type: "SET_BTW_STATE",
379
- payload: { isActive: true, question: "", isLoading: false },
380
- });
376
+ } else if (command === "clear") {
377
+ await callbacks.onClearMessages?.();
378
+ } else if (command === "compact") {
379
+ await callbacks.onCompact?.();
380
+ } else if (command === "goal") {
381
+ await callbacks.onGoalCommand?.();
381
382
  }
382
383
  }
383
384
  })();
@@ -701,44 +702,17 @@ export const handleInput = async (
701
702
  key: Key,
702
703
  clearImages?: () => void,
703
704
  ): Promise<boolean> => {
704
- if (state.btwState.isActive) {
705
- if (key.escape) {
706
- dispatch({
707
- type: "SET_BTW_STATE",
708
- payload: {
709
- isActive: false,
710
- question: "",
711
- answer: undefined,
712
- isLoading: false,
713
- },
714
- });
715
- return true;
716
- }
717
-
718
- if (key.return) {
719
- if (state.inputText.trim() && !state.btwState.isLoading) {
720
- dispatch({
721
- type: "SET_BTW_STATE",
722
- payload: {
723
- question: state.inputText.trim(),
724
- isLoading: true,
725
- answer: undefined,
726
- },
727
- });
728
- dispatch({ type: "CLEAR_INPUT" });
729
- }
730
- return true;
731
- }
732
-
733
- // Handle normal input for the question
734
- return await handleNormalInput(
735
- state,
736
- dispatch,
737
- callbacks,
738
- input,
739
- key,
740
- clearImages,
741
- );
705
+ // Handle ESC to dismiss btw answer
706
+ if (key.escape && state.btwState.question && !state.btwState.isLoading) {
707
+ dispatch({
708
+ type: "SET_BTW_STATE",
709
+ payload: {
710
+ question: "",
711
+ answer: undefined,
712
+ isLoading: false,
713
+ },
714
+ });
715
+ return true;
742
716
  }
743
717
 
744
718
  if (key.escape) {
@@ -754,8 +728,7 @@ export const handleInput = async (
754
728
  state.showStatusCommand ||
755
729
  state.showPluginManager ||
756
730
  state.showModelSelector ||
757
- state.showWorkflowManager ||
758
- state.btwState.isActive
731
+ state.showWorkflowManager
759
732
  )
760
733
  ) {
761
734
  callbacks.onAbortMessage?.();
@@ -779,8 +752,7 @@ export const handleInput = async (
779
752
  state.showStatusCommand ||
780
753
  state.showPluginManager ||
781
754
  state.showModelSelector ||
782
- state.showWorkflowManager ||
783
- state.btwState.isActive
755
+ state.showWorkflowManager
784
756
  ) {
785
757
  if (
786
758
  state.showBackgroundTaskManager ||
@@ -790,8 +762,7 @@ export const handleInput = async (
790
762
  state.showStatusCommand ||
791
763
  state.showPluginManager ||
792
764
  state.showModelSelector ||
793
- state.showWorkflowManager ||
794
- state.btwState.isActive
765
+ state.showWorkflowManager
795
766
  ) {
796
767
  return true;
797
768
  }
@@ -22,7 +22,6 @@ export interface AttachedImage {
22
22
  }
23
23
 
24
24
  export interface BtwState {
25
- isActive: boolean;
26
25
  question: string;
27
26
  answer?: string;
28
27
  isLoading: boolean;
@@ -41,7 +40,7 @@ export type PendingEffect =
41
40
  | { type: "PERMISSION_MODE_CHANGE"; mode: PermissionMode }
42
41
  | { type: "FETCH_HISTORY" }
43
42
  | { type: "PASTE_IMAGE" }
44
- | { type: "EXECUTE_COMMAND"; command: string }
43
+ | { type: "EXECUTE_COMMAND"; command: string; args?: string }
45
44
  | { type: "RECALL_QUEUED_MESSAGE" };
46
45
 
47
46
  export interface InputManagerCallbacks {
@@ -78,6 +77,9 @@ export interface InputManagerCallbacks {
78
77
  onBackgroundCurrentTask?: () => void;
79
78
  onPermissionModeChange?: (mode: PermissionMode) => void;
80
79
  onAskBtw?: (question: string) => Promise<string>;
80
+ onClearMessages?: () => Promise<void>;
81
+ onCompact?: (instructions?: string) => Promise<void>;
82
+ onGoalCommand?: (args?: string) => Promise<void>;
81
83
  sessionId?: string;
82
84
  workdir?: string;
83
85
  getFullMessageThread?: () => Promise<{
@@ -164,7 +166,6 @@ export const initialState: InputState = {
164
166
  originalLongTextMap: {},
165
167
  isFileSearching: false,
166
168
  btwState: {
167
- isActive: false,
168
169
  question: "",
169
170
  isLoading: false,
170
171
  },
@@ -688,42 +689,19 @@ export function inputReducer(
688
689
  const { input, key } = action.payload;
689
690
  const hasQueuedMessages = action.payload.hasQueuedMessages ?? false;
690
691
 
691
- // 1. BTW State Handling
692
- if (state.btwState.isActive) {
693
- if (key.escape) {
692
+ // 1. Escape Handling
693
+ if (key.escape) {
694
+ // Dismiss btw answer
695
+ if (state.btwState.question && !state.btwState.isLoading) {
694
696
  return {
695
697
  ...state,
696
698
  btwState: {
697
- isActive: false,
698
699
  question: "",
699
700
  answer: undefined,
700
701
  isLoading: false,
701
702
  },
702
703
  };
703
704
  }
704
-
705
- if (key.return) {
706
- const question = state.inputText.trim();
707
- if (question && !state.btwState.isLoading) {
708
- return {
709
- ...state,
710
- inputText: "",
711
- cursorPosition: 0,
712
- btwState: {
713
- ...state.btwState,
714
- question,
715
- isLoading: true,
716
- answer: undefined,
717
- },
718
- pendingEffect: { type: "ASK_BTW", question },
719
- };
720
- }
721
- return state;
722
- }
723
- }
724
-
725
- // 2. Escape Handling
726
- if (key.escape) {
727
705
  if (state.showFileSelector) {
728
706
  return {
729
707
  ...state,
@@ -1017,13 +995,12 @@ export function inputReducer(
1017
995
  .replace(imageRegex, "")
1018
996
  .trim();
1019
997
 
1020
- if (
1021
- contentWithPlaceholders === "/btw" ||
1022
- contentWithPlaceholders.startsWith("/btw ")
1023
- ) {
1024
- const question = contentWithPlaceholders.startsWith("/btw ")
1025
- ? contentWithPlaceholders.substring(5).trim()
1026
- : "";
998
+ if (contentWithPlaceholders.startsWith("/btw ")) {
999
+ const question = contentWithPlaceholders.substring(5).trim();
1000
+ if (!question) {
1001
+ // Bare /btw with no question text — ignore
1002
+ return state;
1003
+ }
1027
1004
 
1028
1005
  return {
1029
1006
  ...state,
@@ -1032,15 +1009,19 @@ export function inputReducer(
1032
1009
  historyIndex: -1,
1033
1010
  longTextMap: {},
1034
1011
  btwState: {
1035
- isActive: true,
1036
1012
  question,
1037
- isLoading: question !== "",
1013
+ isLoading: true,
1038
1014
  answer: undefined,
1039
1015
  },
1040
- pendingEffect: question ? { type: "ASK_BTW", question } : null,
1016
+ pendingEffect: { type: "ASK_BTW", question },
1041
1017
  };
1042
1018
  }
1043
1019
 
1020
+ if (contentWithPlaceholders === "/btw") {
1021
+ // Bare /btw — ignore
1022
+ return state;
1023
+ }
1024
+
1044
1025
  // Check if the content is a CLI-internal slash command (help, tasks,
1045
1026
  // etc.) that should be executed locally rather than sent as a message.
1046
1027
  // Agent slash commands and unknown /commands always go to SEND_MESSAGE.
@@ -1055,6 +1036,11 @@ export function inputReducer(
1055
1036
  (cmd) => cmd.id === commandName,
1056
1037
  );
1057
1038
  if (isInternalCommand) {
1039
+ const argsText =
1040
+ spaceIndex === -1
1041
+ ? undefined
1042
+ : contentWithPlaceholders.substring(spaceIndex + 1).trim() ||
1043
+ undefined;
1058
1044
  return {
1059
1045
  ...state,
1060
1046
  inputText: "",
@@ -1064,6 +1050,7 @@ export function inputReducer(
1064
1050
  pendingEffect: {
1065
1051
  type: "EXECUTE_COMMAND",
1066
1052
  command: commandName,
1053
+ args: argsText,
1067
1054
  },
1068
1055
  };
1069
1056
  }