snow-ai 0.3.22 → 0.3.24

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 (35) hide show
  1. package/dist/api/gemini.d.ts +5 -1
  2. package/dist/api/gemini.js +30 -5
  3. package/dist/api/responses.js +18 -3
  4. package/dist/hooks/useConversation.d.ts +0 -5
  5. package/dist/hooks/useConversation.js +109 -56
  6. package/dist/hooks/useFilePicker.d.ts +1 -1
  7. package/dist/hooks/useFilePicker.js +13 -7
  8. package/dist/hooks/useHistoryNavigation.js +14 -7
  9. package/dist/hooks/useInputBuffer.d.ts +1 -1
  10. package/dist/hooks/useInputBuffer.js +22 -6
  11. package/dist/hooks/useStreamingState.js +2 -2
  12. package/dist/hooks/useVSCodeState.js +23 -6
  13. package/dist/mcp/filesystem.js +1 -1
  14. package/dist/ui/components/ChatInput.js +17 -11
  15. package/dist/ui/components/MessageList.d.ts +0 -1
  16. package/dist/ui/components/MessageList.js +1 -2
  17. package/dist/ui/components/SessionListPanel.js +12 -8
  18. package/dist/ui/components/SessionListScreen.js +2 -1
  19. package/dist/ui/components/ToolConfirmation.d.ts +1 -1
  20. package/dist/ui/components/ToolConfirmation.js +63 -22
  21. package/dist/ui/components/ToolResultPreview.js +33 -6
  22. package/dist/ui/pages/ChatScreen.js +21 -17
  23. package/dist/ui/pages/ConfigScreen.js +167 -16
  24. package/dist/ui/pages/HeadlessModeScreen.js +0 -1
  25. package/dist/ui/pages/ProxyConfigScreen.d.ts +1 -1
  26. package/dist/ui/pages/ProxyConfigScreen.js +6 -6
  27. package/dist/ui/pages/SensitiveCommandConfigScreen.d.ts +7 -0
  28. package/dist/ui/pages/SensitiveCommandConfigScreen.js +262 -0
  29. package/dist/ui/pages/SubAgentConfigScreen.js +1 -1
  30. package/dist/ui/pages/WelcomeScreen.js +14 -3
  31. package/dist/utils/apiConfig.d.ts +10 -0
  32. package/dist/utils/sensitiveCommandManager.d.ts +53 -0
  33. package/dist/utils/sensitiveCommandManager.js +308 -0
  34. package/dist/utils/sessionConverter.js +16 -11
  35. package/package.json +4 -2
@@ -0,0 +1,308 @@
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
4
+ const CONFIG_DIR = join(homedir(), '.snow');
5
+ const SENSITIVE_COMMANDS_FILE = join(CONFIG_DIR, 'sensitive-commands.json');
6
+ /**
7
+ * 预设的常见敏感指令
8
+ */
9
+ export const PRESET_SENSITIVE_COMMANDS = [
10
+ {
11
+ id: 'rm',
12
+ pattern: 'rm*',
13
+ description: 'Delete files or directories (rm, rm -rf, etc.)',
14
+ enabled: true,
15
+ isPreset: true,
16
+ },
17
+ {
18
+ id: 'rmdir',
19
+ pattern: 'rmdir*',
20
+ description: 'Remove directories',
21
+ enabled: true,
22
+ isPreset: true,
23
+ },
24
+ {
25
+ id: 'mv-to-trash',
26
+ pattern: 'mv * /tmp*',
27
+ description: 'Move files to trash/tmp (potential data loss)',
28
+ enabled: true,
29
+ isPreset: true,
30
+ },
31
+ {
32
+ id: 'chmod',
33
+ pattern: 'chmod*',
34
+ description: 'Change file permissions',
35
+ enabled: true,
36
+ isPreset: true,
37
+ },
38
+ {
39
+ id: 'chown',
40
+ pattern: 'chown*',
41
+ description: 'Change file ownership',
42
+ enabled: true,
43
+ isPreset: true,
44
+ },
45
+ {
46
+ id: 'dd',
47
+ pattern: 'dd*',
48
+ description: 'Low-level data copy (disk operations)',
49
+ enabled: true,
50
+ isPreset: true,
51
+ },
52
+ {
53
+ id: 'mkfs',
54
+ pattern: 'mkfs*',
55
+ description: 'Format filesystem',
56
+ enabled: true,
57
+ isPreset: true,
58
+ },
59
+ {
60
+ id: 'fdisk',
61
+ pattern: 'fdisk*',
62
+ description: 'Disk partition manipulation',
63
+ enabled: true,
64
+ isPreset: true,
65
+ },
66
+ {
67
+ id: 'killall',
68
+ pattern: 'killall*',
69
+ description: 'Kill all processes by name',
70
+ enabled: true,
71
+ isPreset: true,
72
+ },
73
+ {
74
+ id: 'pkill',
75
+ pattern: 'pkill*',
76
+ description: 'Kill processes by pattern',
77
+ enabled: true,
78
+ isPreset: true,
79
+ },
80
+ {
81
+ id: 'reboot',
82
+ pattern: 'reboot*',
83
+ description: 'Reboot the system',
84
+ enabled: true,
85
+ isPreset: true,
86
+ },
87
+ {
88
+ id: 'shutdown',
89
+ pattern: 'shutdown*',
90
+ description: 'Shutdown the system',
91
+ enabled: true,
92
+ isPreset: true,
93
+ },
94
+ {
95
+ id: 'sudo',
96
+ pattern: 'sudo*',
97
+ description: 'Execute commands with superuser privileges',
98
+ enabled: true,
99
+ isPreset: true,
100
+ },
101
+ {
102
+ id: 'su',
103
+ pattern: 'su*',
104
+ description: 'Switch user',
105
+ enabled: true,
106
+ isPreset: true,
107
+ },
108
+ {
109
+ id: 'curl-post',
110
+ pattern: 'curl*-X POST*',
111
+ description: 'HTTP POST requests (potential data transmission)',
112
+ enabled: false,
113
+ isPreset: true,
114
+ },
115
+ {
116
+ id: 'wget',
117
+ pattern: 'wget*',
118
+ description: 'Download files from internet',
119
+ enabled: false,
120
+ isPreset: true,
121
+ },
122
+ {
123
+ id: 'git-push',
124
+ pattern: 'git push*',
125
+ description: 'Push code to remote repository',
126
+ enabled: false,
127
+ isPreset: true,
128
+ },
129
+ {
130
+ id: 'git-force-push',
131
+ pattern: 'git push*--force*',
132
+ description: 'Force push to remote repository (destructive)',
133
+ enabled: true,
134
+ isPreset: true,
135
+ },
136
+ {
137
+ id: 'npm-publish',
138
+ pattern: 'npm publish*',
139
+ description: 'Publish package to npm registry',
140
+ enabled: true,
141
+ isPreset: true,
142
+ },
143
+ {
144
+ id: 'docker-rm',
145
+ pattern: 'docker rm*',
146
+ description: 'Remove Docker containers',
147
+ enabled: false,
148
+ isPreset: true,
149
+ },
150
+ {
151
+ id: 'docker-rmi',
152
+ pattern: 'docker rmi*',
153
+ description: 'Remove Docker images',
154
+ enabled: false,
155
+ isPreset: true,
156
+ },
157
+ ];
158
+ /**
159
+ * Ensure config directory exists
160
+ */
161
+ function ensureConfigDirectory() {
162
+ if (!existsSync(CONFIG_DIR)) {
163
+ mkdirSync(CONFIG_DIR, { recursive: true });
164
+ }
165
+ }
166
+ /**
167
+ * Load sensitive commands configuration
168
+ */
169
+ export function loadSensitiveCommands() {
170
+ ensureConfigDirectory();
171
+ if (!existsSync(SENSITIVE_COMMANDS_FILE)) {
172
+ // 首次加载,使用预设配置
173
+ const defaultConfig = {
174
+ commands: [...PRESET_SENSITIVE_COMMANDS],
175
+ };
176
+ saveSensitiveCommands(defaultConfig);
177
+ return defaultConfig;
178
+ }
179
+ try {
180
+ const configData = readFileSync(SENSITIVE_COMMANDS_FILE, 'utf8');
181
+ const config = JSON.parse(configData);
182
+ // 合并预设命令(处理新增的预设命令)
183
+ const existingIds = new Set(config.commands.map(cmd => cmd.id));
184
+ const newPresets = PRESET_SENSITIVE_COMMANDS.filter(preset => !existingIds.has(preset.id));
185
+ if (newPresets.length > 0) {
186
+ config.commands = [...config.commands, ...newPresets];
187
+ saveSensitiveCommands(config);
188
+ }
189
+ return config;
190
+ }
191
+ catch (error) {
192
+ console.error('Failed to load sensitive commands config:', error);
193
+ return { commands: [...PRESET_SENSITIVE_COMMANDS] };
194
+ }
195
+ }
196
+ /**
197
+ * Save sensitive commands configuration
198
+ */
199
+ export function saveSensitiveCommands(config) {
200
+ ensureConfigDirectory();
201
+ try {
202
+ const configData = JSON.stringify(config, null, 2);
203
+ writeFileSync(SENSITIVE_COMMANDS_FILE, configData, 'utf8');
204
+ }
205
+ catch (error) {
206
+ throw new Error(`Failed to save sensitive commands config: ${error}`);
207
+ }
208
+ }
209
+ /**
210
+ * Add a custom sensitive command
211
+ */
212
+ export function addSensitiveCommand(pattern, description) {
213
+ const config = loadSensitiveCommands();
214
+ // 生成唯一ID
215
+ const id = `custom-${Date.now()}-${Math.random()
216
+ .toString(36)
217
+ .substring(2, 9)}`;
218
+ config.commands.push({
219
+ id,
220
+ pattern,
221
+ description,
222
+ enabled: true,
223
+ isPreset: false,
224
+ });
225
+ saveSensitiveCommands(config);
226
+ }
227
+ /**
228
+ * Remove a sensitive command
229
+ */
230
+ export function removeSensitiveCommand(id) {
231
+ const config = loadSensitiveCommands();
232
+ config.commands = config.commands.filter(cmd => cmd.id !== id);
233
+ saveSensitiveCommands(config);
234
+ }
235
+ /**
236
+ * Update a sensitive command
237
+ */
238
+ export function updateSensitiveCommand(id, updates) {
239
+ const config = loadSensitiveCommands();
240
+ const commandIndex = config.commands.findIndex(cmd => cmd.id === id);
241
+ if (commandIndex === -1) {
242
+ throw new Error(`Sensitive command with id "${id}" not found`);
243
+ }
244
+ const existingCommand = config.commands[commandIndex];
245
+ config.commands[commandIndex] = {
246
+ ...existingCommand,
247
+ ...updates,
248
+ id: existingCommand.id,
249
+ isPreset: existingCommand.isPreset,
250
+ };
251
+ saveSensitiveCommands(config);
252
+ }
253
+ /**
254
+ * Toggle a sensitive command enabled state
255
+ */
256
+ export function toggleSensitiveCommand(id) {
257
+ const config = loadSensitiveCommands();
258
+ const command = config.commands.find(cmd => cmd.id === id);
259
+ if (!command) {
260
+ throw new Error(`Sensitive command with id "${id}" not found`);
261
+ }
262
+ command.enabled = !command.enabled;
263
+ saveSensitiveCommands(config);
264
+ }
265
+ /**
266
+ * 将通配符模式转换为正则表达式
267
+ * 支持 * 通配符
268
+ */
269
+ function patternToRegex(pattern) {
270
+ // 转义特殊字符,除了 * 和空格
271
+ const escaped = pattern
272
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
273
+ .replace(/\*/g, '.*');
274
+ // 使用 ^ 和 $ 确保完整匹配,但允许参数
275
+ return new RegExp(`^${escaped}`, 'i');
276
+ }
277
+ /**
278
+ * Check if a command matches any enabled sensitive pattern
279
+ */
280
+ export function isSensitiveCommand(command) {
281
+ const config = loadSensitiveCommands();
282
+ const enabledCommands = config.commands.filter(cmd => cmd.enabled);
283
+ // 清理命令,移除多余的空格
284
+ const cleanCommand = command.trim().replace(/\s+/g, ' ');
285
+ for (const cmd of enabledCommands) {
286
+ const regex = patternToRegex(cmd.pattern);
287
+ if (regex.test(cleanCommand)) {
288
+ return { isSensitive: true, matchedCommand: cmd };
289
+ }
290
+ }
291
+ return { isSensitive: false };
292
+ }
293
+ /**
294
+ * Get all sensitive commands
295
+ */
296
+ export function getAllSensitiveCommands() {
297
+ const config = loadSensitiveCommands();
298
+ return config.commands;
299
+ }
300
+ /**
301
+ * Reset to default preset commands
302
+ */
303
+ export function resetToDefaults() {
304
+ const config = {
305
+ commands: [...PRESET_SENSITIVE_COMMANDS],
306
+ };
307
+ saveSensitiveCommands(config);
308
+ }
@@ -1,4 +1,5 @@
1
1
  import { formatToolCallMessage } from './messageFormatter.js';
2
+ import { isToolNeedTwoStepDisplay } from './toolDisplayConfig.js';
2
3
  /**
3
4
  * Convert API format session messages to UI format messages
4
5
  * Process messages in order to maintain correct sequence
@@ -114,17 +115,21 @@ export function convertSessionMessagesToUI(sessionMessages) {
114
115
  catch (e) {
115
116
  toolArgs = {};
116
117
  }
117
- // Add tool call message
118
- uiMessages.push({
119
- role: 'assistant',
120
- content: `⚡ ${toolDisplay.toolName}`,
121
- streaming: false,
122
- toolCall: {
123
- name: toolCall.function.name,
124
- arguments: toolArgs,
125
- },
126
- toolDisplay,
127
- });
118
+ // Only add "in progress" message for tools that need two-step display
119
+ const needTwoSteps = isToolNeedTwoStepDisplay(toolCall.function.name);
120
+ if (needTwoSteps) {
121
+ // Add tool call message (in progress)
122
+ uiMessages.push({
123
+ role: 'assistant',
124
+ content: `⚡ ${toolDisplay.toolName}`,
125
+ streaming: false,
126
+ toolCall: {
127
+ name: toolCall.function.name,
128
+ arguments: toolArgs,
129
+ },
130
+ toolDisplay,
131
+ });
132
+ }
128
133
  processedToolCalls.add(toolCall.id);
129
134
  }
130
135
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -89,7 +89,9 @@
89
89
  "extends": "xo-react",
90
90
  "prettier": true,
91
91
  "rules": {
92
- "react/prop-types": "off"
92
+ "react/prop-types": "off",
93
+ "react-hooks/rules-of-hooks": "error",
94
+ "react-hooks/exhaustive-deps": "warn"
93
95
  }
94
96
  },
95
97
  "prettier": "@vdemedes/prettier-config"