wave-code 0.0.6 → 0.0.8

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 (77) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +2 -2
  5. package/dist/components/App.d.ts +1 -0
  6. package/dist/components/App.d.ts.map +1 -1
  7. package/dist/components/App.js +4 -4
  8. package/dist/components/BashHistorySelector.d.ts.map +1 -1
  9. package/dist/components/BashHistorySelector.js +17 -3
  10. package/dist/components/ChatInterface.d.ts.map +1 -1
  11. package/dist/components/ChatInterface.js +4 -2
  12. package/dist/components/Confirmation.d.ts +11 -0
  13. package/dist/components/Confirmation.d.ts.map +1 -0
  14. package/dist/components/Confirmation.js +148 -0
  15. package/dist/components/DiffDisplay.d.ts +8 -0
  16. package/dist/components/DiffDisplay.d.ts.map +1 -0
  17. package/dist/components/DiffDisplay.js +168 -0
  18. package/dist/components/FileSelector.d.ts +2 -4
  19. package/dist/components/FileSelector.d.ts.map +1 -1
  20. package/dist/components/InputBox.d.ts.map +1 -1
  21. package/dist/components/InputBox.js +10 -1
  22. package/dist/components/MemoryDisplay.js +1 -1
  23. package/dist/components/MessageItem.d.ts +1 -2
  24. package/dist/components/MessageItem.d.ts.map +1 -1
  25. package/dist/components/MessageItem.js +3 -3
  26. package/dist/components/MessageList.d.ts.map +1 -1
  27. package/dist/components/MessageList.js +2 -2
  28. package/dist/components/ReasoningDisplay.d.ts +8 -0
  29. package/dist/components/ReasoningDisplay.d.ts.map +1 -0
  30. package/dist/components/ReasoningDisplay.js +10 -0
  31. package/dist/components/ToolResultDisplay.d.ts.map +1 -1
  32. package/dist/components/ToolResultDisplay.js +2 -1
  33. package/dist/contexts/useChat.d.ts +13 -1
  34. package/dist/contexts/useChat.d.ts.map +1 -1
  35. package/dist/contexts/useChat.js +117 -15
  36. package/dist/hooks/useInputManager.d.ts +3 -0
  37. package/dist/hooks/useInputManager.d.ts.map +1 -1
  38. package/dist/hooks/useInputManager.js +17 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +22 -4
  41. package/dist/managers/InputManager.d.ts +8 -0
  42. package/dist/managers/InputManager.d.ts.map +1 -1
  43. package/dist/managers/InputManager.js +33 -2
  44. package/dist/print-cli.d.ts +1 -0
  45. package/dist/print-cli.d.ts.map +1 -1
  46. package/dist/print-cli.js +36 -3
  47. package/dist/utils/toolParameterTransforms.d.ts +23 -0
  48. package/dist/utils/toolParameterTransforms.d.ts.map +1 -0
  49. package/dist/utils/toolParameterTransforms.js +77 -0
  50. package/package.json +6 -5
  51. package/src/cli.tsx +3 -1
  52. package/src/components/App.tsx +7 -3
  53. package/src/components/BashHistorySelector.tsx +26 -3
  54. package/src/components/ChatInterface.tsx +29 -15
  55. package/src/components/Confirmation.tsx +253 -0
  56. package/src/components/DiffDisplay.tsx +300 -0
  57. package/src/components/FileSelector.tsx +2 -4
  58. package/src/components/InputBox.tsx +37 -14
  59. package/src/components/MemoryDisplay.tsx +1 -1
  60. package/src/components/MessageItem.tsx +4 -12
  61. package/src/components/MessageList.tsx +0 -2
  62. package/src/components/ReasoningDisplay.tsx +33 -0
  63. package/src/components/ToolResultDisplay.tsx +4 -0
  64. package/src/contexts/useChat.tsx +178 -14
  65. package/src/hooks/useInputManager.ts +19 -0
  66. package/src/index.ts +34 -4
  67. package/src/managers/InputManager.ts +46 -2
  68. package/src/print-cli.ts +42 -2
  69. package/src/utils/toolParameterTransforms.ts +104 -0
  70. package/dist/components/DiffViewer.d.ts +0 -9
  71. package/dist/components/DiffViewer.d.ts.map +0 -1
  72. package/dist/components/DiffViewer.js +0 -221
  73. package/dist/utils/fileSearch.d.ts +0 -20
  74. package/dist/utils/fileSearch.d.ts.map +0 -1
  75. package/dist/utils/fileSearch.js +0 -102
  76. package/src/components/DiffViewer.tsx +0 -323
  77. package/src/utils/fileSearch.ts +0 -133
@@ -0,0 +1,253 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import type { PermissionDecision } from "wave-agent-sdk";
4
+
5
+ // Helper function to generate descriptive action text
6
+ const getActionDescription = (
7
+ toolName: string,
8
+ toolInput?: Record<string, unknown>,
9
+ ): string => {
10
+ if (!toolInput) {
11
+ return "Execute operation";
12
+ }
13
+
14
+ switch (toolName) {
15
+ case "Bash":
16
+ return `Execute command: ${toolInput.command || "unknown command"}`;
17
+ case "Edit":
18
+ return `Edit file: ${toolInput.file_path || "unknown file"}`;
19
+ case "MultiEdit":
20
+ return `Edit multiple sections in: ${toolInput.file_path || "unknown file"}`;
21
+ case "Delete":
22
+ return `Delete file: ${toolInput.target_file || "unknown file"}`;
23
+ case "Write":
24
+ return `Write to file: ${toolInput.file_path || "unknown file"}`;
25
+ default:
26
+ return "Execute operation";
27
+ }
28
+ };
29
+
30
+ export interface ConfirmationProps {
31
+ toolName: string;
32
+ toolInput?: Record<string, unknown>;
33
+ onDecision: (decision: PermissionDecision) => void;
34
+ onCancel: () => void;
35
+ onAbort: () => void;
36
+ }
37
+
38
+ interface ConfirmationState {
39
+ selectedOption: "allow" | "auto" | "alternative";
40
+ alternativeText: string;
41
+ hasUserInput: boolean; // to hide placeholder
42
+ }
43
+
44
+ export const Confirmation: React.FC<ConfirmationProps> = ({
45
+ toolName,
46
+ toolInput,
47
+ onDecision,
48
+ onCancel,
49
+ onAbort,
50
+ }) => {
51
+ const [state, setState] = useState<ConfirmationState>({
52
+ selectedOption: "allow",
53
+ alternativeText: "",
54
+ hasUserInput: false,
55
+ });
56
+
57
+ const getAutoOptionText = () => {
58
+ if (toolName === "Bash") {
59
+ return `Yes, and don't ask again for ${toolInput?.command || "this"} commands in this workdir`;
60
+ }
61
+ return "Yes, and auto-accept edits";
62
+ };
63
+
64
+ useInput((input, key) => {
65
+ // Handle ESC to cancel and abort
66
+ if (key.escape) {
67
+ onCancel();
68
+ onAbort();
69
+ return;
70
+ }
71
+
72
+ // Handle Enter to confirm selection
73
+ if (key.return) {
74
+ if (state.selectedOption === "allow") {
75
+ onDecision({ behavior: "allow" });
76
+ } else if (state.selectedOption === "auto") {
77
+ if (toolName === "Bash") {
78
+ onDecision({
79
+ behavior: "allow",
80
+ newPermissionRule: `Bash(${toolInput?.command})`,
81
+ });
82
+ } else {
83
+ onDecision({
84
+ behavior: "allow",
85
+ newPermissionMode: "acceptEdits",
86
+ });
87
+ }
88
+ } else {
89
+ // For alternative option, require text input
90
+ if (state.alternativeText.trim()) {
91
+ onDecision({
92
+ behavior: "deny",
93
+ message: state.alternativeText.trim(),
94
+ });
95
+ }
96
+ }
97
+ return;
98
+ }
99
+
100
+ // Handle numeric keys for quick selection (only if not typing in alternative)
101
+ if (state.selectedOption !== "alternative" || !state.hasUserInput) {
102
+ if (input === "1") {
103
+ onDecision({ behavior: "allow" });
104
+ return;
105
+ }
106
+ if (input === "2") {
107
+ if (toolName === "Bash") {
108
+ onDecision({
109
+ behavior: "allow",
110
+ newPermissionRule: `Bash(${toolInput?.command})`,
111
+ });
112
+ } else {
113
+ onDecision({
114
+ behavior: "allow",
115
+ newPermissionMode: "acceptEdits",
116
+ });
117
+ }
118
+ return;
119
+ }
120
+ if (input === "3") {
121
+ setState((prev) => ({ ...prev, selectedOption: "alternative" }));
122
+ return;
123
+ }
124
+ }
125
+
126
+ // Handle arrow keys for navigation
127
+ if (key.upArrow) {
128
+ setState((prev) => {
129
+ if (prev.selectedOption === "alternative")
130
+ return { ...prev, selectedOption: "auto" };
131
+ if (prev.selectedOption === "auto")
132
+ return { ...prev, selectedOption: "allow" };
133
+ return prev;
134
+ });
135
+ return;
136
+ }
137
+
138
+ if (key.downArrow) {
139
+ setState((prev) => {
140
+ if (prev.selectedOption === "allow")
141
+ return { ...prev, selectedOption: "auto" };
142
+ if (prev.selectedOption === "auto")
143
+ return { ...prev, selectedOption: "alternative" };
144
+ return prev;
145
+ });
146
+ return;
147
+ }
148
+
149
+ // Handle text input for alternative option
150
+ if (input && !key.ctrl && !key.meta && !("alt" in key && key.alt)) {
151
+ // Focus on alternative option when user starts typing
152
+ setState((prev) => ({
153
+ selectedOption: "alternative",
154
+ alternativeText: prev.alternativeText + input,
155
+ hasUserInput: true,
156
+ }));
157
+ return;
158
+ }
159
+
160
+ // Handle backspace and delete (same behavior - delete one character)
161
+ if (key.backspace || key.delete) {
162
+ setState((prev) => {
163
+ const newText = prev.alternativeText.slice(0, -1);
164
+ return {
165
+ ...prev,
166
+ selectedOption: "alternative",
167
+ alternativeText: newText,
168
+ hasUserInput: newText.length > 0,
169
+ };
170
+ });
171
+ return;
172
+ }
173
+ });
174
+
175
+ const placeholderText = "Type here to tell Wave what to do differently";
176
+ const showPlaceholder =
177
+ state.selectedOption === "alternative" && !state.hasUserInput;
178
+
179
+ return (
180
+ <Box
181
+ flexDirection="column"
182
+ borderStyle="single"
183
+ borderColor="yellow"
184
+ padding={1}
185
+ marginBottom={1}
186
+ >
187
+ <Text color="yellow" bold>
188
+ Tool: {toolName}
189
+ </Text>
190
+ <Text color="yellow">{getActionDescription(toolName, toolInput)}</Text>
191
+
192
+ <Box marginTop={1}>
193
+ <Text>Do you want to proceed?</Text>
194
+ </Box>
195
+
196
+ <Box marginTop={1} flexDirection="column">
197
+ {/* Option 1: Yes */}
198
+ <Box key="allow-option">
199
+ <Text
200
+ color={state.selectedOption === "allow" ? "black" : "white"}
201
+ backgroundColor={
202
+ state.selectedOption === "allow" ? "yellow" : undefined
203
+ }
204
+ bold={state.selectedOption === "allow"}
205
+ >
206
+ {state.selectedOption === "allow" ? "> " : " "}1. Yes
207
+ </Text>
208
+ </Box>
209
+
210
+ {/* Option 2: Auto-accept/Persistent */}
211
+ <Box key="auto-option">
212
+ <Text
213
+ color={state.selectedOption === "auto" ? "black" : "white"}
214
+ backgroundColor={
215
+ state.selectedOption === "auto" ? "yellow" : undefined
216
+ }
217
+ bold={state.selectedOption === "auto"}
218
+ >
219
+ {state.selectedOption === "auto" ? "> " : " "}2.{" "}
220
+ {getAutoOptionText()}
221
+ </Text>
222
+ </Box>
223
+
224
+ {/* Option 3: Alternative */}
225
+ <Box key="alternative-option">
226
+ <Text
227
+ color={state.selectedOption === "alternative" ? "black" : "white"}
228
+ backgroundColor={
229
+ state.selectedOption === "alternative" ? "yellow" : undefined
230
+ }
231
+ bold={state.selectedOption === "alternative"}
232
+ >
233
+ {state.selectedOption === "alternative" ? "> " : " "}3.{" "}
234
+ {showPlaceholder ? (
235
+ <Text color="gray" dimColor>
236
+ {placeholderText}
237
+ </Text>
238
+ ) : (
239
+ <Text>
240
+ {state.alternativeText ||
241
+ "Type here to tell Wave what to do differently"}
242
+ </Text>
243
+ )}
244
+ </Text>
245
+ </Box>
246
+ </Box>
247
+
248
+ <Box marginTop={1}>
249
+ <Text dimColor>Use ↑↓ or 1-3 to navigate • ESC to cancel</Text>
250
+ </Box>
251
+ </Box>
252
+ );
253
+ };
@@ -0,0 +1,300 @@
1
+ import React, { useMemo } from "react";
2
+ import { Box, Text } from "ink";
3
+ import { transformToolBlockToChanges } from "../utils/toolParameterTransforms.js";
4
+ import { diffLines, diffWords } from "diff";
5
+ import type { ToolBlock } from "wave-agent-sdk";
6
+
7
+ interface DiffDisplayProps {
8
+ toolBlock: ToolBlock;
9
+ }
10
+
11
+ export const DiffDisplay: React.FC<DiffDisplayProps> = ({ toolBlock }) => {
12
+ const showDiff =
13
+ ["running", "end"].includes(toolBlock.stage) &&
14
+ toolBlock.name &&
15
+ ["Write", "Edit", "MultiEdit"].includes(toolBlock.name);
16
+
17
+ // Diff detection and transformation using typed parameters
18
+ const changes = useMemo(() => {
19
+ if (!showDiff || !toolBlock.name || !toolBlock.parameters) return [];
20
+ try {
21
+ // Use local transformation with JSON parsing and type guards
22
+ return transformToolBlockToChanges(toolBlock.name, toolBlock.parameters);
23
+ } catch (error) {
24
+ console.warn("Error transforming tool block to changes:", error);
25
+ return [];
26
+ }
27
+ }, [toolBlock.name, toolBlock.parameters, showDiff]);
28
+
29
+ // Render word-level diff between two lines of text
30
+ const renderWordLevelDiff = (
31
+ oldLine: string,
32
+ newLine: string,
33
+ keyPrefix: string,
34
+ ) => {
35
+ try {
36
+ const changes = diffWords(oldLine, newLine);
37
+
38
+ const removedParts: React.ReactNode[] = [];
39
+ const addedParts: React.ReactNode[] = [];
40
+
41
+ changes.forEach((part, index) => {
42
+ if (part.removed) {
43
+ removedParts.push(
44
+ <Text
45
+ key={`removed-${keyPrefix}-${index}`}
46
+ color="black"
47
+ backgroundColor="red"
48
+ >
49
+ {part.value}
50
+ </Text>,
51
+ );
52
+ } else if (part.added) {
53
+ addedParts.push(
54
+ <Text
55
+ key={`added-${keyPrefix}-${index}`}
56
+ color="black"
57
+ backgroundColor="green"
58
+ >
59
+ {part.value}
60
+ </Text>,
61
+ );
62
+ } else {
63
+ // Unchanged parts
64
+ removedParts.push(
65
+ <Text key={`removed-unchanged-${keyPrefix}-${index}`} color="red">
66
+ {part.value}
67
+ </Text>,
68
+ );
69
+ addedParts.push(
70
+ <Text key={`added-unchanged-${keyPrefix}-${index}`} color="green">
71
+ {part.value}
72
+ </Text>,
73
+ );
74
+ }
75
+ });
76
+
77
+ return { removedParts, addedParts };
78
+ } catch (error) {
79
+ console.warn("Error rendering word-level diff:", error);
80
+ // Fallback to simple line display
81
+ return {
82
+ removedParts: [
83
+ <Text key={`fallback-removed-${keyPrefix}`} color="red">
84
+ {oldLine}
85
+ </Text>,
86
+ ],
87
+ addedParts: [
88
+ <Text key={`fallback-added-${keyPrefix}`} color="green">
89
+ {newLine}
90
+ </Text>,
91
+ ],
92
+ };
93
+ }
94
+ };
95
+
96
+ // Render expanded diff display using word-level diff for all changes
97
+ const renderExpandedDiff = () => {
98
+ try {
99
+ if (changes.length === 0) return null;
100
+
101
+ return (
102
+ <Box flexDirection="column">
103
+ {changes.map((change, changeIndex) => {
104
+ try {
105
+ // Get line-level diff to understand the structure
106
+ const lineDiffs = diffLines(
107
+ change.oldContent || "",
108
+ change.newContent || "",
109
+ );
110
+
111
+ const diffElements: React.ReactNode[] = [];
112
+
113
+ // Process line diffs and apply word-level diff to changed lines
114
+ lineDiffs.forEach((part, partIndex) => {
115
+ if (part.added) {
116
+ const lines = part.value
117
+ .split("\n")
118
+ .filter((line) => line !== "");
119
+ lines.forEach((line, lineIndex) => {
120
+ diffElements.push(
121
+ <Box
122
+ key={`add-${changeIndex}-${partIndex}-${lineIndex}`}
123
+ flexDirection="row"
124
+ >
125
+ <Text color="green">+</Text>
126
+ <Text color="green">{line}</Text>
127
+ </Box>,
128
+ );
129
+ });
130
+ } else if (part.removed) {
131
+ const lines = part.value
132
+ .split("\n")
133
+ .filter((line) => line !== "");
134
+ lines.forEach((line, lineIndex) => {
135
+ diffElements.push(
136
+ <Box
137
+ key={`remove-${changeIndex}-${partIndex}-${lineIndex}`}
138
+ flexDirection="row"
139
+ >
140
+ <Text color="red">-</Text>
141
+ <Text color="red">{line}</Text>
142
+ </Box>,
143
+ );
144
+ });
145
+ } else {
146
+ // Context lines - show unchanged content
147
+ const lines = part.value
148
+ .split("\n")
149
+ .filter((line) => line !== "");
150
+ lines.forEach((line, lineIndex) => {
151
+ diffElements.push(
152
+ <Box
153
+ key={`context-${changeIndex}-${partIndex}-${lineIndex}`}
154
+ flexDirection="row"
155
+ >
156
+ <Text color="white"> </Text>
157
+ <Text color="white">{line}</Text>
158
+ </Box>,
159
+ );
160
+ });
161
+ }
162
+ });
163
+
164
+ // Now look for pairs of removed/added lines that can be word-diffed
165
+ const processedElements: React.ReactNode[] = [];
166
+ let i = 0;
167
+
168
+ while (i < diffElements.length) {
169
+ const current = diffElements[i];
170
+ const next =
171
+ i + 1 < diffElements.length ? diffElements[i + 1] : null;
172
+
173
+ // Check if we have a removed line followed by an added line
174
+ const currentKey = React.isValidElement(current)
175
+ ? current.key
176
+ : "";
177
+ const nextKey = React.isValidElement(next) ? next.key : "";
178
+
179
+ const isCurrentRemoved =
180
+ typeof currentKey === "string" &&
181
+ currentKey.includes("remove-");
182
+ const isNextAdded =
183
+ typeof nextKey === "string" && nextKey.includes("add-");
184
+
185
+ if (
186
+ isCurrentRemoved &&
187
+ isNextAdded &&
188
+ React.isValidElement(current) &&
189
+ React.isValidElement(next)
190
+ ) {
191
+ // Extract the text content from the removed and added lines
192
+ const removedText = extractTextFromElement(current);
193
+ const addedText = extractTextFromElement(next);
194
+
195
+ if (removedText && addedText) {
196
+ // Apply word-level diff
197
+ const { removedParts, addedParts } = renderWordLevelDiff(
198
+ removedText,
199
+ addedText,
200
+ `word-${changeIndex}-${i}`,
201
+ );
202
+
203
+ processedElements.push(
204
+ <Box
205
+ key={`word-diff-removed-${changeIndex}-${i}`}
206
+ flexDirection="row"
207
+ >
208
+ <Text color="red">-</Text>
209
+ {removedParts}
210
+ </Box>,
211
+ );
212
+ processedElements.push(
213
+ <Box
214
+ key={`word-diff-added-${changeIndex}-${i}`}
215
+ flexDirection="row"
216
+ >
217
+ <Text color="green">+</Text>
218
+ {addedParts}
219
+ </Box>,
220
+ );
221
+
222
+ i += 2; // Skip the next element since we processed it
223
+ } else {
224
+ // Fallback to original elements
225
+ processedElements.push(current);
226
+ i += 1;
227
+ }
228
+ } else {
229
+ processedElements.push(current);
230
+ i += 1;
231
+ }
232
+ }
233
+
234
+ return (
235
+ <Box key={changeIndex} flexDirection="column">
236
+ {processedElements}
237
+ </Box>
238
+ );
239
+ } catch (error) {
240
+ console.warn(
241
+ `Error rendering diff for change ${changeIndex}:`,
242
+ error,
243
+ );
244
+ // Fallback to simple display
245
+ return (
246
+ <Box key={changeIndex} flexDirection="column">
247
+ <Text color="red">-{change.oldContent || ""}</Text>
248
+ <Text color="green">+{change.newContent || ""}</Text>
249
+ </Box>
250
+ );
251
+ }
252
+ })}
253
+ </Box>
254
+ );
255
+ } catch (error) {
256
+ console.warn("Error rendering expanded diff:", error);
257
+ return (
258
+ <Box>
259
+ <Text color="gray">Error rendering diff display</Text>
260
+ </Box>
261
+ );
262
+ }
263
+ };
264
+
265
+ // Helper function to extract text content from a React element
266
+ const extractTextFromElement = (element: React.ReactNode): string | null => {
267
+ if (!React.isValidElement(element)) return null;
268
+
269
+ // Navigate through Box -> Text structure
270
+ const children = (
271
+ element.props as unknown as { children?: React.ReactNode[] }
272
+ ).children;
273
+ if (Array.isArray(children) && children.length >= 2) {
274
+ const textElement = children[1]; // Second child should be the Text with content
275
+ if (
276
+ React.isValidElement(textElement) &&
277
+ (textElement.props as unknown as { children?: string }).children
278
+ ) {
279
+ return (textElement.props as unknown as { children: string }).children;
280
+ }
281
+ }
282
+ return null;
283
+ };
284
+
285
+ // Don't render anything if no diff should be shown
286
+ if (!showDiff) {
287
+ return null;
288
+ }
289
+
290
+ return (
291
+ <Box flexDirection="column">
292
+ <Box paddingLeft={2} borderLeft borderColor="cyan" flexDirection="column">
293
+ <Text color="cyan" bold>
294
+ Diff:
295
+ </Text>
296
+ {renderExpandedDiff()}
297
+ </Box>
298
+ </Box>
299
+ );
300
+ };
@@ -1,10 +1,8 @@
1
1
  import React, { useState } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
+ import type { FileItem } from "wave-agent-sdk";
3
4
 
4
- export interface FileItem {
5
- path: string;
6
- type: "file" | "directory";
7
- }
5
+ export { type FileItem } from "wave-agent-sdk";
8
6
 
9
7
  export interface FileSelectorProps {
10
8
  files: FileItem[];
@@ -8,6 +8,7 @@ import { MemoryTypeSelector } from "./MemoryTypeSelector.js";
8
8
  import { BashShellManager } from "./BashShellManager.js";
9
9
  import { McpManager } from "./McpManager.js";
10
10
  import { useInputManager } from "../hooks/useInputManager.js";
11
+ import { useChat } from "../contexts/useChat.js";
11
12
 
12
13
  import type { McpServerStatus, SlashCommand } from "wave-agent-sdk";
13
14
 
@@ -56,6 +57,11 @@ export const InputBox: React.FC<InputBoxProps> = ({
56
57
  // Get current working directory - memoized to avoid repeated process.cwd() calls
57
58
  const currentWorkdir = useMemo(() => workdir || process.cwd(), [workdir]);
58
59
 
60
+ const {
61
+ permissionMode: chatPermissionMode,
62
+ setPermissionMode: setChatPermissionMode,
63
+ } = useChat();
64
+
59
65
  // Input manager with all input state and functionality (including images)
60
66
  const {
61
67
  inputText,
@@ -90,6 +96,9 @@ export const InputBox: React.FC<InputBoxProps> = ({
90
96
  showMcpManager,
91
97
  setShowBashManager,
92
98
  setShowMcpManager,
99
+ // Permission mode
100
+ permissionMode,
101
+ setPermissionMode,
93
102
  // Input history
94
103
  setUserInputHistory,
95
104
  // Complex handlers combining multiple operations
@@ -103,8 +112,14 @@ export const InputBox: React.FC<InputBoxProps> = ({
103
112
  onHasSlashCommand: hasSlashCommand,
104
113
  onSaveMemory: saveMemory,
105
114
  onAbortMessage: abortMessage,
115
+ onPermissionModeChange: setChatPermissionMode,
106
116
  });
107
117
 
118
+ // Sync permission mode from useChat to InputManager
119
+ useEffect(() => {
120
+ setPermissionMode(chatPermissionMode);
121
+ }, [chatPermissionMode, setPermissionMode]);
122
+
108
123
  // Set user input history when it changes
109
124
  useEffect(() => {
110
125
  setUserInputHistory(userInputHistory);
@@ -198,20 +213,28 @@ export const InputBox: React.FC<InputBoxProps> = ({
198
213
  />
199
214
  )}
200
215
  {showBashManager || showMcpManager || (
201
- <Box borderStyle="single" borderColor="gray" paddingX={1}>
202
- <Text color={isPlaceholder ? "gray" : "white"}>
203
- {shouldShowCursor ? (
204
- <>
205
- {beforeCursor}
206
- <Text backgroundColor="white" color="black">
207
- {atCursor}
208
- </Text>
209
- {afterCursor}
210
- </>
211
- ) : (
212
- displayText
213
- )}
214
- </Text>
216
+ <Box flexDirection="column">
217
+ <Box borderStyle="single" borderColor="gray" paddingX={1}>
218
+ <Text color={isPlaceholder ? "gray" : "white"}>
219
+ {shouldShowCursor ? (
220
+ <>
221
+ {beforeCursor}
222
+ <Text backgroundColor="white" color="black">
223
+ {atCursor}
224
+ </Text>
225
+ {afterCursor}
226
+ </>
227
+ ) : (
228
+ displayText
229
+ )}
230
+ </Text>
231
+ </Box>
232
+ <Box paddingX={1}>
233
+ <Text color="gray">
234
+ Mode: <Text color="cyan">{permissionMode}</Text> (Shift+Tab to
235
+ cycle)
236
+ </Text>
237
+ </Box>
215
238
  </Box>
216
239
  )}
217
240
  </Box>
@@ -25,7 +25,7 @@ export const MemoryDisplay: React.FC<MemoryDisplayProps> = ({ block }) => {
25
25
  if (!isSuccess) return null;
26
26
 
27
27
  if (memoryType === "user") {
28
- return `Memory saved to ${storagePath || "user-memory.md"}`;
28
+ return `Memory saved to ${storagePath || "AGENTS.md"}`;
29
29
  } else {
30
30
  return `Memory saved to ${storagePath || "AGENTS.md"}`;
31
31
  }