wave-code 0.14.4 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/acp/agent.d.ts.map +1 -1
  2. package/dist/acp/agent.js +10 -0
  3. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  4. package/dist/components/BackgroundTaskManager.js +17 -53
  5. package/dist/components/CommandSelector.d.ts.map +1 -1
  6. package/dist/components/CommandSelector.js +38 -28
  7. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  8. package/dist/components/ConfirmationSelector.js +18 -196
  9. package/dist/components/DiscoverView.d.ts.map +1 -1
  10. package/dist/components/DiscoverView.js +19 -11
  11. package/dist/components/FileSelector.d.ts.map +1 -1
  12. package/dist/components/FileSelector.js +29 -25
  13. package/dist/components/HelpView.d.ts.map +1 -1
  14. package/dist/components/HelpView.js +24 -28
  15. package/dist/components/HistorySearch.d.ts.map +1 -1
  16. package/dist/components/HistorySearch.js +26 -28
  17. package/dist/components/InputBox.d.ts.map +1 -1
  18. package/dist/components/InputBox.js +14 -6
  19. package/dist/components/InstalledView.d.ts.map +1 -1
  20. package/dist/components/InstalledView.js +19 -11
  21. package/dist/components/MarketplaceDetail.d.ts.map +1 -1
  22. package/dist/components/MarketplaceDetail.js +35 -16
  23. package/dist/components/MarketplaceView.d.ts.map +1 -1
  24. package/dist/components/MarketplaceView.js +21 -12
  25. package/dist/components/McpManager.d.ts.map +1 -1
  26. package/dist/components/McpManager.js +27 -46
  27. package/dist/components/ModelSelector.d.ts.map +1 -1
  28. package/dist/components/ModelSelector.js +27 -17
  29. package/dist/components/PluginDetail.d.ts.map +1 -1
  30. package/dist/components/PluginDetail.js +39 -26
  31. package/dist/components/RewindCommand.d.ts.map +1 -1
  32. package/dist/components/RewindCommand.js +27 -21
  33. package/dist/components/SessionSelector.d.ts.map +1 -1
  34. package/dist/components/SessionSelector.js +21 -16
  35. package/dist/components/StatusLine.d.ts.map +1 -1
  36. package/dist/components/StatusLine.js +5 -1
  37. package/dist/components/WorktreeExitPrompt.d.ts.map +1 -1
  38. package/dist/components/WorktreeExitPrompt.js +21 -12
  39. package/dist/contexts/useChat.d.ts +0 -1
  40. package/dist/contexts/useChat.d.ts.map +1 -1
  41. package/dist/contexts/useChat.js +13 -20
  42. package/dist/hooks/useInputManager.d.ts +5 -19
  43. package/dist/hooks/useInputManager.d.ts.map +1 -1
  44. package/dist/hooks/useInputManager.js +222 -79
  45. package/dist/managers/inputHandlers.d.ts +1 -1
  46. package/dist/managers/inputHandlers.d.ts.map +1 -1
  47. package/dist/managers/inputHandlers.js +3 -6
  48. package/dist/managers/inputReducer.d.ts +46 -4
  49. package/dist/managers/inputReducer.d.ts.map +1 -1
  50. package/dist/managers/inputReducer.js +519 -4
  51. package/dist/print-cli.d.ts.map +1 -1
  52. package/dist/print-cli.js +21 -9
  53. package/dist/reducers/backgroundTaskManagerReducer.d.ts +20 -0
  54. package/dist/reducers/backgroundTaskManagerReducer.d.ts.map +1 -1
  55. package/dist/reducers/backgroundTaskManagerReducer.js +83 -0
  56. package/dist/reducers/confirmationReducer.d.ts +11 -0
  57. package/dist/reducers/confirmationReducer.d.ts.map +1 -1
  58. package/dist/reducers/confirmationReducer.js +146 -0
  59. package/dist/reducers/helpSelectorReducer.d.ts +26 -0
  60. package/dist/reducers/helpSelectorReducer.d.ts.map +1 -0
  61. package/dist/reducers/helpSelectorReducer.js +52 -0
  62. package/dist/reducers/mcpManagerReducer.d.ts +22 -0
  63. package/dist/reducers/mcpManagerReducer.d.ts.map +1 -1
  64. package/dist/reducers/mcpManagerReducer.js +57 -0
  65. package/dist/reducers/pluginDetailReducer.d.ts +8 -0
  66. package/dist/reducers/pluginDetailReducer.d.ts.map +1 -1
  67. package/dist/reducers/pluginDetailReducer.js +32 -0
  68. package/dist/reducers/questionReducer.d.ts +21 -0
  69. package/dist/reducers/questionReducer.d.ts.map +1 -1
  70. package/dist/reducers/questionReducer.js +177 -0
  71. package/dist/reducers/rewindSelectorReducer.d.ts +22 -0
  72. package/dist/reducers/rewindSelectorReducer.d.ts.map +1 -0
  73. package/dist/reducers/rewindSelectorReducer.js +37 -0
  74. package/dist/reducers/selectorReducer.d.ts +22 -0
  75. package/dist/reducers/selectorReducer.d.ts.map +1 -0
  76. package/dist/reducers/selectorReducer.js +40 -0
  77. package/dist/utils/inputUtils.d.ts +18 -0
  78. package/dist/utils/inputUtils.d.ts.map +1 -0
  79. package/dist/utils/inputUtils.js +67 -0
  80. package/dist/utils/worktree.d.ts.map +1 -1
  81. package/dist/utils/worktree.js +1 -2
  82. package/package.json +2 -2
  83. package/src/acp/agent.ts +10 -0
  84. package/src/components/BackgroundTaskManager.tsx +19 -58
  85. package/src/components/CommandSelector.tsx +53 -44
  86. package/src/components/ConfirmationSelector.tsx +18 -202
  87. package/src/components/DiscoverView.tsx +21 -11
  88. package/src/components/FileSelector.tsx +40 -35
  89. package/src/components/HelpView.tsx +37 -43
  90. package/src/components/HistorySearch.tsx +29 -36
  91. package/src/components/InputBox.tsx +14 -7
  92. package/src/components/InstalledView.tsx +21 -11
  93. package/src/components/MarketplaceDetail.tsx +48 -24
  94. package/src/components/MarketplaceView.tsx +25 -10
  95. package/src/components/McpManager.tsx +29 -54
  96. package/src/components/ModelSelector.tsx +30 -23
  97. package/src/components/PluginDetail.tsx +50 -27
  98. package/src/components/RewindCommand.tsx +38 -32
  99. package/src/components/SessionSelector.tsx +23 -20
  100. package/src/components/StatusLine.tsx +10 -1
  101. package/src/components/WorktreeExitPrompt.tsx +23 -13
  102. package/src/contexts/useChat.tsx +13 -31
  103. package/src/hooks/useInputManager.ts +273 -141
  104. package/src/managers/inputHandlers.ts +1 -6
  105. package/src/managers/inputReducer.ts +675 -7
  106. package/src/print-cli.ts +20 -11
  107. package/src/reducers/backgroundTaskManagerReducer.ts +110 -1
  108. package/src/reducers/confirmationReducer.ts +173 -1
  109. package/src/reducers/helpSelectorReducer.ts +77 -0
  110. package/src/reducers/mcpManagerReducer.ts +82 -1
  111. package/src/reducers/pluginDetailReducer.ts +42 -1
  112. package/src/reducers/questionReducer.ts +204 -0
  113. package/src/reducers/rewindSelectorReducer.ts +54 -0
  114. package/src/reducers/selectorReducer.ts +57 -0
  115. package/src/utils/inputUtils.ts +94 -0
  116. package/src/utils/worktree.ts +1 -7
@@ -1,7 +1,8 @@
1
- import React, { useState } from "react";
1
+ import React, { useState, useReducer, useEffect } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import type { Message } from "wave-agent-sdk";
4
4
  import { getMessageContent } from "wave-agent-sdk";
5
+ import { rewindSelectorReducer } from "../reducers/rewindSelectorReducer.js";
5
6
 
6
7
  export interface RewindCommandProps {
7
8
  messages: Message[];
@@ -22,7 +23,7 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
22
23
  const [messages, setMessages] = useState<Message[]>(initialMessages);
23
24
  const [isLoading, setIsLoading] = useState(!!getFullMessageThread);
24
25
 
25
- React.useEffect(() => {
26
+ useEffect(() => {
26
27
  if (getFullMessageThread) {
27
28
  getFullMessageThread().then(({ messages: fullMessages }) => {
28
29
  setMessages(fullMessages);
@@ -37,48 +38,40 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
37
38
  .filter(({ msg }) => msg.role === "user" && !msg.isMeta);
38
39
 
39
40
  const MAX_VISIBLE_ITEMS = 3;
40
- const [selectedIndex, setSelectedIndex] = useState(checkpoints.length - 1);
41
+
42
+ const [state, dispatch] = useReducer(rewindSelectorReducer, {
43
+ selectedIndex: checkpoints.length - 1,
44
+ pendingDecision: null,
45
+ });
46
+
47
+ const { selectedIndex, pendingDecision } = state;
41
48
 
42
49
  // Update selectedIndex when checkpoints change (after loading full thread)
43
- React.useEffect(() => {
44
- setSelectedIndex(checkpoints.length - 1);
50
+ useEffect(() => {
51
+ dispatch({ type: "RESET_INDEX", index: checkpoints.length - 1 });
45
52
  }, [checkpoints.length]);
46
53
 
47
- // Calculate visible window
48
- const startIndex = Math.max(
49
- 0,
50
- Math.min(
51
- selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2),
52
- Math.max(0, checkpoints.length - MAX_VISIBLE_ITEMS),
53
- ),
54
- );
55
- const visibleCheckpoints = checkpoints.slice(
56
- startIndex,
57
- startIndex + MAX_VISIBLE_ITEMS,
58
- );
54
+ // Handle decisions from reducer
55
+ useEffect(() => {
56
+ if (!pendingDecision) return;
59
57
 
60
- useInput((input, key) => {
61
- if (key.return) {
58
+ if (pendingDecision === "select") {
62
59
  if (checkpoints.length > 0 && selectedIndex >= 0) {
63
60
  onSelect(checkpoints[selectedIndex].index);
64
61
  }
65
- return;
66
- }
67
-
68
- if (key.escape) {
62
+ } else if (pendingDecision === "cancel") {
69
63
  onCancel();
70
- return;
71
64
  }
72
65
 
73
- if (key.upArrow) {
74
- setSelectedIndex(Math.max(0, selectedIndex - 1));
75
- return;
76
- }
66
+ dispatch({ type: "CLEAR_DECISION" });
67
+ }, [pendingDecision, selectedIndex, checkpoints, onSelect, onCancel]);
77
68
 
78
- if (key.downArrow) {
79
- setSelectedIndex(Math.min(checkpoints.length - 1, selectedIndex + 1));
80
- return;
81
- }
69
+ useInput((input, key) => {
70
+ dispatch({
71
+ type: "HANDLE_KEY",
72
+ key,
73
+ maxIndex: checkpoints.length - 1,
74
+ });
82
75
  });
83
76
 
84
77
  if (isLoading) {
@@ -112,6 +105,19 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
112
105
  );
113
106
  }
114
107
 
108
+ // Calculate visible window
109
+ const startIndex = Math.max(
110
+ 0,
111
+ Math.min(
112
+ selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2),
113
+ Math.max(0, checkpoints.length - MAX_VISIBLE_ITEMS),
114
+ ),
115
+ );
116
+ const visibleCheckpoints = checkpoints.slice(
117
+ startIndex,
118
+ startIndex + MAX_VISIBLE_ITEMS,
119
+ );
120
+
115
121
  return (
116
122
  <Box
117
123
  flexDirection="column"
@@ -1,6 +1,7 @@
1
- import React, { useState } from "react";
1
+ import React, { useReducer, useEffect } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import type { SessionMetadata } from "wave-agent-sdk";
4
+ import { selectorReducer } from "../reducers/selectorReducer.js";
4
5
 
5
6
  export interface SessionSelectorProps {
6
7
  sessions: (SessionMetadata & { firstMessage?: string })[];
@@ -13,31 +14,33 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
13
14
  onSelect,
14
15
  onCancel,
15
16
  }) => {
16
- const [selectedIndex, setSelectedIndex] = useState(0);
17
+ const [state, dispatch] = useReducer(selectorReducer, {
18
+ selectedIndex: 0,
19
+ pendingDecision: null,
20
+ });
21
+
22
+ const { selectedIndex, pendingDecision } = state;
23
+
24
+ useInput((_input, key) => {
25
+ dispatch({
26
+ type: "HANDLE_KEY",
27
+ key,
28
+ maxIndex: sessions.length - 1,
29
+ hasInsert: false,
30
+ });
31
+ });
17
32
 
18
- useInput((input, key) => {
19
- if (key.return) {
33
+ useEffect(() => {
34
+ if (pendingDecision === "select") {
20
35
  if (sessions.length > 0 && selectedIndex < sessions.length) {
21
36
  onSelect(sessions[selectedIndex].id);
22
37
  }
23
- return;
24
- }
25
-
26
- if (key.escape) {
38
+ dispatch({ type: "CLEAR_DECISION" });
39
+ } else if (pendingDecision === "cancel") {
27
40
  onCancel();
28
- return;
41
+ dispatch({ type: "CLEAR_DECISION" });
29
42
  }
30
-
31
- if (key.upArrow) {
32
- setSelectedIndex(Math.max(0, selectedIndex - 1));
33
- return;
34
- }
35
-
36
- if (key.downArrow) {
37
- setSelectedIndex(Math.min(sessions.length - 1, selectedIndex + 1));
38
- return;
39
- }
40
- });
43
+ }, [pendingDecision, selectedIndex, sessions, onSelect, onCancel]);
41
44
 
42
45
  if (sessions.length === 0) {
43
46
  return (
@@ -25,7 +25,16 @@ export const StatusLine: React.FC<StatusLineProps> = ({
25
25
  ) : (
26
26
  <Text color="gray">
27
27
  Mode:{" "}
28
- <Text color={permissionMode === "plan" ? "yellow" : "cyan"}>
28
+ <Text
29
+ color={
30
+ permissionMode === "plan"
31
+ ? "yellow"
32
+ : permissionMode === "bypassPermissions"
33
+ ? "red"
34
+ : "cyan"
35
+ }
36
+ bold={permissionMode === "bypassPermissions"}
37
+ >
29
38
  {permissionMode}
30
39
  </Text>{" "}
31
40
  (Shift+Tab to cycle)
@@ -1,5 +1,6 @@
1
- import React, { useState } from "react";
1
+ import React, { useReducer, useEffect } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
+ import { selectorReducer } from "../reducers/selectorReducer.js";
3
4
 
4
5
  interface WorktreeExitPromptProps {
5
6
  name: string;
@@ -20,26 +21,35 @@ export const WorktreeExitPrompt: React.FC<WorktreeExitPromptProps> = ({
20
21
  onRemove,
21
22
  onCancel,
22
23
  }) => {
23
- const [selectedIndex, setSelectedIndex] = useState(0);
24
+ const [state, dispatch] = useReducer(selectorReducer, {
25
+ selectedIndex: 0,
26
+ pendingDecision: null,
27
+ });
24
28
 
25
- useInput((input, key) => {
26
- if (key.upArrow) {
27
- setSelectedIndex((prev) => (prev === 0 ? 1 : 0));
28
- }
29
- if (key.downArrow) {
30
- setSelectedIndex((prev) => (prev === 1 ? 0 : 1));
31
- }
32
- if (key.return) {
29
+ const { selectedIndex, pendingDecision } = state;
30
+
31
+ useInput((_input, key) => {
32
+ dispatch({
33
+ type: "HANDLE_KEY",
34
+ key,
35
+ maxIndex: 1,
36
+ hasInsert: false,
37
+ });
38
+ });
39
+
40
+ useEffect(() => {
41
+ if (pendingDecision === "select") {
33
42
  if (selectedIndex === 0) {
34
43
  onKeep();
35
44
  } else {
36
45
  onRemove();
37
46
  }
38
- }
39
- if (key.escape) {
47
+ dispatch({ type: "CLEAR_DECISION" });
48
+ } else if (pendingDecision === "cancel") {
40
49
  onCancel();
50
+ dispatch({ type: "CLEAR_DECISION" });
41
51
  }
42
- });
52
+ }, [pendingDecision, selectedIndex, onKeep, onRemove, onCancel]);
43
53
 
44
54
  return (
45
55
  <Box flexDirection="column" paddingX={1} marginY={1}>
@@ -24,6 +24,7 @@ import {
24
24
  AgentCallbacks,
25
25
  type ToolPermissionContext,
26
26
  OPERATION_CANCELLED_BY_USER,
27
+ extractLatestTotalTokens,
27
28
  } from "wave-agent-sdk";
28
29
  import { logger } from "../utils/logger.js";
29
30
  import { throttle } from "../utils/throttle.js";
@@ -79,7 +80,6 @@ export interface ChatContextType {
79
80
  // Permission functionality
80
81
  permissionMode: PermissionMode;
81
82
  setPermissionMode: (mode: PermissionMode) => void;
82
- allowBypassInCycle: boolean;
83
83
  // Permission confirmation state
84
84
  isConfirmationVisible: boolean;
85
85
  hasPendingConfirmations: boolean;
@@ -158,28 +158,19 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
158
158
  const [isTaskListVisible, setIsTaskListVisible] = useState(true);
159
159
 
160
160
  const [messages, setMessages] = useState<Message[]>([]);
161
+ const [latestTotalTokens, setLatestTotalTokens] = useState(0);
161
162
 
162
163
  const throttledSetMessages = useMemo(
163
164
  () =>
164
165
  throttle(
165
166
  () => {
166
167
  if (!isExpandedRef.current && agentRef.current) {
167
- setMessages([...agentRef.current.messages]);
168
+ const msgs = [...agentRef.current.messages];
169
+ setMessages(msgs);
170
+ setLatestTotalTokens(extractLatestTotalTokens(msgs));
168
171
  }
169
172
  },
170
- 300,
171
- { leading: true, trailing: true },
172
- ),
173
- [],
174
- );
175
-
176
- const throttledSetTokens = useMemo(
177
- () =>
178
- throttle(
179
- ((tokens: number) => {
180
- setlatestTotalTokens(tokens);
181
- }) as (...args: unknown[]) => void,
182
- 300,
173
+ 500,
183
174
  { leading: true, trailing: true },
184
175
  ),
185
176
  [],
@@ -189,19 +180,16 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
189
180
  isExpandedRef.current = isExpanded;
190
181
  if (isExpanded) {
191
182
  throttledSetMessages.cancel();
192
- throttledSetTokens.cancel();
193
183
  }
194
- }, [isExpanded, throttledSetMessages, throttledSetTokens]);
184
+ }, [isExpanded, throttledSetMessages]);
195
185
 
196
186
  useEffect(() => {
197
187
  return () => {
198
188
  throttledSetMessages.cancel();
199
- throttledSetTokens.cancel();
200
189
  };
201
- }, [throttledSetMessages, throttledSetTokens]);
190
+ }, [throttledSetMessages]);
202
191
 
203
192
  const [isLoading, setIsLoading] = useState(false);
204
- const [latestTotalTokens, setlatestTotalTokens] = useState(0);
205
193
  const [sessionId, setSessionId] = useState("");
206
194
  const [isCommandRunning, setIsCommandRunning] = useState(false);
207
195
  const [isCompacting, setIsCompacting] = useState(false);
@@ -301,9 +289,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
301
289
  // Status metadata state
302
290
  const [workingDirectory, setWorkingDirectory] = useState("");
303
291
 
304
- const allowBypassInCycle =
305
- !!bypassPermissions || initialPermissionMode === "bypassPermissions";
306
-
307
292
  const agentRef = useRef<Agent | null>(null);
308
293
 
309
294
  // Permission confirmation methods with queue support
@@ -349,9 +334,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
349
334
  onSessionIdChange: (sessionId) => {
350
335
  setSessionId(sessionId);
351
336
  },
352
- onLatestTotalTokensChange: (tokens) => {
353
- throttledSetTokens(tokens);
354
- },
355
337
  onCompactionStateChange: (isCompactingState) => {
356
338
  setIsCompacting(isCompactingState);
357
339
  },
@@ -429,7 +411,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
429
411
  setSessionId(agent.sessionId);
430
412
  setMessages(agent.messages);
431
413
  setIsLoading(agent.isLoading);
432
- setlatestTotalTokens(agent.latestTotalTokens);
414
+ setLatestTotalTokens(extractLatestTotalTokens(agent.messages));
433
415
  setIsCommandRunning(agent.isCommandRunning);
434
416
  setIsCompacting(agent.isCompacting);
435
417
  setPermissionModeState(agent.getPermissionMode());
@@ -462,7 +444,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
462
444
  model,
463
445
  initialPermissionMode,
464
446
  throttledSetMessages,
465
- throttledSetTokens,
466
447
  ],
467
448
  );
468
449
 
@@ -482,7 +463,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
482
463
  setSlashCommands([]);
483
464
  setSessionId("");
484
465
  setIsLoading(false);
485
- setlatestTotalTokens(0);
466
+ setLatestTotalTokens(0);
486
467
  setIsCommandRunning(false);
487
468
  setIsCompacting(false);
488
469
  if (currentSessionId) {
@@ -719,7 +700,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
719
700
  } else {
720
701
  // Transitioning to COLLAPSED: Restore from agent's actual state
721
702
  if (agentRef.current) {
722
- setMessages([...agentRef.current.messages]);
703
+ const msgs = [...agentRef.current.messages];
704
+ setMessages(msgs);
705
+ setLatestTotalTokens(extractLatestTotalTokens(msgs));
723
706
  }
724
707
  }
725
708
  // Force remount to re-render Static items with new isExpanded state
@@ -765,7 +748,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
765
748
  hasSlashCommand,
766
749
  permissionMode,
767
750
  setPermissionMode,
768
- allowBypassInCycle,
769
751
  isConfirmationVisible,
770
752
  hasPendingConfirmations: confirmationQueue.length > 0,
771
753
  confirmingTool,