snow-ai 0.2.23 → 0.2.25

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 (43) hide show
  1. package/dist/agents/compactAgent.d.ts +55 -0
  2. package/dist/agents/compactAgent.js +301 -0
  3. package/dist/api/chat.d.ts +0 -8
  4. package/dist/api/chat.js +1 -144
  5. package/dist/api/responses.d.ts +0 -11
  6. package/dist/api/responses.js +1 -189
  7. package/dist/api/systemPrompt.d.ts +1 -1
  8. package/dist/api/systemPrompt.js +80 -206
  9. package/dist/app.d.ts +2 -1
  10. package/dist/app.js +11 -13
  11. package/dist/cli.js +23 -3
  12. package/dist/hooks/useConversation.js +51 -7
  13. package/dist/hooks/useGlobalNavigation.d.ts +1 -1
  14. package/dist/hooks/useKeyboardInput.js +14 -8
  15. package/dist/mcp/filesystem.d.ts +49 -6
  16. package/dist/mcp/filesystem.js +243 -86
  17. package/dist/mcp/websearch.d.ts +118 -0
  18. package/dist/mcp/websearch.js +451 -0
  19. package/dist/ui/components/ToolResultPreview.js +60 -1
  20. package/dist/ui/pages/ChatScreen.d.ts +4 -2
  21. package/dist/ui/pages/ChatScreen.js +62 -14
  22. package/dist/ui/pages/{ApiConfigScreen.d.ts → ConfigScreen.d.ts} +1 -1
  23. package/dist/ui/pages/ConfigScreen.js +549 -0
  24. package/dist/ui/pages/{ModelConfigScreen.d.ts → ProxyConfigScreen.d.ts} +1 -1
  25. package/dist/ui/pages/ProxyConfigScreen.js +143 -0
  26. package/dist/ui/pages/WelcomeScreen.js +15 -15
  27. package/dist/utils/apiConfig.d.ts +8 -2
  28. package/dist/utils/apiConfig.js +21 -0
  29. package/dist/utils/commandExecutor.d.ts +1 -1
  30. package/dist/utils/contextCompressor.js +363 -49
  31. package/dist/utils/mcpToolsManager.d.ts +1 -1
  32. package/dist/utils/mcpToolsManager.js +106 -6
  33. package/dist/utils/resourceMonitor.d.ts +65 -0
  34. package/dist/utils/resourceMonitor.js +175 -0
  35. package/dist/utils/retryUtils.js +6 -0
  36. package/dist/utils/sessionManager.d.ts +1 -0
  37. package/dist/utils/sessionManager.js +10 -0
  38. package/dist/utils/textBuffer.js +7 -2
  39. package/dist/utils/toolExecutor.d.ts +2 -2
  40. package/dist/utils/toolExecutor.js +4 -4
  41. package/package.json +5 -1
  42. package/dist/ui/pages/ApiConfigScreen.js +0 -161
  43. package/dist/ui/pages/ModelConfigScreen.js +0 -467
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { exec, execSync } from 'child_process';
7
7
  import { promisify } from 'util';
8
8
  import App from './app.js';
9
9
  import { vscodeConnection } from './utils/vscodeConnection.js';
10
+ import { resourceMonitor } from './utils/resourceMonitor.js';
10
11
  const execAsync = promisify(exec);
11
12
  // Check for updates asynchronously
12
13
  async function checkForUpdates(currentVersion) {
@@ -34,6 +35,7 @@ const cli = meow(`
34
35
  --help Show help
35
36
  --version Show version
36
37
  --update Update to latest version
38
+ -c Skip welcome screen and resume last conversation
37
39
  `, {
38
40
  importMeta: import.meta,
39
41
  flags: {
@@ -41,6 +43,10 @@ const cli = meow(`
41
43
  type: 'boolean',
42
44
  default: false,
43
45
  },
46
+ c: {
47
+ type: 'boolean',
48
+ default: false,
49
+ },
44
50
  },
45
51
  });
46
52
  // Handle update flag
@@ -56,8 +62,20 @@ if (cli.flags.update) {
56
62
  process.exit(1);
57
63
  }
58
64
  }
65
+ // Start resource monitoring in development/debug mode
66
+ if (process.env['NODE_ENV'] === 'development' || process.env['DEBUG']) {
67
+ resourceMonitor.startMonitoring(30000); // Monitor every 30 seconds
68
+ // Check for leaks every 5 minutes
69
+ setInterval(() => {
70
+ const { hasLeak, reasons } = resourceMonitor.checkForLeaks();
71
+ if (hasLeak) {
72
+ console.error('⚠️ Potential memory leak detected:');
73
+ reasons.forEach(reason => console.error(` - ${reason}`));
74
+ }
75
+ }, 5 * 60 * 1000);
76
+ }
59
77
  // Startup component that shows loading spinner during update check
60
- const Startup = ({ version }) => {
78
+ const Startup = ({ version, skipWelcome, }) => {
61
79
  const [appReady, setAppReady] = React.useState(false);
62
80
  React.useEffect(() => {
63
81
  let mounted = true;
@@ -87,13 +105,15 @@ const Startup = ({ version }) => {
87
105
  React.createElement(Spinner, { type: "dots" })),
88
106
  React.createElement(Text, null, " Checking for updates..."))));
89
107
  }
90
- return React.createElement(App, { version: version });
108
+ return React.createElement(App, { version: version, skipWelcome: skipWelcome });
91
109
  };
92
110
  // Disable bracketed paste mode on startup
93
111
  process.stdout.write('\x1b[?2004l');
94
112
  // Re-enable on exit to avoid polluting parent shell
95
113
  const cleanup = () => {
96
114
  process.stdout.write('\x1b[?2004l');
115
+ // Stop resource monitoring
116
+ resourceMonitor.stopMonitoring();
97
117
  // Disconnect VSCode connection before exit
98
118
  vscodeConnection.stop();
99
119
  };
@@ -106,7 +126,7 @@ process.on('SIGTERM', () => {
106
126
  cleanup();
107
127
  process.exit(0);
108
128
  });
109
- render(React.createElement(Startup, { version: cli.pkg.version }), {
129
+ render(React.createElement(Startup, { version: cli.pkg.version, skipWelcome: cli.flags.c }), {
110
130
  exitOnCtrlC: false,
111
131
  patchConsole: true,
112
132
  });
@@ -10,6 +10,7 @@ import { getOpenAiConfig } from '../utils/apiConfig.js';
10
10
  import { sessionManager } from '../utils/sessionManager.js';
11
11
  import { formatTodoContext } from '../utils/todoPreprocessor.js';
12
12
  import { formatToolCallMessage } from '../utils/messageFormatter.js';
13
+ import { resourceMonitor } from '../utils/resourceMonitor.js';
13
14
  /**
14
15
  * Handle conversation with streaming and tool calls
15
16
  */
@@ -64,13 +65,28 @@ export async function handleConversationWithTools(options) {
64
65
  }).catch(error => {
65
66
  console.error('Failed to save user message:', error);
66
67
  });
67
- // Initialize token encoder
68
+ // Initialize token encoder with proper cleanup tracking
68
69
  let encoder;
70
+ let encoderFreed = false;
71
+ const freeEncoder = () => {
72
+ if (!encoderFreed && encoder) {
73
+ try {
74
+ encoder.free();
75
+ encoderFreed = true;
76
+ resourceMonitor.trackEncoderFreed();
77
+ }
78
+ catch (e) {
79
+ console.error('Failed to free encoder:', e);
80
+ }
81
+ }
82
+ };
69
83
  try {
70
84
  encoder = encoding_for_model('gpt-4');
85
+ resourceMonitor.trackEncoderCreated();
71
86
  }
72
87
  catch (e) {
73
88
  encoder = encoding_for_model('gpt-3.5-turbo');
89
+ resourceMonitor.trackEncoderCreated();
74
90
  }
75
91
  setStreamTokenCount(0);
76
92
  const config = getOpenAiConfig();
@@ -83,8 +99,10 @@ export async function handleConversationWithTools(options) {
83
99
  const sessionApprovedTools = new Set();
84
100
  try {
85
101
  while (true) {
86
- if (controller.signal.aborted)
102
+ if (controller.signal.aborted) {
103
+ freeEncoder();
87
104
  break;
105
+ }
88
106
  let streamedContent = '';
89
107
  let receivedToolCalls;
90
108
  // Stream AI response - choose API based on config
@@ -200,6 +218,7 @@ export async function handleConversationWithTools(options) {
200
218
  // If aborted during streaming, exit the loop
201
219
  // (discontinued message already added by ChatScreen ESC handler)
202
220
  if (controller.signal.aborted) {
221
+ freeEncoder();
203
222
  break;
204
223
  }
205
224
  // If there are tool calls, we need to handle them specially
@@ -222,6 +241,17 @@ export async function handleConversationWithTools(options) {
222
241
  saveMessage(assistantMessage).catch(error => {
223
242
  console.error('Failed to save assistant message:', error);
224
243
  });
244
+ // If there's text content before tool calls, display it first
245
+ if (streamedContent && streamedContent.trim()) {
246
+ setMessages(prev => [
247
+ ...prev,
248
+ {
249
+ role: 'assistant',
250
+ content: streamedContent.trim(),
251
+ streaming: false,
252
+ },
253
+ ]);
254
+ }
225
255
  // Display tool calls in UI with pending status
226
256
  for (const toolCall of receivedToolCalls) {
227
257
  const toolDisplay = formatToolCallMessage(toolCall);
@@ -292,7 +322,7 @@ export async function handleConversationWithTools(options) {
292
322
  if (options.setIsStreaming) {
293
323
  options.setIsStreaming(false);
294
324
  }
295
- encoder.free();
325
+ freeEncoder();
296
326
  return; // Exit the conversation loop
297
327
  }
298
328
  // If approved_always, add ALL these tools to both global and session-approved sets
@@ -307,7 +337,12 @@ export async function handleConversationWithTools(options) {
307
337
  approvedTools.push(...toolsNeedingConfirmation);
308
338
  }
309
339
  // Execute approved tools
310
- const toolResults = await executeToolCalls(approvedTools);
340
+ const toolResults = await executeToolCalls(approvedTools, controller.signal, setStreamTokenCount);
341
+ // Check if aborted during tool execution
342
+ if (controller.signal.aborted) {
343
+ freeEncoder();
344
+ break;
345
+ }
311
346
  // Check if there are TODO related tool calls, if yes refresh TODO list
312
347
  const hasTodoTools = approvedTools.some(t => t.function.name.startsWith('todo-'));
313
348
  const hasTodoUpdateTools = approvedTools.some(t => t.function.name === 'todo-update');
@@ -367,6 +402,13 @@ export async function handleConversationWithTools(options) {
367
402
  // If parsing fails, just show regular result
368
403
  }
369
404
  }
405
+ // Check if this tool should show preview (websearch, ace, filesystem-read, etc.)
406
+ const shouldShowPreview = toolCall.function.name.startsWith('websearch-') ||
407
+ toolCall.function.name.startsWith('ace-') ||
408
+ toolCall.function.name === 'filesystem-read' ||
409
+ toolCall.function.name === 'filesystem-list' ||
410
+ toolCall.function.name === 'filesystem-create' ||
411
+ toolCall.function.name === 'filesystem-write';
370
412
  // Update the existing pending message instead of adding a new one
371
413
  setMessages(prev => prev.map(msg => {
372
414
  if (msg.toolCallId === toolCall.id && msg.toolPending) {
@@ -384,7 +426,9 @@ export async function handleConversationWithTools(options) {
384
426
  name: toolCall.function.name,
385
427
  arguments: terminalResultData,
386
428
  }
387
- : msg.toolCall,
429
+ : shouldShowPreview
430
+ ? undefined // Clear toolCall for preview-enabled tools
431
+ : msg.toolCall, // Keep original toolCall for other tools
388
432
  // Store tool result for preview rendering
389
433
  toolResult: !isError ? result.content : undefined,
390
434
  };
@@ -464,10 +508,10 @@ export async function handleConversationWithTools(options) {
464
508
  break;
465
509
  }
466
510
  // Free encoder
467
- encoder.free();
511
+ freeEncoder();
468
512
  }
469
513
  catch (error) {
470
- encoder.free();
514
+ freeEncoder();
471
515
  throw error;
472
516
  }
473
517
  }
@@ -1,6 +1,6 @@
1
1
  export declare const NAVIGATION_EVENT = "navigate";
2
2
  export interface NavigationEvent {
3
- destination: 'welcome' | 'chat' | 'settings' | 'config' | 'models' | 'mcp';
3
+ destination: 'welcome' | 'chat' | 'settings' | 'mcp' | 'systemprompt' | 'customheaders';
4
4
  }
5
5
  export declare function navigateTo(destination: NavigationEvent['destination']): void;
6
6
  export declare function onNavigate(handler: (event: NavigationEvent) => void): () => void;
@@ -26,15 +26,21 @@ export function useKeyboardInput(options) {
26
26
  useInput((input, key) => {
27
27
  if (disabled)
28
28
  return;
29
- // Filter out focus events - ONLY if the input is exactly a focus event
30
- // Focus events from terminals: ESC[I (focus in) or ESC[O (focus out)
31
- // DO NOT filter if input contains other content (like drag-and-drop paths)
32
- // The key insight: focus events are standalone, user input is never JUST "[I" or "[O"
33
- if (input === '\x1b[I' ||
29
+ // Filter out focus events more robustly
30
+ // Focus events: ESC[I (focus in) or ESC[O (focus out)
31
+ // Some terminals may send these with or without ESC, and they might appear
32
+ // anywhere in the input string (especially during drag-and-drop with Shift held)
33
+ // We need to filter them out but NOT remove legitimate user input
34
+ if (
35
+ // Complete escape sequences
36
+ input === '\x1b[I' ||
34
37
  input === '\x1b[O' ||
35
- // Some terminals may send without ESC, but only if it's the entire input
36
- (input === '[I' && input.length === 2) ||
37
- (input === '[O' && input.length === 2)) {
38
+ // Standalone sequences (exact match only)
39
+ input === '[I' ||
40
+ input === '[O' ||
41
+ // Filter if input ONLY contains focus events and whitespace
42
+ // This handles cases like " [I" or "[I " from drag-and-drop with Shift
43
+ /^[\s\x1b\[IO]+$/.test(input) && (input.includes('[I') || input.includes('[O'))) {
38
44
  return;
39
45
  }
40
46
  // Shift+Tab - Toggle YOLO mode
@@ -39,6 +39,26 @@ export declare class FilesystemMCPService {
39
39
  */
40
40
  private readonly prettierSupportedExtensions;
41
41
  constructor(basePath?: string);
42
+ /**
43
+ * Calculate similarity between two strings using a smarter algorithm
44
+ * This normalizes whitespace first to avoid false negatives from spacing differences
45
+ * Returns a value between 0 (completely different) and 1 (identical)
46
+ */
47
+ private calculateSimilarity;
48
+ /**
49
+ * Calculate Levenshtein distance between two strings
50
+ */
51
+ private levenshteinDistance;
52
+ /**
53
+ * Find the closest matching candidates in the file content
54
+ * Returns top N candidates sorted by similarity
55
+ */
56
+ private findClosestMatches;
57
+ /**
58
+ * Generate a helpful diff message showing differences between search and actual content
59
+ * Note: This is ONLY for display purposes. Tabs/spaces are normalized for better readability.
60
+ */
61
+ private generateDiffMessage;
42
62
  /**
43
63
  * Analyze code structure for balance and completeness
44
64
  * Helps AI identify bracket mismatches, unclosed tags, and boundary issues
@@ -50,14 +70,14 @@ export declare class FilesystemMCPService {
50
70
  */
51
71
  private findSmartContextBoundaries;
52
72
  /**
53
- * Get the content of a file with specified line range
73
+ * Get the content of a file with optional line range
54
74
  * @param filePath - Path to the file (relative to base path or absolute)
55
- * @param startLine - Starting line number (1-indexed, inclusive)
56
- * @param endLine - Ending line number (1-indexed, inclusive)
75
+ * @param startLine - Starting line number (1-indexed, inclusive, optional - defaults to 1)
76
+ * @param endLine - Ending line number (1-indexed, inclusive, optional - defaults to 500 or file end)
57
77
  * @returns Object containing the requested content with line numbers and metadata
58
78
  * @throws Error if file doesn't exist or cannot be read
59
79
  */
60
- getFileContent(filePath: string, startLine: number, endLine: number): Promise<{
80
+ getFileContent(filePath: string, startLine?: number, endLine?: number): Promise<{
61
81
  content: string;
62
82
  startLine: number;
63
83
  endLine: number;
@@ -107,10 +127,10 @@ export declare class FilesystemMCPService {
107
127
  }>;
108
128
  /**
109
129
  * Edit a file by searching for exact content and replacing it
110
- * This method is SAFER than line-based editing as it automatically handles code boundaries.
130
+ * This method uses SMART MATCHING to handle whitespace differences automatically.
111
131
  *
112
132
  * @param filePath - Path to the file to edit
113
- * @param searchContent - Exact content to search for (must match precisely, including whitespace)
133
+ * @param searchContent - Content to search for (whitespace will be normalized automatically)
114
134
  * @param replaceContent - New content to replace the search content with
115
135
  * @param occurrence - Which occurrence to replace (1-indexed, default: 1, use -1 for all)
116
136
  * @param contextLines - Number of context lines to return before and after the edit (default: 8)
@@ -169,6 +189,29 @@ export declare class FilesystemMCPService {
169
189
  private validatePath;
170
190
  }
171
191
  export declare const filesystemService: FilesystemMCPService;
192
+ /**
193
+ * MCP Tool definitions for integration
194
+ *
195
+ * 🎯 **RECOMMENDED WORKFLOW FOR AI AGENTS**:
196
+ *
197
+ * 1️⃣ **SEARCH FIRST** (DON'T skip this!):
198
+ * - Use ace_text_search() to find code patterns/strings
199
+ * - Use ace_search_symbols() to find functions/classes by name
200
+ * - Use ace_file_outline() to understand file structure
201
+ *
202
+ * 2️⃣ **READ STRATEGICALLY** (Only after search):
203
+ * - Use filesystem_read() WITHOUT line numbers to read entire file
204
+ * - OR use filesystem_read(filePath, startLine, endLine) to read specific range
205
+ * - ⚠️ AVOID reading files line-by-line from top - wastes tokens!
206
+ *
207
+ * 3️⃣ **EDIT SAFELY**:
208
+ * - PREFER filesystem_edit_search() for modifying existing code (no line counting!)
209
+ * - Use filesystem_edit() only for adding new code or when search-replace doesn't fit
210
+ *
211
+ * 📊 **TOKEN EFFICIENCY**:
212
+ * - ❌ BAD: Read file top-to-bottom, repeat reading, blind scanning
213
+ * - ✅ GOOD: Search → Targeted read → Edit with context
214
+ */
172
215
  export declare const mcpTools: ({
173
216
  name: string;
174
217
  description: string;