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,262 @@
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import { Alert } from '@inkjs/ui';
5
+ import { getAllSensitiveCommands, toggleSensitiveCommand, addSensitiveCommand, removeSensitiveCommand, resetToDefaults, } from '../../utils/sensitiveCommandManager.js';
6
+ // Focus event handling
7
+ const focusEventTokenRegex = /(?:\x1b)?\[[0-9;]*[IO]/g;
8
+ const isFocusEventInput = (value) => {
9
+ if (!value)
10
+ return false;
11
+ if (value === '\x1b[I' ||
12
+ value === '\x1b[O' ||
13
+ value === '[I' ||
14
+ value === '[O') {
15
+ return true;
16
+ }
17
+ const trimmed = value.trim();
18
+ if (!trimmed)
19
+ return false;
20
+ const tokens = trimmed.match(focusEventTokenRegex);
21
+ if (!tokens)
22
+ return false;
23
+ const normalized = trimmed.replace(/\s+/g, '');
24
+ const tokensCombined = tokens.join('');
25
+ return tokensCombined === normalized;
26
+ };
27
+ const stripFocusArtifacts = (value) => {
28
+ if (!value)
29
+ return '';
30
+ return value
31
+ .replace(/\x1b\[[0-9;]*[IO]/g, '')
32
+ .replace(/\[[0-9;]*[IO]/g, '')
33
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
34
+ };
35
+ export default function SensitiveCommandConfigScreen({ onBack, inlineMode = false, }) {
36
+ const [commands, setCommands] = useState([]);
37
+ const [selectedIndex, setSelectedIndex] = useState(0);
38
+ const [viewMode, setViewMode] = useState('list');
39
+ const [showSuccess, setShowSuccess] = useState(false);
40
+ const [successMessage, setSuccessMessage] = useState('');
41
+ // Confirmation states
42
+ const [confirmDelete, setConfirmDelete] = useState(false);
43
+ const [confirmReset, setConfirmReset] = useState(false);
44
+ // Add custom command fields
45
+ const [customPattern, setCustomPattern] = useState('');
46
+ const [customDescription, setCustomDescription] = useState('');
47
+ const [addField, setAddField] = useState('pattern');
48
+ // Load commands
49
+ const loadCommands = useCallback(() => {
50
+ const allCommands = getAllSensitiveCommands();
51
+ setCommands(allCommands);
52
+ }, []);
53
+ useEffect(() => {
54
+ loadCommands();
55
+ }, [loadCommands]);
56
+ // Handle list view input
57
+ const handleListInput = useCallback((input, key) => {
58
+ if (key.escape) {
59
+ // Cancel any pending confirmations
60
+ if (confirmDelete || confirmReset) {
61
+ setConfirmDelete(false);
62
+ setConfirmReset(false);
63
+ return;
64
+ }
65
+ onBack();
66
+ return;
67
+ }
68
+ if (key.upArrow) {
69
+ setSelectedIndex(prev => Math.max(0, prev - 1));
70
+ // Clear confirmations when navigating
71
+ setConfirmDelete(false);
72
+ setConfirmReset(false);
73
+ }
74
+ else if (key.downArrow) {
75
+ setSelectedIndex(prev => Math.min(commands.length - 1, prev + 1));
76
+ // Clear confirmations when navigating
77
+ setConfirmDelete(false);
78
+ setConfirmReset(false);
79
+ }
80
+ else if (input === ' ') {
81
+ // Toggle command
82
+ const cmd = commands[selectedIndex];
83
+ if (cmd) {
84
+ toggleSensitiveCommand(cmd.id);
85
+ loadCommands();
86
+ setSuccessMessage(cmd.enabled
87
+ ? `Disabled: ${cmd.pattern}`
88
+ : `Enabled: ${cmd.pattern}`);
89
+ setShowSuccess(true);
90
+ setTimeout(() => setShowSuccess(false), 2000);
91
+ }
92
+ }
93
+ else if (input === 'a' || input === 'A') {
94
+ // Add custom command
95
+ setViewMode('add');
96
+ setCustomPattern('');
97
+ setCustomDescription('');
98
+ setAddField('pattern');
99
+ }
100
+ else if (input === 'd' || input === 'D') {
101
+ // Delete custom command - requires confirmation
102
+ const cmd = commands[selectedIndex];
103
+ if (cmd && !cmd.isPreset) {
104
+ if (!confirmDelete) {
105
+ // First press - ask for confirmation
106
+ setConfirmDelete(true);
107
+ setConfirmReset(false);
108
+ }
109
+ else {
110
+ // Second press - execute delete
111
+ removeSensitiveCommand(cmd.id);
112
+ loadCommands();
113
+ setSelectedIndex(prev => Math.min(prev, commands.length - 2));
114
+ setSuccessMessage(`Deleted: ${cmd.pattern}`);
115
+ setShowSuccess(true);
116
+ setTimeout(() => setShowSuccess(false), 2000);
117
+ setConfirmDelete(false);
118
+ }
119
+ }
120
+ }
121
+ else if (input === 'r' || input === 'R') {
122
+ // Reset to defaults - requires confirmation
123
+ if (!confirmReset) {
124
+ // First press - ask for confirmation
125
+ setConfirmReset(true);
126
+ setConfirmDelete(false);
127
+ }
128
+ else {
129
+ // Second press - execute reset
130
+ resetToDefaults();
131
+ loadCommands();
132
+ setSelectedIndex(0);
133
+ setSuccessMessage('Reset to default commands');
134
+ setShowSuccess(true);
135
+ setTimeout(() => setShowSuccess(false), 2000);
136
+ setConfirmReset(false);
137
+ }
138
+ }
139
+ }, [
140
+ commands,
141
+ selectedIndex,
142
+ onBack,
143
+ loadCommands,
144
+ confirmDelete,
145
+ confirmReset,
146
+ ]);
147
+ // Handle add view input
148
+ const handleAddInput = useCallback((_input, key) => {
149
+ if (key.escape) {
150
+ setViewMode('list');
151
+ return;
152
+ }
153
+ if (key.tab) {
154
+ setAddField(prev => (prev === 'pattern' ? 'description' : 'pattern'));
155
+ }
156
+ }, []);
157
+ // Use input hook
158
+ useInput((input, key) => {
159
+ if (viewMode === 'list') {
160
+ handleListInput(input, key);
161
+ }
162
+ else {
163
+ handleAddInput(input, key);
164
+ }
165
+ }, { isActive: true });
166
+ // Handle pattern input change
167
+ const handlePatternChange = useCallback((value) => {
168
+ if (!isFocusEventInput(value)) {
169
+ setCustomPattern(stripFocusArtifacts(value));
170
+ }
171
+ }, []);
172
+ // Handle description input change
173
+ const handleDescriptionChange = useCallback((value) => {
174
+ if (!isFocusEventInput(value)) {
175
+ setCustomDescription(stripFocusArtifacts(value));
176
+ }
177
+ }, []);
178
+ // Handle add submit
179
+ const handleAddSubmit = useCallback(() => {
180
+ if (addField === 'pattern') {
181
+ setAddField('description');
182
+ }
183
+ else {
184
+ // Submit
185
+ if (customPattern.trim() && customDescription.trim()) {
186
+ try {
187
+ addSensitiveCommand(customPattern.trim(), customDescription.trim());
188
+ loadCommands();
189
+ setViewMode('list');
190
+ setSuccessMessage(`Added: ${customPattern}`);
191
+ setShowSuccess(true);
192
+ setTimeout(() => setShowSuccess(false), 2000);
193
+ }
194
+ catch (error) {
195
+ // Handle error
196
+ }
197
+ }
198
+ }
199
+ }, [addField, customPattern, customDescription, loadCommands]);
200
+ if (viewMode === 'add') {
201
+ return (React.createElement(Box, { flexDirection: "column", paddingX: inlineMode ? 0 : 2, paddingY: 1 },
202
+ React.createElement(Text, { bold: true, color: "cyan" }, "Add Custom Sensitive Command"),
203
+ React.createElement(Box, { marginTop: 1 }),
204
+ React.createElement(Text, { dimColor: true }, "Pattern (supports wildcards, e.g., \"rm*\"):"),
205
+ React.createElement(Box, null,
206
+ React.createElement(Text, { color: addField === 'pattern' ? 'cyan' : 'gray' }, "\u276F "),
207
+ React.createElement(TextInput, { value: customPattern, onChange: handlePatternChange, onSubmit: handleAddSubmit, focus: addField === 'pattern' })),
208
+ React.createElement(Box, { marginTop: 1 }),
209
+ React.createElement(Text, { dimColor: true }, "Description:"),
210
+ React.createElement(Box, null,
211
+ React.createElement(Text, { color: addField === 'description' ? 'cyan' : 'gray' }, "\u276F "),
212
+ React.createElement(TextInput, { value: customDescription, onChange: handleDescriptionChange, onSubmit: handleAddSubmit, focus: addField === 'description' })),
213
+ React.createElement(Box, { marginTop: 1 }),
214
+ React.createElement(Text, { dimColor: true }, "Tab: Switch \u2022 Enter: Submit \u2022 Esc: Cancel")));
215
+ }
216
+ // Calculate visible range for scrolling
217
+ const viewportHeight = 13;
218
+ const startIndex = Math.max(0, selectedIndex - Math.floor(viewportHeight / 2));
219
+ const endIndex = Math.min(commands.length, startIndex + viewportHeight);
220
+ const adjustedStart = Math.max(0, endIndex - viewportHeight);
221
+ const selectedCmd = commands[selectedIndex];
222
+ return (React.createElement(Box, { flexDirection: "column", paddingX: inlineMode ? 0 : 2, paddingY: 1 },
223
+ React.createElement(Text, { bold: true, color: "cyan" }, "Sensitive Command Protection"),
224
+ React.createElement(Text, { dimColor: true }, "Configure commands that require confirmation even in YOLO/Always-Approved mode"),
225
+ showSuccess && (React.createElement(Box, { marginTop: 1 },
226
+ React.createElement(Alert, { variant: "success" }, successMessage))),
227
+ React.createElement(Box, { marginTop: 1 }),
228
+ commands.length === 0 ? (React.createElement(Text, { dimColor: true }, "No commands configured")) : (commands.map((cmd, index) => {
229
+ // Only render items in the visible viewport
230
+ if (index < adjustedStart || index >= endIndex) {
231
+ return null;
232
+ }
233
+ return (React.createElement(Text, { key: cmd.id, color: selectedIndex === index
234
+ ? 'cyan'
235
+ : cmd.enabled
236
+ ? 'white'
237
+ : 'gray', bold: selectedIndex === index, dimColor: !cmd.enabled },
238
+ selectedIndex === index ? '❯ ' : ' ',
239
+ "[",
240
+ cmd.enabled ? '✓' : ' ',
241
+ "]",
242
+ ' ',
243
+ cmd.pattern,
244
+ !cmd.isPreset && React.createElement(Text, { color: "yellow" }, " (custom)")));
245
+ })),
246
+ React.createElement(Box, { marginTop: 1 }),
247
+ selectedCmd && !confirmDelete && !confirmReset && (React.createElement(Text, { dimColor: true },
248
+ selectedCmd.description,
249
+ " (",
250
+ selectedCmd.enabled ? 'Enabled' : 'Disabled',
251
+ ")",
252
+ !selectedCmd.isPreset && ' [Custom]')),
253
+ confirmDelete && selectedCmd && (React.createElement(Text, { bold: true, color: "yellow" },
254
+ "\u26A0\uFE0F Press D again to confirm deletion of \"",
255
+ selectedCmd.pattern,
256
+ "\"")),
257
+ confirmReset && (React.createElement(Text, { bold: true, color: "yellow" }, "\u26A0\uFE0F Press R again to confirm reset to default commands")),
258
+ React.createElement(Box, { marginTop: 1 }),
259
+ React.createElement(Text, { dimColor: true }, confirmDelete || confirmReset
260
+ ? 'Press the same key again to confirm • Esc: Cancel'
261
+ : '↑↓: Navigate • Space: Toggle • A: Add • D: Delete • R: Reset • Esc: Back')));
262
+ }
@@ -353,7 +353,7 @@ export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = fals
353
353
  return (React.createElement(Box, { key: tool },
354
354
  React.createElement(Text, { color: isCurrentTool ? 'cyan' : 'white', bold: isCurrentTool },
355
355
  isCurrentTool ? '❯ ' : ' ',
356
- selectedTools.has(tool) ? '' : '',
356
+ selectedTools.has(tool) ? '[✓]' : '[ ]',
357
357
  " ",
358
358
  tool)));
359
359
  })))));
@@ -9,6 +9,7 @@ import ConfigScreen from './ConfigScreen.js';
9
9
  import ProxyConfigScreen from './ProxyConfigScreen.js';
10
10
  import SubAgentConfigScreen from './SubAgentConfigScreen.js';
11
11
  import SubAgentListScreen from './SubAgentListScreen.js';
12
+ import SensitiveCommandConfigScreen from './SensitiveCommandConfigScreen.js';
12
13
  export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
13
14
  const [infoText, setInfoText] = useState('Start a new chat conversation');
14
15
  const [inlineView, setInlineView] = useState('menu');
@@ -54,6 +55,11 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
54
55
  value: 'subagent',
55
56
  infoText: 'Configure sub-agents with custom tool permissions',
56
57
  },
58
+ {
59
+ label: 'Sensitive Commands',
60
+ value: 'sensitive-commands',
61
+ infoText: 'Configure commands that require confirmation even in YOLO mode',
62
+ },
57
63
  {
58
64
  label: 'Exit',
59
65
  value: 'exit',
@@ -75,6 +81,9 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
75
81
  else if (value === 'subagent') {
76
82
  setInlineView('subagent-list');
77
83
  }
84
+ else if (value === 'sensitive-commands') {
85
+ setInlineView('sensitive-commands');
86
+ }
78
87
  else {
79
88
  // Pass through to parent for other actions (chat, exit, etc.)
80
89
  onMenuSelect?.(value);
@@ -107,11 +116,11 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
107
116
  const handler = setTimeout(() => {
108
117
  stdout.write(ansiEscapes.clearTerminal);
109
118
  setRemountKey(prev => prev + 1); // Force re-render
110
- }, 0); // Wait for resize to stabilize
119
+ }, 200); // Add debounce delay to avoid rapid re-renders
111
120
  return () => {
112
121
  clearTimeout(handler);
113
122
  };
114
- }, [terminalWidth, stdout]);
123
+ }, [terminalWidth]); // Remove stdout from dependencies to avoid loops
115
124
  return (React.createElement(Box, { flexDirection: "column", width: terminalWidth },
116
125
  React.createElement(Static, { key: remountKey, items: [
117
126
  React.createElement(Box, { key: "welcome-header", flexDirection: "row", paddingLeft: 2, paddingTop: 1, paddingBottom: 0, width: terminalWidth },
@@ -137,5 +146,7 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
137
146
  inlineView === 'subagent-add' && (React.createElement(Box, { paddingX: 1 },
138
147
  React.createElement(SubAgentConfigScreen, { onBack: () => setInlineView('subagent-list'), onSave: handleSubAgentSave, inlineMode: true }))),
139
148
  inlineView === 'subagent-edit' && (React.createElement(Box, { paddingX: 1 },
140
- React.createElement(SubAgentConfigScreen, { onBack: () => setInlineView('subagent-list'), onSave: handleSubAgentSave, agentId: editingAgentId, inlineMode: true })))));
149
+ React.createElement(SubAgentConfigScreen, { onBack: () => setInlineView('subagent-list'), onSave: handleSubAgentSave, agentId: editingAgentId, inlineMode: true }))),
150
+ inlineView === 'sensitive-commands' && (React.createElement(Box, { paddingX: 1 },
151
+ React.createElement(SensitiveCommandConfigScreen, { onBack: handleBackToMenu, inlineMode: true })))));
141
152
  }
@@ -6,6 +6,14 @@ export interface ThinkingConfig {
6
6
  type: 'enabled';
7
7
  budget_tokens: number;
8
8
  }
9
+ export interface GeminiThinkingConfig {
10
+ enabled: boolean;
11
+ budget: number;
12
+ }
13
+ export interface ResponsesReasoningConfig {
14
+ enabled: boolean;
15
+ effort: 'low' | 'medium' | 'high';
16
+ }
9
17
  export interface ApiConfig {
10
18
  baseUrl: string;
11
19
  apiKey: string;
@@ -17,6 +25,8 @@ export interface ApiConfig {
17
25
  compactModel?: CompactModelConfig;
18
26
  anthropicBeta?: boolean;
19
27
  thinking?: ThinkingConfig;
28
+ geminiThinking?: GeminiThinkingConfig;
29
+ responsesReasoning?: ResponsesReasoningConfig;
20
30
  }
21
31
  export interface MCPServer {
22
32
  url?: string;
@@ -0,0 +1,53 @@
1
+ export interface SensitiveCommand {
2
+ id: string;
3
+ pattern: string;
4
+ description: string;
5
+ enabled: boolean;
6
+ isPreset: boolean;
7
+ }
8
+ export interface SensitiveCommandsConfig {
9
+ commands: SensitiveCommand[];
10
+ }
11
+ /**
12
+ * 预设的常见敏感指令
13
+ */
14
+ export declare const PRESET_SENSITIVE_COMMANDS: SensitiveCommand[];
15
+ /**
16
+ * Load sensitive commands configuration
17
+ */
18
+ export declare function loadSensitiveCommands(): SensitiveCommandsConfig;
19
+ /**
20
+ * Save sensitive commands configuration
21
+ */
22
+ export declare function saveSensitiveCommands(config: SensitiveCommandsConfig): void;
23
+ /**
24
+ * Add a custom sensitive command
25
+ */
26
+ export declare function addSensitiveCommand(pattern: string, description: string): void;
27
+ /**
28
+ * Remove a sensitive command
29
+ */
30
+ export declare function removeSensitiveCommand(id: string): void;
31
+ /**
32
+ * Update a sensitive command
33
+ */
34
+ export declare function updateSensitiveCommand(id: string, updates: Partial<Omit<SensitiveCommand, 'id' | 'isPreset'>>): void;
35
+ /**
36
+ * Toggle a sensitive command enabled state
37
+ */
38
+ export declare function toggleSensitiveCommand(id: string): void;
39
+ /**
40
+ * Check if a command matches any enabled sensitive pattern
41
+ */
42
+ export declare function isSensitiveCommand(command: string): {
43
+ isSensitive: boolean;
44
+ matchedCommand?: SensitiveCommand;
45
+ };
46
+ /**
47
+ * Get all sensitive commands
48
+ */
49
+ export declare function getAllSensitiveCommands(): SensitiveCommand[];
50
+ /**
51
+ * Reset to default preset commands
52
+ */
53
+ export declare function resetToDefaults(): void;