snow-ai 0.3.36 → 0.4.0
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/dist/agents/codebaseIndexAgent.js +1 -0
- package/dist/agents/codebaseReviewAgent.d.ts +61 -0
- package/dist/agents/codebaseReviewAgent.js +301 -0
- package/dist/agents/promptOptimizeAgent.d.ts +54 -0
- package/dist/agents/promptOptimizeAgent.js +268 -0
- package/dist/api/anthropic.js +1 -0
- package/dist/api/chat.js +1 -0
- package/dist/api/embedding.js +1 -0
- package/dist/api/gemini.js +2 -1
- package/dist/api/responses.js +1 -0
- package/dist/api/systemPrompt.d.ts +1 -5
- package/dist/api/systemPrompt.js +168 -100
- package/dist/app.js +14 -6
- package/dist/cli.js +1 -1
- package/dist/hooks/useCommandPanel.js +48 -46
- package/dist/hooks/useConversation.d.ts +2 -1
- package/dist/hooks/useConversation.js +116 -30
- package/dist/hooks/useGlobalExit.js +4 -2
- package/dist/hooks/useStreamingState.d.ts +9 -0
- package/dist/hooks/useStreamingState.js +3 -0
- package/dist/i18n/I18nContext.d.ts +14 -0
- package/dist/i18n/I18nContext.js +24 -0
- package/dist/i18n/index.d.ts +3 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/lang/en.d.ts +2 -0
- package/dist/i18n/lang/en.js +483 -0
- package/dist/i18n/lang/es.d.ts +2 -0
- package/dist/i18n/lang/es.js +483 -0
- package/dist/i18n/lang/ja.d.ts +2 -0
- package/dist/i18n/lang/ja.js +483 -0
- package/dist/i18n/lang/ko.d.ts +2 -0
- package/dist/i18n/lang/ko.js +483 -0
- package/dist/i18n/lang/zh-TW.d.ts +2 -0
- package/dist/i18n/lang/zh-TW.js +483 -0
- package/dist/i18n/lang/zh.d.ts +2 -0
- package/dist/i18n/lang/zh.js +483 -0
- package/dist/i18n/translations.d.ts +2 -0
- package/dist/i18n/translations.js +14 -0
- package/dist/i18n/types.d.ts +459 -0
- package/dist/i18n/types.js +1 -0
- package/dist/mcp/aceCodeSearch.d.ts +17 -48
- package/dist/mcp/aceCodeSearch.js +24 -56
- package/dist/mcp/bash.js +8 -1
- package/dist/mcp/codebaseSearch.d.ts +1 -1
- package/dist/mcp/codebaseSearch.js +159 -30
- package/dist/mcp/filesystem.d.ts +3 -80
- package/dist/mcp/filesystem.js +23 -103
- package/dist/mcp/subagent.d.ts +2 -1
- package/dist/mcp/subagent.js +54 -5
- package/dist/ui/components/ChatInput.js +22 -25
- package/dist/ui/components/CommandPanel.d.ts +1 -1
- package/dist/ui/components/CommandPanel.js +20 -13
- package/dist/ui/components/DiffViewer.d.ts +1 -1
- package/dist/ui/components/DiffViewer.js +101 -91
- package/dist/ui/components/FileList.js +22 -11
- package/dist/ui/components/HelpPanel.js +47 -21
- package/dist/ui/components/Menu.js +6 -2
- package/dist/ui/components/MessageList.d.ts +6 -0
- package/dist/ui/components/MessageList.js +1 -1
- package/dist/ui/components/ToolConfirmation.d.ts +4 -1
- package/dist/ui/components/ToolConfirmation.js +28 -2
- package/dist/ui/components/ToolResultPreview.d.ts +2 -1
- package/dist/ui/components/ToolResultPreview.js +41 -25
- package/dist/ui/pages/ChatScreen.js +177 -56
- package/dist/ui/pages/CodeBaseConfigScreen.js +54 -30
- package/dist/ui/pages/ConfigScreen.js +138 -98
- package/dist/ui/pages/CustomHeadersScreen.js +75 -69
- package/dist/ui/pages/LanguageSettingsScreen.d.ts +7 -0
- package/dist/ui/pages/LanguageSettingsScreen.js +89 -0
- package/dist/ui/pages/ProxyConfigScreen.js +27 -23
- package/dist/ui/pages/SensitiveCommandConfigScreen.js +32 -25
- package/dist/ui/pages/SubAgentConfigScreen.js +88 -75
- package/dist/ui/pages/SystemPromptConfigScreen.js +31 -26
- package/dist/ui/pages/WelcomeScreen.js +40 -26
- package/dist/utils/apiConfig.d.ts +2 -0
- package/dist/utils/codebaseConfig.d.ts +1 -5
- package/dist/utils/codebaseConfig.js +2 -10
- package/dist/utils/codebaseSearchEvents.d.ts +16 -0
- package/dist/utils/codebaseSearchEvents.js +13 -0
- package/dist/utils/commands/agent.js +2 -2
- package/dist/utils/commands/init.js +1 -1
- package/dist/utils/configManager.js +26 -5
- package/dist/utils/contextCompressor.js +1 -1
- package/dist/utils/languageConfig.d.ts +21 -0
- package/dist/utils/languageConfig.js +61 -0
- package/dist/utils/mcpToolsManager.js +0 -9
- package/dist/utils/notebookManager.js +11 -4
- package/dist/utils/sessionConverter.js +13 -3
- package/dist/utils/sessionManager.d.ts +1 -0
- package/dist/utils/subAgentConfig.d.ts +10 -5
- package/dist/utils/subAgentConfig.js +112 -19
- package/dist/utils/subAgentExecutor.d.ts +9 -1
- package/dist/utils/subAgentExecutor.js +122 -9
- package/dist/utils/toolExecutor.d.ts +2 -1
- package/dist/utils/toolExecutor.js +1 -2
- package/dist/utils/usageLogger.js +18 -3
- package/package.json +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import * as Diff from 'diff';
|
|
4
4
|
// Helper function to strip line numbers from content (format: "123→content")
|
|
@@ -23,109 +23,119 @@ export default function DiffViewer({ oldContent = '', newContent, filename, comp
|
|
|
23
23
|
: stripLineNumbers(newContent);
|
|
24
24
|
// If no old content, show as new file creation
|
|
25
25
|
const isNewFile = !diffOldContent || diffOldContent.trim() === '';
|
|
26
|
-
|
|
26
|
+
// Memoize new file rendering to avoid re-splitting lines on every render
|
|
27
|
+
const newFileContent = useMemo(() => {
|
|
28
|
+
if (!isNewFile)
|
|
29
|
+
return null;
|
|
27
30
|
const allLines = diffNewContent.split('\n');
|
|
28
31
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
29
32
|
React.createElement(Box, { marginBottom: 1 },
|
|
30
33
|
React.createElement(Text, { bold: true, color: "green" }, "[New File]"),
|
|
31
|
-
filename &&
|
|
32
|
-
|
|
33
|
-
filename))
|
|
34
|
+
filename && React.createElement(Text, { color: "cyan" },
|
|
35
|
+
" ",
|
|
36
|
+
filename)),
|
|
34
37
|
React.createElement(Box, { flexDirection: "column" }, allLines.map((line, index) => (React.createElement(Text, { key: index, color: "white", backgroundColor: "#006400" },
|
|
35
38
|
"+ ",
|
|
36
39
|
line))))));
|
|
40
|
+
}, [isNewFile, diffNewContent, filename]);
|
|
41
|
+
if (isNewFile) {
|
|
42
|
+
return newFileContent;
|
|
37
43
|
}
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
72
|
-
// Find diff hunks (groups of changes with context)
|
|
73
|
-
const hunks = [];
|
|
74
|
-
const contextLines = 3; // Number of context lines before and after changes
|
|
75
|
-
for (let i = 0; i < allChanges.length; i++) {
|
|
76
|
-
const change = allChanges[i];
|
|
77
|
-
if (change?.type !== 'unchanged') {
|
|
78
|
-
// Found a change, create a hunk
|
|
79
|
-
const hunkStart = Math.max(0, i - contextLines);
|
|
80
|
-
let hunkEnd = i;
|
|
81
|
-
// Extend the hunk to include all consecutive changes
|
|
82
|
-
while (hunkEnd < allChanges.length - 1) {
|
|
83
|
-
const nextChange = allChanges[hunkEnd + 1];
|
|
84
|
-
if (!nextChange)
|
|
85
|
-
break;
|
|
86
|
-
// If next line is a change, extend the hunk
|
|
87
|
-
if (nextChange.type !== 'unchanged') {
|
|
88
|
-
hunkEnd++;
|
|
89
|
-
continue;
|
|
44
|
+
// Memoize expensive diff calculation - only recompute when content changes
|
|
45
|
+
const hunks = useMemo(() => {
|
|
46
|
+
// Generate line-by-line diff
|
|
47
|
+
const diffResult = Diff.diffLines(diffOldContent, diffNewContent);
|
|
48
|
+
const allChanges = [];
|
|
49
|
+
let oldLineNum = startLineNumber;
|
|
50
|
+
let newLineNum = startLineNumber;
|
|
51
|
+
diffResult.forEach(part => {
|
|
52
|
+
const lines = part.value.replace(/\n$/, '').split('\n');
|
|
53
|
+
lines.forEach(line => {
|
|
54
|
+
if (part.added) {
|
|
55
|
+
allChanges.push({
|
|
56
|
+
type: 'added',
|
|
57
|
+
content: line,
|
|
58
|
+
oldLineNum: null,
|
|
59
|
+
newLineNum: newLineNum++,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else if (part.removed) {
|
|
63
|
+
allChanges.push({
|
|
64
|
+
type: 'removed',
|
|
65
|
+
content: line,
|
|
66
|
+
oldLineNum: oldLineNum++,
|
|
67
|
+
newLineNum: null,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
allChanges.push({
|
|
72
|
+
type: 'unchanged',
|
|
73
|
+
content: line,
|
|
74
|
+
oldLineNum: oldLineNum++,
|
|
75
|
+
newLineNum: newLineNum++,
|
|
76
|
+
});
|
|
90
77
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
// Find diff hunks (groups of changes with context)
|
|
81
|
+
const computedHunks = [];
|
|
82
|
+
const contextLines = 3; // Number of context lines before and after changes
|
|
83
|
+
for (let i = 0; i < allChanges.length; i++) {
|
|
84
|
+
const change = allChanges[i];
|
|
85
|
+
if (change?.type !== 'unchanged') {
|
|
86
|
+
// Found a change, create a hunk
|
|
87
|
+
const hunkStart = Math.max(0, i - contextLines);
|
|
88
|
+
let hunkEnd = i;
|
|
89
|
+
// Extend the hunk to include all consecutive changes
|
|
90
|
+
while (hunkEnd < allChanges.length - 1) {
|
|
91
|
+
const nextChange = allChanges[hunkEnd + 1];
|
|
92
|
+
if (!nextChange)
|
|
93
|
+
break;
|
|
94
|
+
// If next line is a change, extend the hunk
|
|
95
|
+
if (nextChange.type !== 'unchanged') {
|
|
96
|
+
hunkEnd++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
// If there are more changes within context distance, extend the hunk
|
|
100
|
+
let hasMoreChanges = false;
|
|
101
|
+
for (let j = hunkEnd + 1; j < Math.min(allChanges.length, hunkEnd + 1 + contextLines * 2); j++) {
|
|
102
|
+
if (allChanges[j]?.type !== 'unchanged') {
|
|
103
|
+
hasMoreChanges = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (hasMoreChanges) {
|
|
108
|
+
hunkEnd++;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
96
111
|
break;
|
|
97
112
|
}
|
|
98
113
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
// Add context lines after the hunk
|
|
115
|
+
hunkEnd = Math.min(allChanges.length - 1, hunkEnd + contextLines);
|
|
116
|
+
// Extract the hunk
|
|
117
|
+
const hunkChanges = allChanges.slice(hunkStart, hunkEnd + 1);
|
|
118
|
+
const firstChange = hunkChanges[0];
|
|
119
|
+
const lastChange = hunkChanges[hunkChanges.length - 1];
|
|
120
|
+
if (firstChange && lastChange) {
|
|
121
|
+
computedHunks.push({
|
|
122
|
+
startLine: firstChange.oldLineNum || firstChange.newLineNum || 1,
|
|
123
|
+
endLine: lastChange.oldLineNum || lastChange.newLineNum || 1,
|
|
124
|
+
changes: hunkChanges,
|
|
125
|
+
});
|
|
104
126
|
}
|
|
127
|
+
// Skip to the end of this hunk
|
|
128
|
+
i = hunkEnd;
|
|
105
129
|
}
|
|
106
|
-
// Add context lines after the hunk
|
|
107
|
-
hunkEnd = Math.min(allChanges.length - 1, hunkEnd + contextLines);
|
|
108
|
-
// Extract the hunk
|
|
109
|
-
const hunkChanges = allChanges.slice(hunkStart, hunkEnd + 1);
|
|
110
|
-
const firstChange = hunkChanges[0];
|
|
111
|
-
const lastChange = hunkChanges[hunkChanges.length - 1];
|
|
112
|
-
if (firstChange && lastChange) {
|
|
113
|
-
hunks.push({
|
|
114
|
-
startLine: firstChange.oldLineNum || firstChange.newLineNum || 1,
|
|
115
|
-
endLine: lastChange.oldLineNum || lastChange.newLineNum || 1,
|
|
116
|
-
changes: hunkChanges,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
// Skip to the end of this hunk
|
|
120
|
-
i = hunkEnd;
|
|
121
130
|
}
|
|
122
|
-
|
|
131
|
+
return computedHunks;
|
|
132
|
+
}, [diffOldContent, diffNewContent, startLineNumber]);
|
|
123
133
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
124
134
|
React.createElement(Box, { marginBottom: 1 },
|
|
125
135
|
React.createElement(Text, { bold: true, color: "yellow" }, "[File Modified]"),
|
|
126
|
-
filename &&
|
|
127
|
-
|
|
128
|
-
filename))
|
|
136
|
+
filename && React.createElement(Text, { color: "cyan" },
|
|
137
|
+
" ",
|
|
138
|
+
filename)),
|
|
129
139
|
React.createElement(Box, { flexDirection: "column" },
|
|
130
140
|
hunks.map((hunk, hunkIndex) => (React.createElement(Box, { key: hunkIndex, flexDirection: "column", marginBottom: 1 },
|
|
131
141
|
React.createElement(Text, { color: "cyan", dimColor: true },
|
|
@@ -136,10 +146,10 @@ export default function DiffViewer({ oldContent = '', newContent, filename, comp
|
|
|
136
146
|
" @@"),
|
|
137
147
|
hunk.changes.map((change, changeIndex) => {
|
|
138
148
|
// Calculate line number to display
|
|
139
|
-
const lineNum = change.type === 'added'
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
const lineNum = change.type === 'added' ? change.newLineNum : change.oldLineNum;
|
|
150
|
+
const lineNumStr = lineNum
|
|
151
|
+
? String(lineNum).padStart(4, ' ')
|
|
152
|
+
: ' ';
|
|
143
153
|
if (change.type === 'added') {
|
|
144
154
|
return (React.createElement(Text, { key: changeIndex, color: "white", backgroundColor: "#006400" },
|
|
145
155
|
lineNumStr,
|
|
@@ -155,7 +165,7 @@ export default function DiffViewer({ oldContent = '', newContent, filename, comp
|
|
|
155
165
|
// Unchanged lines (context)
|
|
156
166
|
return (React.createElement(Text, { key: changeIndex, dimColor: true },
|
|
157
167
|
lineNumStr,
|
|
158
|
-
"
|
|
168
|
+
" ",
|
|
159
169
|
change.content));
|
|
160
170
|
})))),
|
|
161
171
|
hunks.length > 1 && (React.createElement(Box, { marginTop: 1 },
|
|
@@ -15,9 +15,15 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
|
|
|
15
15
|
? Math.min(maxItems, MAX_DISPLAY_ITEMS)
|
|
16
16
|
: MAX_DISPLAY_ITEMS;
|
|
17
17
|
}, [maxItems]);
|
|
18
|
-
// Get files from directory - optimized for performance with
|
|
18
|
+
// Get files from directory - optimized for performance with depth limit
|
|
19
19
|
const loadFiles = useCallback(async () => {
|
|
20
|
+
const MAX_DEPTH = 5; // Limit recursion depth to prevent performance issues
|
|
21
|
+
const MAX_FILES = 1000; // Reduced from 2000 for better performance
|
|
20
22
|
const getFilesRecursively = async (dir, depth = 0) => {
|
|
23
|
+
// Stop recursion if depth limit reached
|
|
24
|
+
if (depth > MAX_DEPTH) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
21
27
|
try {
|
|
22
28
|
const entries = await fs.promises.readdir(dir, {
|
|
23
29
|
withFileTypes: true,
|
|
@@ -48,6 +54,10 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
|
|
|
48
54
|
'.env',
|
|
49
55
|
];
|
|
50
56
|
for (const entry of entries) {
|
|
57
|
+
// Early exit if we've collected enough files
|
|
58
|
+
if (result.length >= MAX_FILES) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
51
61
|
// Skip hidden files and ignore patterns
|
|
52
62
|
if (entry.name.startsWith('.') ||
|
|
53
63
|
ignorePatterns.includes(entry.name)) {
|
|
@@ -70,20 +80,18 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
|
|
|
70
80
|
!path.isAbsolute(relativePath)) {
|
|
71
81
|
relativePath = './' + relativePath;
|
|
72
82
|
}
|
|
83
|
+
// Normalize to forward slashes for cross-platform consistency
|
|
84
|
+
relativePath = relativePath.replace(/\\/g, '/');
|
|
73
85
|
result.push({
|
|
74
86
|
name: entry.name,
|
|
75
87
|
path: relativePath,
|
|
76
88
|
isDirectory: entry.isDirectory(),
|
|
77
89
|
});
|
|
78
|
-
// Recursively get files from subdirectories
|
|
79
|
-
if (entry.isDirectory()) {
|
|
90
|
+
// Recursively get files from subdirectories with depth limit
|
|
91
|
+
if (entry.isDirectory() && depth < MAX_DEPTH) {
|
|
80
92
|
const subFiles = await getFilesRecursively(fullPath, depth + 1);
|
|
81
93
|
result = result.concat(subFiles);
|
|
82
94
|
}
|
|
83
|
-
// Limit total files for performance (increased from 500 to 2000)
|
|
84
|
-
if (result.length > 2000) {
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
95
|
}
|
|
88
96
|
return result;
|
|
89
97
|
}
|
|
@@ -211,12 +219,15 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
|
|
|
211
219
|
}
|
|
212
220
|
return results;
|
|
213
221
|
}, [files, rootPath, terminalWidth]);
|
|
214
|
-
// Load files
|
|
222
|
+
// Load files when component becomes visible
|
|
223
|
+
// This ensures the file list is always fresh without complex file watching
|
|
215
224
|
useEffect(() => {
|
|
216
|
-
if (visible
|
|
217
|
-
|
|
225
|
+
if (!visible) {
|
|
226
|
+
return;
|
|
218
227
|
}
|
|
219
|
-
|
|
228
|
+
// Always reload when becoming visible to ensure fresh data
|
|
229
|
+
loadFiles();
|
|
230
|
+
}, [visible, rootPath, loadFiles]);
|
|
220
231
|
// State for filtered files (needed for async content search)
|
|
221
232
|
const [allFilteredFiles, setAllFilteredFiles] = useState([]);
|
|
222
233
|
// Filter files based on query and search mode with debounce
|
|
@@ -1,38 +1,64 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { useI18n } from '../../i18n/index.js';
|
|
3
4
|
// Get platform-specific paste key
|
|
4
5
|
const getPasteKey = () => {
|
|
5
6
|
return process.platform === 'darwin' ? 'Ctrl+V' : 'Alt+V';
|
|
6
7
|
};
|
|
7
8
|
export default function HelpPanel() {
|
|
8
9
|
const pasteKey = getPasteKey();
|
|
10
|
+
const { t } = useI18n();
|
|
9
11
|
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 },
|
|
10
12
|
React.createElement(Box, { marginBottom: 1 },
|
|
11
|
-
React.createElement(Text, { bold: true, color: "cyan" },
|
|
13
|
+
React.createElement(Text, { bold: true, color: "cyan" }, t.helpPanel.title)),
|
|
12
14
|
React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
13
|
-
React.createElement(Text, { bold: true, color: "yellow" },
|
|
14
|
-
React.createElement(Text, null, " \u2022 Ctrl+L - Delete from cursor to start"),
|
|
15
|
-
React.createElement(Text, null, " \u2022 Ctrl+R - Delete from cursor to end"),
|
|
15
|
+
React.createElement(Text, { bold: true, color: "yellow" }, t.helpPanel.textEditingTitle),
|
|
16
16
|
React.createElement(Text, null,
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
" \u2022 ",
|
|
18
|
+
t.helpPanel.deleteToStart),
|
|
19
|
+
React.createElement(Text, null,
|
|
20
|
+
" \u2022 ",
|
|
21
|
+
t.helpPanel.deleteToEnd),
|
|
22
|
+
React.createElement(Text, null,
|
|
23
|
+
' ',
|
|
24
|
+
"\u2022 ",
|
|
25
|
+
t.helpPanel.pasteImages.replace('{pasteKey}', pasteKey))),
|
|
20
26
|
React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
21
|
-
React.createElement(Text, { bold: true, color: "green" },
|
|
22
|
-
React.createElement(Text, null,
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
React.createElement(Text, { bold: true, color: "green" }, t.helpPanel.quickAccessTitle),
|
|
28
|
+
React.createElement(Text, null,
|
|
29
|
+
" \u2022 ",
|
|
30
|
+
t.helpPanel.insertFiles),
|
|
31
|
+
React.createElement(Text, null,
|
|
32
|
+
" \u2022 ",
|
|
33
|
+
t.helpPanel.searchContent),
|
|
34
|
+
React.createElement(Text, null,
|
|
35
|
+
" \u2022 ",
|
|
36
|
+
t.helpPanel.showCommands)),
|
|
25
37
|
React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
26
|
-
React.createElement(Text, { bold: true, color: "blue" },
|
|
27
|
-
React.createElement(Text, null,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
React.createElement(Text, null,
|
|
38
|
+
React.createElement(Text, { bold: true, color: "blue" }, t.helpPanel.navigationTitle),
|
|
39
|
+
React.createElement(Text, null,
|
|
40
|
+
" \u2022 ",
|
|
41
|
+
t.helpPanel.navigateHistory),
|
|
42
|
+
React.createElement(Text, null,
|
|
43
|
+
" \u2022 ",
|
|
44
|
+
t.helpPanel.selectItem),
|
|
45
|
+
React.createElement(Text, null,
|
|
46
|
+
" \u2022 ",
|
|
47
|
+
t.helpPanel.cancelClose),
|
|
48
|
+
React.createElement(Text, null,
|
|
49
|
+
" \u2022 ",
|
|
50
|
+
t.helpPanel.toggleYolo)),
|
|
31
51
|
React.createElement(Box, { flexDirection: "column" },
|
|
32
|
-
React.createElement(Text, { bold: true, color: "magenta" },
|
|
33
|
-
React.createElement(Text, null,
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
React.createElement(Text, { bold: true, color: "magenta" }, t.helpPanel.tipsTitle),
|
|
53
|
+
React.createElement(Text, null,
|
|
54
|
+
" \u2022 ",
|
|
55
|
+
t.helpPanel.tipUseHelp),
|
|
56
|
+
React.createElement(Text, null,
|
|
57
|
+
" \u2022 ",
|
|
58
|
+
t.helpPanel.tipShowCommands),
|
|
59
|
+
React.createElement(Text, null,
|
|
60
|
+
" \u2022 ",
|
|
61
|
+
t.helpPanel.tipInterrupt)),
|
|
36
62
|
React.createElement(Box, { marginTop: 1 },
|
|
37
|
-
React.createElement(Text, { dimColor: true, color: "gray" },
|
|
63
|
+
React.createElement(Text, { dimColor: true, color: "gray" }, t.helpPanel.closeHint))));
|
|
38
64
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import React, { useState, useCallback } from 'react';
|
|
2
2
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
3
3
|
import { resetTerminal } from '../../utils/terminal.js';
|
|
4
|
+
import { useI18n } from '../../i18n/index.js';
|
|
4
5
|
function Menu({ options, onSelect, onSelectionChange, maxHeight }) {
|
|
5
6
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
6
7
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
7
8
|
const { stdout } = useStdout();
|
|
9
|
+
const { t } = useI18n();
|
|
8
10
|
// Calculate available height
|
|
9
11
|
const terminalHeight = stdout?.rows || 24;
|
|
10
12
|
const headerHeight = 8; // Space for header, borders, etc.
|
|
@@ -54,7 +56,7 @@ function Menu({ options, onSelect, onSelectionChange, maxHeight }) {
|
|
|
54
56
|
const moreBelowCount = options.length - (scrollOffset + visibleItemCount);
|
|
55
57
|
return (React.createElement(Box, { flexDirection: "column", width: '100%', padding: 1 },
|
|
56
58
|
React.createElement(Box, { marginBottom: 1 },
|
|
57
|
-
React.createElement(Text, { color: "cyan" },
|
|
59
|
+
React.createElement(Text, { color: "cyan" }, t.menu.navigate)),
|
|
58
60
|
hasMoreAbove && (React.createElement(Box, null,
|
|
59
61
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
60
62
|
"\u2191 +",
|
|
@@ -63,7 +65,9 @@ function Menu({ options, onSelect, onSelectionChange, maxHeight }) {
|
|
|
63
65
|
visibleOptions.map((option, index) => {
|
|
64
66
|
const actualIndex = scrollOffset + index;
|
|
65
67
|
return (React.createElement(Box, { key: option.value },
|
|
66
|
-
React.createElement(Text, { color: actualIndex === selectedIndex
|
|
68
|
+
React.createElement(Text, { color: actualIndex === selectedIndex
|
|
69
|
+
? 'green'
|
|
70
|
+
: option.color || 'white', bold: true },
|
|
67
71
|
actualIndex === selectedIndex ? '❯ ' : ' ',
|
|
68
72
|
option.label)));
|
|
69
73
|
}),
|
|
@@ -39,6 +39,12 @@ export interface Message {
|
|
|
39
39
|
isComplete?: boolean;
|
|
40
40
|
};
|
|
41
41
|
subAgentInternal?: boolean;
|
|
42
|
+
subAgentUsage?: {
|
|
43
|
+
inputTokens: number;
|
|
44
|
+
outputTokens: number;
|
|
45
|
+
cacheCreationInputTokens?: number;
|
|
46
|
+
cacheReadInputTokens?: number;
|
|
47
|
+
};
|
|
42
48
|
parallelGroup?: string;
|
|
43
49
|
}
|
|
44
50
|
interface Props {
|
|
@@ -33,7 +33,7 @@ const MessageList = memo(({ messages, animationFrame, maxMessages = 6 }) => {
|
|
|
33
33
|
message.subAgent?.isComplete ? ' ✓' : ' ...'),
|
|
34
34
|
React.createElement(Box, { marginLeft: 2 },
|
|
35
35
|
React.createElement(Text, { color: "gray" }, message.content || ' ')))) : (React.createElement(React.Fragment, null,
|
|
36
|
-
message.role === 'user' ? (React.createElement(Text, { color: "
|
|
36
|
+
message.role === 'user' ? (React.createElement(Text, { color: "white", backgroundColor: "#4a4a4a" }, message.content || ' ')) : (React.createElement(MarkdownRenderer, { content: message.content || ' ' })),
|
|
37
37
|
(message.files || message.images) && (React.createElement(Box, { flexDirection: "column" },
|
|
38
38
|
message.files && message.files.length > 0 && (React.createElement(React.Fragment, null, message.files.map((file, fileIndex) => (React.createElement(Text, { key: fileIndex, color: "gray", dimColor: true }, file.isImage
|
|
39
39
|
? `└─ [image #{fileIndex + 1}] ${file.path}`
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export type ConfirmationResult = 'approve' | 'approve_always' | 'reject'
|
|
2
|
+
export type ConfirmationResult = 'approve' | 'approve_always' | 'reject' | {
|
|
3
|
+
type: 'reject_with_reply';
|
|
4
|
+
reason: string;
|
|
5
|
+
};
|
|
3
6
|
export interface ToolCall {
|
|
4
7
|
id: string;
|
|
5
8
|
type: 'function';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useMemo } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
3
4
|
import SelectInput from 'ink-select-input';
|
|
4
5
|
import { isSensitiveCommand } from '../../utils/sensitiveCommandManager.js';
|
|
5
6
|
// Helper function to format argument values with truncation
|
|
@@ -41,6 +42,8 @@ function formatArgumentsAsTree(args, toolName) {
|
|
|
41
42
|
}
|
|
42
43
|
export default function ToolConfirmation({ toolName, toolArguments, allTools, onConfirm, }) {
|
|
43
44
|
const [hasSelected, setHasSelected] = useState(false);
|
|
45
|
+
const [showRejectInput, setShowRejectInput] = useState(false);
|
|
46
|
+
const [rejectReason, setRejectReason] = useState('');
|
|
44
47
|
// Check if this is a sensitive command (for terminal-execute)
|
|
45
48
|
const sensitiveCommandCheck = useMemo(() => {
|
|
46
49
|
if (toolName !== 'terminal-execute' || !toolArguments) {
|
|
@@ -105,6 +108,10 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
|
|
|
105
108
|
value: 'approve_always',
|
|
106
109
|
});
|
|
107
110
|
}
|
|
111
|
+
baseItems.push({
|
|
112
|
+
label: 'Reject with reply',
|
|
113
|
+
value: 'reject_with_reply',
|
|
114
|
+
});
|
|
108
115
|
baseItems.push({
|
|
109
116
|
label: 'Reject (end session)',
|
|
110
117
|
value: 'reject',
|
|
@@ -113,8 +120,19 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
|
|
|
113
120
|
}, [sensitiveCommandCheck.isSensitive]);
|
|
114
121
|
const handleSelect = (item) => {
|
|
115
122
|
if (!hasSelected) {
|
|
123
|
+
if (item.value === 'reject_with_reply') {
|
|
124
|
+
setShowRejectInput(true);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
setHasSelected(true);
|
|
128
|
+
onConfirm(item.value);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const handleRejectReasonSubmit = () => {
|
|
133
|
+
if (!hasSelected && rejectReason.trim()) {
|
|
116
134
|
setHasSelected(true);
|
|
117
|
-
onConfirm(
|
|
135
|
+
onConfirm({ type: 'reject_with_reply', reason: rejectReason.trim() });
|
|
118
136
|
}
|
|
119
137
|
};
|
|
120
138
|
return (React.createElement(Box, { flexDirection: "column", marginX: 1, marginY: 1, borderStyle: 'round', borderColor: 'yellow', paddingX: 1 },
|
|
@@ -170,7 +188,15 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
|
|
|
170
188
|
React.createElement(Text, { color: "white" }, arg.value))))))))))),
|
|
171
189
|
React.createElement(Box, { marginBottom: 1 },
|
|
172
190
|
React.createElement(Text, { dimColor: true }, "Select action:")),
|
|
173
|
-
!hasSelected && React.createElement(SelectInput, { items: items, onSelect: handleSelect }),
|
|
191
|
+
!hasSelected && !showRejectInput && (React.createElement(SelectInput, { items: items, onSelect: handleSelect })),
|
|
192
|
+
showRejectInput && !hasSelected && (React.createElement(Box, { flexDirection: "column" },
|
|
193
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
194
|
+
React.createElement(Text, { color: "yellow" }, "Enter rejection reason:")),
|
|
195
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
196
|
+
React.createElement(Text, { color: "cyan" }, "> "),
|
|
197
|
+
React.createElement(TextInput, { value: rejectReason, onChange: setRejectReason, onSubmit: handleRejectReasonSubmit })),
|
|
198
|
+
React.createElement(Box, null,
|
|
199
|
+
React.createElement(Text, { dimColor: true }, "Press Enter to submit")))),
|
|
174
200
|
hasSelected && (React.createElement(Box, null,
|
|
175
201
|
React.createElement(Text, { color: "green" }, "Confirmed")))));
|
|
176
202
|
}
|
|
@@ -3,10 +3,11 @@ interface ToolResultPreviewProps {
|
|
|
3
3
|
toolName: string;
|
|
4
4
|
result: string;
|
|
5
5
|
maxLines?: number;
|
|
6
|
+
isSubAgentInternal?: boolean;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Display a compact preview of tool execution results
|
|
9
10
|
* Shows a tree-like structure with limited content
|
|
10
11
|
*/
|
|
11
|
-
export default function ToolResultPreview({ toolName, result, maxLines, }: ToolResultPreviewProps): React.JSX.Element | null;
|
|
12
|
+
export default function ToolResultPreview({ toolName, result, maxLines, isSubAgentInternal, }: ToolResultPreviewProps): React.JSX.Element | null;
|
|
12
13
|
export {};
|