wave-code 0.0.5 → 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 (93) hide show
  1. package/README.md +3 -3
  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 +6 -24
  12. package/dist/components/CommandSelector.js +4 -4
  13. package/dist/components/Confirmation.d.ts +11 -0
  14. package/dist/components/Confirmation.d.ts.map +1 -0
  15. package/dist/components/Confirmation.js +148 -0
  16. package/dist/components/DiffDisplay.d.ts +8 -0
  17. package/dist/components/DiffDisplay.d.ts.map +1 -0
  18. package/dist/components/DiffDisplay.js +168 -0
  19. package/dist/components/FileSelector.d.ts +2 -4
  20. package/dist/components/FileSelector.d.ts.map +1 -1
  21. package/dist/components/FileSelector.js +2 -2
  22. package/dist/components/InputBox.d.ts.map +1 -1
  23. package/dist/components/InputBox.js +30 -50
  24. package/dist/components/Markdown.d.ts +6 -0
  25. package/dist/components/Markdown.d.ts.map +1 -0
  26. package/dist/components/Markdown.js +22 -0
  27. package/dist/components/MemoryDisplay.js +1 -1
  28. package/dist/components/MessageItem.d.ts +8 -0
  29. package/dist/components/MessageItem.d.ts.map +1 -0
  30. package/dist/components/MessageItem.js +15 -0
  31. package/dist/components/MessageList.d.ts +1 -1
  32. package/dist/components/MessageList.d.ts.map +1 -1
  33. package/dist/components/MessageList.js +33 -33
  34. package/dist/components/ReasoningDisplay.d.ts +8 -0
  35. package/dist/components/ReasoningDisplay.d.ts.map +1 -0
  36. package/dist/components/ReasoningDisplay.js +10 -0
  37. package/dist/components/SubagentBlock.d.ts +0 -1
  38. package/dist/components/SubagentBlock.d.ts.map +1 -1
  39. package/dist/components/SubagentBlock.js +29 -30
  40. package/dist/components/ToolResultDisplay.d.ts.map +1 -1
  41. package/dist/components/ToolResultDisplay.js +6 -5
  42. package/dist/contexts/useChat.d.ts +14 -2
  43. package/dist/contexts/useChat.d.ts.map +1 -1
  44. package/dist/contexts/useChat.js +128 -17
  45. package/dist/hooks/useInputManager.d.ts +6 -1
  46. package/dist/hooks/useInputManager.d.ts.map +1 -1
  47. package/dist/hooks/useInputManager.js +32 -2
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +30 -5
  50. package/dist/managers/InputManager.d.ts +11 -1
  51. package/dist/managers/InputManager.d.ts.map +1 -1
  52. package/dist/managers/InputManager.js +77 -26
  53. package/dist/print-cli.d.ts +2 -0
  54. package/dist/print-cli.d.ts.map +1 -1
  55. package/dist/print-cli.js +121 -23
  56. package/dist/utils/toolParameterTransforms.d.ts +23 -0
  57. package/dist/utils/toolParameterTransforms.d.ts.map +1 -0
  58. package/dist/utils/toolParameterTransforms.js +77 -0
  59. package/dist/utils/usageSummary.d.ts +6 -0
  60. package/dist/utils/usageSummary.d.ts.map +1 -1
  61. package/dist/utils/usageSummary.js +72 -0
  62. package/package.json +13 -8
  63. package/src/cli.tsx +3 -1
  64. package/src/components/App.tsx +7 -3
  65. package/src/components/BashHistorySelector.tsx +26 -3
  66. package/src/components/ChatInterface.tsx +38 -54
  67. package/src/components/CommandSelector.tsx +5 -5
  68. package/src/components/Confirmation.tsx +253 -0
  69. package/src/components/DiffDisplay.tsx +300 -0
  70. package/src/components/FileSelector.tsx +4 -6
  71. package/src/components/InputBox.tsx +58 -87
  72. package/src/components/Markdown.tsx +29 -0
  73. package/src/components/MemoryDisplay.tsx +1 -1
  74. package/src/components/MessageItem.tsx +96 -0
  75. package/src/components/MessageList.tsx +140 -202
  76. package/src/components/ReasoningDisplay.tsx +33 -0
  77. package/src/components/SubagentBlock.tsx +56 -84
  78. package/src/components/ToolResultDisplay.tsx +9 -5
  79. package/src/contexts/useChat.tsx +194 -21
  80. package/src/hooks/useInputManager.ts +40 -3
  81. package/src/index.ts +45 -5
  82. package/src/managers/InputManager.ts +101 -27
  83. package/src/print-cli.ts +143 -21
  84. package/src/utils/toolParameterTransforms.ts +104 -0
  85. package/src/utils/usageSummary.ts +109 -0
  86. package/dist/components/DiffViewer.d.ts +0 -9
  87. package/dist/components/DiffViewer.d.ts.map +0 -1
  88. package/dist/components/DiffViewer.js +0 -221
  89. package/dist/utils/fileSearch.d.ts +0 -20
  90. package/dist/utils/fileSearch.d.ts.map +0 -1
  91. package/dist/utils/fileSearch.js +0 -102
  92. package/src/components/DiffViewer.tsx +0 -321
  93. package/src/utils/fileSearch.ts +0 -133
@@ -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[];
@@ -22,7 +20,7 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
22
20
  const [selectedIndex, setSelectedIndex] = useState(0);
23
21
 
24
22
  useInput((input, key) => {
25
- if (key.return) {
23
+ if (key.return || key.tab) {
26
24
  if (files.length > 0 && selectedIndex < files.length) {
27
25
  onSelect(files[selectedIndex].path);
28
26
  }
@@ -126,7 +124,7 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
126
124
 
127
125
  <Box marginTop={1}>
128
126
  <Text dimColor>
129
- Use ↑↓ to navigate, Enter to select, Escape to cancel
127
+ Use ↑↓ to navigate, Enter/Tab to select, Escape to cancel
130
128
  </Text>
131
129
  <Text dimColor>
132
130
  File {selectedIndex + 1} of {files.length}
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect } from "react";
1
+ import React, { useEffect, useMemo } from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import { useInput } from "ink";
4
4
  import { FileSelector } from "./FileSelector.js";
@@ -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
 
@@ -53,19 +54,18 @@ export const InputBox: React.FC<InputBoxProps> = ({
53
54
  slashCommands = [],
54
55
  hasSlashCommand = () => false,
55
56
  }) => {
56
- // Get current working directory
57
- const currentWorkdir = workdir || process.cwd();
57
+ // Get current working directory - memoized to avoid repeated process.cwd() calls
58
+ const currentWorkdir = useMemo(() => workdir || process.cwd(), [workdir]);
58
59
 
59
- // Simple history navigation reset function
60
- const resetHistoryNavigation = useCallback(() => {
61
- // This will be handled by InputManager through callbacks
62
- }, []);
60
+ const {
61
+ permissionMode: chatPermissionMode,
62
+ setPermissionMode: setChatPermissionMode,
63
+ } = useChat();
63
64
 
64
65
  // Input manager with all input state and functionality (including images)
65
66
  const {
66
67
  inputText,
67
68
  cursorPosition,
68
- clearInput,
69
69
  // Image management
70
70
  attachedImages,
71
71
  clearImages,
@@ -73,44 +73,53 @@ export const InputBox: React.FC<InputBoxProps> = ({
73
73
  showFileSelector,
74
74
  filteredFiles,
75
75
  fileSearchQuery: searchQuery,
76
- handleFileSelect: handleFileSelectorSelect,
76
+ handleFileSelect,
77
77
  handleCancelFileSelect,
78
78
  // Command selector
79
79
  showCommandSelector,
80
80
  commandSearchQuery,
81
- handleCommandSelect: handleCommandSelectorSelect,
82
- handleCommandInsert: handleCommandSelectorInsert,
81
+ handleCommandSelect,
82
+ handleCommandInsert,
83
83
  handleCancelCommandSelect,
84
84
  // Bash history selector
85
85
  showBashHistorySelector,
86
86
  bashHistorySearchQuery,
87
- handleBashHistorySelect: handleBashHistorySelectorSelect,
88
- handleBashHistoryExecute,
87
+ handleBashHistorySelect,
89
88
  handleCancelBashHistorySelect,
90
89
  // Memory type selector
91
90
  showMemoryTypeSelector,
92
91
  memoryMessage,
93
- handleMemoryTypeSelect: handleMemoryTypeSelectorSelect,
92
+ handleMemoryTypeSelect,
94
93
  handleCancelMemoryTypeSelect,
95
94
  // Bash/MCP Manager
96
95
  showBashManager,
97
96
  showMcpManager,
98
97
  setShowBashManager,
99
98
  setShowMcpManager,
99
+ // Permission mode
100
+ permissionMode,
101
+ setPermissionMode,
100
102
  // Input history
101
103
  setUserInputHistory,
104
+ // Complex handlers combining multiple operations
105
+ handleBashHistoryExecuteAndSend,
102
106
  // Main handler
103
107
  handleInput,
108
+ // Manager ready state
109
+ isManagerReady,
104
110
  } = useInputManager({
105
- onShowBashManager: () => setShowBashManager(true),
106
- onShowMcpManager: () => setShowMcpManager(true),
107
111
  onSendMessage: sendMessage,
108
112
  onHasSlashCommand: hasSlashCommand,
109
113
  onSaveMemory: saveMemory,
110
114
  onAbortMessage: abortMessage,
111
- onResetHistoryNavigation: resetHistoryNavigation,
115
+ onPermissionModeChange: setChatPermissionMode,
112
116
  });
113
117
 
118
+ // Sync permission mode from useChat to InputManager
119
+ useEffect(() => {
120
+ setPermissionMode(chatPermissionMode);
121
+ }, [chatPermissionMode, setPermissionMode]);
122
+
114
123
  // Set user input history when it changes
115
124
  useEffect(() => {
116
125
  setUserInputHistory(userInputHistory);
@@ -128,65 +137,14 @@ export const InputBox: React.FC<InputBoxProps> = ({
128
137
  );
129
138
  });
130
139
 
131
- // Handler functions for keyboard events
132
- const handleFileSelect = useCallback(
133
- (filePath: string) => {
134
- handleFileSelectorSelect(filePath);
135
- },
136
- [handleFileSelectorSelect],
137
- );
140
+ // These methods are already memoized in useInputManager, no need to wrap again
138
141
 
139
- const handleCommandSelect = useCallback(
140
- (command: string) => {
141
- handleCommandSelectorSelect(command);
142
- },
143
- [handleCommandSelectorSelect],
144
- );
145
-
146
- const handleBashHistorySelect = useCallback(
147
- (command: string) => {
148
- handleBashHistorySelectorSelect(command);
149
- },
150
- [handleBashHistorySelectorSelect],
151
- );
152
-
153
- const keyboardHandleBashHistoryExecute = useCallback(
154
- (command: string) => {
155
- const commandToExecute = handleBashHistoryExecute(command);
156
- // Clear input box and execute command, ensure command starts with !
157
- const bashCommand = commandToExecute.startsWith("!")
158
- ? commandToExecute
159
- : `!${commandToExecute}`;
160
- clearInput();
161
- sendMessage(bashCommand);
162
- },
163
- [handleBashHistoryExecute, clearInput, sendMessage],
164
- );
165
-
166
- const handleMemoryTypeSelect = useCallback(
167
- async (type: "project" | "user") => {
168
- const currentMessage = inputText.trim();
169
- if (currentMessage.startsWith("#")) {
170
- await saveMemory(currentMessage, type);
171
- }
172
- // Call the handler function to close the selector
173
- handleMemoryTypeSelectorSelect(type);
174
- // Clear input box
175
- clearInput();
176
- },
177
- [inputText, saveMemory, handleMemoryTypeSelectorSelect, clearInput],
178
- );
142
+ // These methods are already memoized in useInputManager and combine multiple operations
179
143
 
180
144
  const isPlaceholder = !inputText;
181
145
  const placeholderText = INPUT_PLACEHOLDER_TEXT;
182
146
 
183
- // Create adapter function for CommandSelector
184
- const handleCommandInsert = useCallback(
185
- (command: string) => {
186
- handleCommandSelectorInsert(command);
187
- },
188
- [handleCommandSelectorInsert],
189
- );
147
+ // handleCommandSelectorInsert is already memoized in useInputManager, no need to wrap again
190
148
 
191
149
  // Split text into three parts: before cursor, cursor position, after cursor
192
150
  const displayText = isPlaceholder ? placeholderText : inputText;
@@ -198,8 +156,13 @@ export const InputBox: React.FC<InputBoxProps> = ({
198
156
  // Always show cursor, allow user to continue input during loading
199
157
  const shouldShowCursor = true;
200
158
 
159
+ // Only show the Box after InputManager is created on first mount
160
+ if (!isManagerReady) {
161
+ return null;
162
+ }
163
+
201
164
  return (
202
- <Box flexDirection="column" width={"100%"}>
165
+ <Box flexDirection="column">
203
166
  {showFileSelector && (
204
167
  <FileSelector
205
168
  files={filteredFiles}
@@ -224,7 +187,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
224
187
  searchQuery={bashHistorySearchQuery}
225
188
  workdir={currentWorkdir}
226
189
  onSelect={handleBashHistorySelect}
227
- onExecute={keyboardHandleBashHistoryExecute}
190
+ onExecute={handleBashHistoryExecuteAndSend}
228
191
  onCancel={handleCancelBashHistorySelect}
229
192
  />
230
193
  )}
@@ -250,20 +213,28 @@ export const InputBox: React.FC<InputBoxProps> = ({
250
213
  />
251
214
  )}
252
215
  {showBashManager || showMcpManager || (
253
- <Box borderStyle="single" borderColor="gray" paddingX={1}>
254
- <Text color={isPlaceholder ? "gray" : "white"}>
255
- {shouldShowCursor ? (
256
- <>
257
- {beforeCursor}
258
- <Text backgroundColor="white" color="black">
259
- {atCursor}
260
- </Text>
261
- {afterCursor}
262
- </>
263
- ) : (
264
- displayText
265
- )}
266
- </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>
267
238
  </Box>
268
239
  )}
269
240
  </Box>
@@ -0,0 +1,29 @@
1
+ import React, { useMemo } from "react";
2
+ import { Text } from "ink";
3
+ import { marked } from "marked";
4
+ import TerminalRenderer from "marked-terminal";
5
+
6
+ export interface MarkdownProps {
7
+ children: string;
8
+ }
9
+
10
+ // Markdown component using marked-terminal with proper unescape option
11
+ export const Markdown = React.memo(({ children }: MarkdownProps) => {
12
+ const result = useMemo(() => {
13
+ // Configure marked with TerminalRenderer using default options
14
+ marked.setOptions({
15
+ renderer: new TerminalRenderer({
16
+ // Use official unescape option to handle HTML entities
17
+ unescape: true,
18
+ }),
19
+ });
20
+
21
+ const output = marked(children);
22
+ return typeof output === "string" ? output.trim() : "";
23
+ }, [children]);
24
+
25
+ return <Text>{result}</Text>;
26
+ });
27
+
28
+ // Add display name for debugging
29
+ Markdown.displayName = "Markdown";
@@ -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
  }