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.
- package/README.md +3 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/components/App.d.ts +1 -0
- package/dist/components/App.d.ts.map +1 -1
- package/dist/components/App.js +4 -4
- package/dist/components/BashHistorySelector.d.ts.map +1 -1
- package/dist/components/BashHistorySelector.js +17 -3
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +6 -24
- package/dist/components/CommandSelector.js +4 -4
- package/dist/components/Confirmation.d.ts +11 -0
- package/dist/components/Confirmation.d.ts.map +1 -0
- package/dist/components/Confirmation.js +148 -0
- package/dist/components/DiffDisplay.d.ts +8 -0
- package/dist/components/DiffDisplay.d.ts.map +1 -0
- package/dist/components/DiffDisplay.js +168 -0
- package/dist/components/FileSelector.d.ts +2 -4
- package/dist/components/FileSelector.d.ts.map +1 -1
- package/dist/components/FileSelector.js +2 -2
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +30 -50
- package/dist/components/Markdown.d.ts +6 -0
- package/dist/components/Markdown.d.ts.map +1 -0
- package/dist/components/Markdown.js +22 -0
- package/dist/components/MemoryDisplay.js +1 -1
- package/dist/components/MessageItem.d.ts +8 -0
- package/dist/components/MessageItem.d.ts.map +1 -0
- package/dist/components/MessageItem.js +15 -0
- package/dist/components/MessageList.d.ts +1 -1
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +33 -33
- package/dist/components/ReasoningDisplay.d.ts +8 -0
- package/dist/components/ReasoningDisplay.d.ts.map +1 -0
- package/dist/components/ReasoningDisplay.js +10 -0
- package/dist/components/SubagentBlock.d.ts +0 -1
- package/dist/components/SubagentBlock.d.ts.map +1 -1
- package/dist/components/SubagentBlock.js +29 -30
- package/dist/components/ToolResultDisplay.d.ts.map +1 -1
- package/dist/components/ToolResultDisplay.js +6 -5
- package/dist/contexts/useChat.d.ts +14 -2
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +128 -17
- package/dist/hooks/useInputManager.d.ts +6 -1
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +32 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -5
- package/dist/managers/InputManager.d.ts +11 -1
- package/dist/managers/InputManager.d.ts.map +1 -1
- package/dist/managers/InputManager.js +77 -26
- package/dist/print-cli.d.ts +2 -0
- package/dist/print-cli.d.ts.map +1 -1
- package/dist/print-cli.js +121 -23
- package/dist/utils/toolParameterTransforms.d.ts +23 -0
- package/dist/utils/toolParameterTransforms.d.ts.map +1 -0
- package/dist/utils/toolParameterTransforms.js +77 -0
- package/dist/utils/usageSummary.d.ts +6 -0
- package/dist/utils/usageSummary.d.ts.map +1 -1
- package/dist/utils/usageSummary.js +72 -0
- package/package.json +13 -8
- package/src/cli.tsx +3 -1
- package/src/components/App.tsx +7 -3
- package/src/components/BashHistorySelector.tsx +26 -3
- package/src/components/ChatInterface.tsx +38 -54
- package/src/components/CommandSelector.tsx +5 -5
- package/src/components/Confirmation.tsx +253 -0
- package/src/components/DiffDisplay.tsx +300 -0
- package/src/components/FileSelector.tsx +4 -6
- package/src/components/InputBox.tsx +58 -87
- package/src/components/Markdown.tsx +29 -0
- package/src/components/MemoryDisplay.tsx +1 -1
- package/src/components/MessageItem.tsx +96 -0
- package/src/components/MessageList.tsx +140 -202
- package/src/components/ReasoningDisplay.tsx +33 -0
- package/src/components/SubagentBlock.tsx +56 -84
- package/src/components/ToolResultDisplay.tsx +9 -5
- package/src/contexts/useChat.tsx +194 -21
- package/src/hooks/useInputManager.ts +40 -3
- package/src/index.ts +45 -5
- package/src/managers/InputManager.ts +101 -27
- package/src/print-cli.ts +143 -21
- package/src/utils/toolParameterTransforms.ts +104 -0
- package/src/utils/usageSummary.ts +109 -0
- package/dist/components/DiffViewer.d.ts +0 -9
- package/dist/components/DiffViewer.d.ts.map +0 -1
- package/dist/components/DiffViewer.js +0 -221
- package/dist/utils/fileSearch.d.ts +0 -20
- package/dist/utils/fileSearch.d.ts.map +0 -1
- package/dist/utils/fileSearch.js +0 -102
- package/src/components/DiffViewer.tsx +0 -321
- 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
|
|
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, {
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
76
|
+
handleFileSelect,
|
|
77
77
|
handleCancelFileSelect,
|
|
78
78
|
// Command selector
|
|
79
79
|
showCommandSelector,
|
|
80
80
|
commandSearchQuery,
|
|
81
|
-
handleCommandSelect
|
|
82
|
-
handleCommandInsert
|
|
81
|
+
handleCommandSelect,
|
|
82
|
+
handleCommandInsert,
|
|
83
83
|
handleCancelCommandSelect,
|
|
84
84
|
// Bash history selector
|
|
85
85
|
showBashHistorySelector,
|
|
86
86
|
bashHistorySearchQuery,
|
|
87
|
-
handleBashHistorySelect
|
|
88
|
-
handleBashHistoryExecute,
|
|
87
|
+
handleBashHistorySelect,
|
|
89
88
|
handleCancelBashHistorySelect,
|
|
90
89
|
// Memory type selector
|
|
91
90
|
showMemoryTypeSelector,
|
|
92
91
|
memoryMessage,
|
|
93
|
-
handleMemoryTypeSelect
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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"
|
|
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={
|
|
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
|
|
254
|
-
<
|
|
255
|
-
{
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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 || "
|
|
28
|
+
return `Memory saved to ${storagePath || "AGENTS.md"}`;
|
|
29
29
|
} else {
|
|
30
30
|
return `Memory saved to ${storagePath || "AGENTS.md"}`;
|
|
31
31
|
}
|