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.
Files changed (97) hide show
  1. package/dist/agents/codebaseIndexAgent.js +1 -0
  2. package/dist/agents/codebaseReviewAgent.d.ts +61 -0
  3. package/dist/agents/codebaseReviewAgent.js +301 -0
  4. package/dist/agents/promptOptimizeAgent.d.ts +54 -0
  5. package/dist/agents/promptOptimizeAgent.js +268 -0
  6. package/dist/api/anthropic.js +1 -0
  7. package/dist/api/chat.js +1 -0
  8. package/dist/api/embedding.js +1 -0
  9. package/dist/api/gemini.js +2 -1
  10. package/dist/api/responses.js +1 -0
  11. package/dist/api/systemPrompt.d.ts +1 -5
  12. package/dist/api/systemPrompt.js +168 -100
  13. package/dist/app.js +14 -6
  14. package/dist/cli.js +1 -1
  15. package/dist/hooks/useCommandPanel.js +48 -46
  16. package/dist/hooks/useConversation.d.ts +2 -1
  17. package/dist/hooks/useConversation.js +116 -30
  18. package/dist/hooks/useGlobalExit.js +4 -2
  19. package/dist/hooks/useStreamingState.d.ts +9 -0
  20. package/dist/hooks/useStreamingState.js +3 -0
  21. package/dist/i18n/I18nContext.d.ts +14 -0
  22. package/dist/i18n/I18nContext.js +24 -0
  23. package/dist/i18n/index.d.ts +3 -0
  24. package/dist/i18n/index.js +2 -0
  25. package/dist/i18n/lang/en.d.ts +2 -0
  26. package/dist/i18n/lang/en.js +483 -0
  27. package/dist/i18n/lang/es.d.ts +2 -0
  28. package/dist/i18n/lang/es.js +483 -0
  29. package/dist/i18n/lang/ja.d.ts +2 -0
  30. package/dist/i18n/lang/ja.js +483 -0
  31. package/dist/i18n/lang/ko.d.ts +2 -0
  32. package/dist/i18n/lang/ko.js +483 -0
  33. package/dist/i18n/lang/zh-TW.d.ts +2 -0
  34. package/dist/i18n/lang/zh-TW.js +483 -0
  35. package/dist/i18n/lang/zh.d.ts +2 -0
  36. package/dist/i18n/lang/zh.js +483 -0
  37. package/dist/i18n/translations.d.ts +2 -0
  38. package/dist/i18n/translations.js +14 -0
  39. package/dist/i18n/types.d.ts +459 -0
  40. package/dist/i18n/types.js +1 -0
  41. package/dist/mcp/aceCodeSearch.d.ts +17 -48
  42. package/dist/mcp/aceCodeSearch.js +24 -56
  43. package/dist/mcp/bash.js +8 -1
  44. package/dist/mcp/codebaseSearch.d.ts +1 -1
  45. package/dist/mcp/codebaseSearch.js +159 -30
  46. package/dist/mcp/filesystem.d.ts +3 -80
  47. package/dist/mcp/filesystem.js +23 -103
  48. package/dist/mcp/subagent.d.ts +2 -1
  49. package/dist/mcp/subagent.js +54 -5
  50. package/dist/ui/components/ChatInput.js +22 -25
  51. package/dist/ui/components/CommandPanel.d.ts +1 -1
  52. package/dist/ui/components/CommandPanel.js +20 -13
  53. package/dist/ui/components/DiffViewer.d.ts +1 -1
  54. package/dist/ui/components/DiffViewer.js +101 -91
  55. package/dist/ui/components/FileList.js +22 -11
  56. package/dist/ui/components/HelpPanel.js +47 -21
  57. package/dist/ui/components/Menu.js +6 -2
  58. package/dist/ui/components/MessageList.d.ts +6 -0
  59. package/dist/ui/components/MessageList.js +1 -1
  60. package/dist/ui/components/ToolConfirmation.d.ts +4 -1
  61. package/dist/ui/components/ToolConfirmation.js +28 -2
  62. package/dist/ui/components/ToolResultPreview.d.ts +2 -1
  63. package/dist/ui/components/ToolResultPreview.js +41 -25
  64. package/dist/ui/pages/ChatScreen.js +177 -56
  65. package/dist/ui/pages/CodeBaseConfigScreen.js +54 -30
  66. package/dist/ui/pages/ConfigScreen.js +138 -98
  67. package/dist/ui/pages/CustomHeadersScreen.js +75 -69
  68. package/dist/ui/pages/LanguageSettingsScreen.d.ts +7 -0
  69. package/dist/ui/pages/LanguageSettingsScreen.js +89 -0
  70. package/dist/ui/pages/ProxyConfigScreen.js +27 -23
  71. package/dist/ui/pages/SensitiveCommandConfigScreen.js +32 -25
  72. package/dist/ui/pages/SubAgentConfigScreen.js +88 -75
  73. package/dist/ui/pages/SystemPromptConfigScreen.js +31 -26
  74. package/dist/ui/pages/WelcomeScreen.js +40 -26
  75. package/dist/utils/apiConfig.d.ts +2 -0
  76. package/dist/utils/codebaseConfig.d.ts +1 -5
  77. package/dist/utils/codebaseConfig.js +2 -10
  78. package/dist/utils/codebaseSearchEvents.d.ts +16 -0
  79. package/dist/utils/codebaseSearchEvents.js +13 -0
  80. package/dist/utils/commands/agent.js +2 -2
  81. package/dist/utils/commands/init.js +1 -1
  82. package/dist/utils/configManager.js +26 -5
  83. package/dist/utils/contextCompressor.js +1 -1
  84. package/dist/utils/languageConfig.d.ts +21 -0
  85. package/dist/utils/languageConfig.js +61 -0
  86. package/dist/utils/mcpToolsManager.js +0 -9
  87. package/dist/utils/notebookManager.js +11 -4
  88. package/dist/utils/sessionConverter.js +13 -3
  89. package/dist/utils/sessionManager.d.ts +1 -0
  90. package/dist/utils/subAgentConfig.d.ts +10 -5
  91. package/dist/utils/subAgentConfig.js +112 -19
  92. package/dist/utils/subAgentExecutor.d.ts +9 -1
  93. package/dist/utils/subAgentExecutor.js +122 -9
  94. package/dist/utils/toolExecutor.d.ts +2 -1
  95. package/dist/utils/toolExecutor.js +1 -2
  96. package/dist/utils/usageLogger.js +18 -3
  97. 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
- if (isNewFile) {
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 && (React.createElement(Text, { color: "cyan" },
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
- // Generate line-by-line diff
39
- const diffResult = Diff.diffLines(diffOldContent, diffNewContent);
40
- const allChanges = [];
41
- let oldLineNum = startLineNumber;
42
- let newLineNum = startLineNumber;
43
- diffResult.forEach((part) => {
44
- const lines = part.value.replace(/\n$/, '').split('\n');
45
- lines.forEach((line) => {
46
- if (part.added) {
47
- allChanges.push({
48
- type: 'added',
49
- content: line,
50
- oldLineNum: null,
51
- newLineNum: newLineNum++,
52
- });
53
- }
54
- else if (part.removed) {
55
- allChanges.push({
56
- type: 'removed',
57
- content: line,
58
- oldLineNum: oldLineNum++,
59
- newLineNum: null,
60
- });
61
- }
62
- else {
63
- allChanges.push({
64
- type: 'unchanged',
65
- content: line,
66
- oldLineNum: oldLineNum++,
67
- newLineNum: newLineNum++,
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
- // If there are more changes within context distance, extend the hunk
92
- let hasMoreChanges = false;
93
- for (let j = hunkEnd + 1; j < Math.min(allChanges.length, hunkEnd + 1 + contextLines * 2); j++) {
94
- if (allChanges[j]?.type !== 'unchanged') {
95
- hasMoreChanges = true;
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
- if (hasMoreChanges) {
100
- hunkEnd++;
101
- }
102
- else {
103
- break;
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 && (React.createElement(Text, { color: "cyan" },
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
- ? change.newLineNum
141
- : change.oldLineNum;
142
- const lineNumStr = lineNum ? String(lineNum).padStart(4, ' ') : ' ';
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 no depth limit
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 (no depth limit)
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 on mount - only once when visible
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 && files.length === 0) {
217
- loadFiles();
225
+ if (!visible) {
226
+ return;
218
227
  }
219
- }, [visible, loadFiles]);
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" }, "\uD83D\uDD30 Keyboard Shortcuts & Help")),
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" }, "\uD83D\uDCDD Text Editing:"),
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
- " \u2022 ",
18
- pasteKey,
19
- " - Paste images from clipboard")),
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" }, "\uD83D\uDD0D Quick Access:"),
22
- React.createElement(Text, null, " \u2022 @ - Insert files from project"),
23
- React.createElement(Text, null, " \u2022 @@ - Search file content"),
24
- React.createElement(Text, null, " \u2022 / - Show available commands")),
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" }, "\uD83D\uDCCB Navigation:"),
27
- React.createElement(Text, null, " \u2022 \u2191/\u2193 - Navigate command/message history"),
28
- React.createElement(Text, null, " \u2022 Tab/Enter - Select item in pickers"),
29
- React.createElement(Text, null, " \u2022 ESC - Cancel/close pickers or interrupt AI response"),
30
- React.createElement(Text, null, " \u2022 Shift+Tab - Toggle YOLO mode (auto-approve tools)")),
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" }, "\uD83D\uDCA1 Tips:"),
33
- React.createElement(Text, null, " \u2022 Use /help anytime to see this information"),
34
- React.createElement(Text, null, " \u2022 Type / to see all available commands"),
35
- React.createElement(Text, null, " \u2022 Press ESC during AI response to interrupt")),
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" }, "Press ESC to close this help panel"))));
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" }, "Use \u2191\u2193 keys to navigate, press Enter to select:")),
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 ? 'green' : option.color || 'white', bold: true },
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: "gray" }, message.content || ' ')) : (React.createElement(MarkdownRenderer, { content: message.content || ' ' })),
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(item.value);
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 {};