snow-ai 0.3.28 → 0.3.29

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.
@@ -515,8 +515,10 @@ export async function handleConversationWithTools(options) {
515
515
  if (subAgentMessage.message.type === 'tool_calls') {
516
516
  const toolCalls = subAgentMessage.message.tool_calls;
517
517
  if (toolCalls && toolCalls.length > 0) {
518
- // Add tool call messages for each tool
519
- const toolMessages = toolCalls.map((toolCall) => {
518
+ // Add tool call messages for each tool (only for time-consuming tools)
519
+ const toolMessages = toolCalls
520
+ .filter((toolCall) => isToolNeedTwoStepDisplay(toolCall.function.name))
521
+ .map((toolCall) => {
520
522
  const toolDisplay = formatToolCallMessage(toolCall);
521
523
  let toolArgs;
522
524
  try {
@@ -543,16 +545,21 @@ export async function handleConversationWithTools(options) {
543
545
  },
544
546
  subAgentInternal: true, // Mark as internal sub-agent message
545
547
  };
546
- // Save to session as 'assistant' role for API compatibility
547
- const sessionMsg = {
548
- role: 'assistant',
549
- content: `⚇⚡ ${toolDisplay.toolName}`,
550
- subAgentInternal: true,
551
- tool_calls: [toolCall],
552
- };
553
- saveMessage(sessionMsg).catch(err => console.error('Failed to save sub-agent tool call:', err));
554
548
  return uiMsg;
555
549
  });
550
+ // Save all tool calls to session (regardless of display type)
551
+ const sessionMsg = {
552
+ role: 'assistant',
553
+ content: toolCalls
554
+ .map((tc) => {
555
+ const display = formatToolCallMessage(tc);
556
+ return `⚇⚡ ${display.toolName}`;
557
+ })
558
+ .join(', '),
559
+ subAgentInternal: true,
560
+ tool_calls: toolCalls,
561
+ };
562
+ saveMessage(sessionMsg).catch(err => console.error('Failed to save sub-agent tool call:', err));
556
563
  return [...prev, ...toolMessages];
557
564
  }
558
565
  }
@@ -10,8 +10,19 @@ export declare class FilesystemMCPService {
10
10
  */
11
11
  private readonly prettierSupportedExtensions;
12
12
  constructor(basePath?: string);
13
+ /**
14
+ * Extract relevant symbol information for a specific line range
15
+ * This provides context that helps AI make more accurate modifications
16
+ * @param symbols - All symbols in the file
17
+ * @param startLine - Start line of the range
18
+ * @param endLine - End line of the range
19
+ * @param _totalLines - Total lines in the file (reserved for future use)
20
+ * @returns Formatted string with relevant symbol information
21
+ */
22
+ private extractRelevantSymbols;
13
23
  /**
14
24
  * Get the content of a file with optional line range
25
+ * Enhanced with symbol information for better AI context
15
26
  * @param filePath - Path to the file (relative to base path or absolute) or array of file paths or array of file config objects
16
27
  * @param startLine - Starting line number (1-indexed, inclusive, optional - defaults to 1). Used for single file or as default for array of strings
17
28
  * @param endLine - Ending line number (1-indexed, inclusive, optional - defaults to file end). Used for single file or as default for array of strings
@@ -11,6 +11,8 @@ import { calculateSimilarity, normalizeForDisplay, } from './utils/filesystem/si
11
11
  import { analyzeCodeStructure, findSmartContextBoundaries, } from './utils/filesystem/code-analysis.utils.js';
12
12
  import { findClosestMatches, generateDiffMessage, } from './utils/filesystem/match-finder.utils.js';
13
13
  import { parseEditBySearchParams, parseEditByLineParams, executeBatchOperation, } from './utils/filesystem/batch-operations.utils.js';
14
+ // ACE Code Search utilities for symbol parsing
15
+ import { parseFileSymbols } from './utils/aceCodeSearch/symbol.utils.js';
14
16
  const { resolve, dirname, isAbsolute } = path;
15
17
  const execAsync = promisify(exec);
16
18
  /**
@@ -52,8 +54,76 @@ export class FilesystemMCPService {
52
54
  });
53
55
  this.basePath = resolve(basePath);
54
56
  }
57
+ /**
58
+ * Extract relevant symbol information for a specific line range
59
+ * This provides context that helps AI make more accurate modifications
60
+ * @param symbols - All symbols in the file
61
+ * @param startLine - Start line of the range
62
+ * @param endLine - End line of the range
63
+ * @param _totalLines - Total lines in the file (reserved for future use)
64
+ * @returns Formatted string with relevant symbol information
65
+ */
66
+ extractRelevantSymbols(symbols, startLine, endLine, _totalLines) {
67
+ if (symbols.length === 0) {
68
+ return '';
69
+ }
70
+ // Categorize symbols
71
+ const imports = symbols.filter(s => s.type === 'import');
72
+ const exports = symbols.filter(s => s.type === 'export');
73
+ // Symbols within the requested range
74
+ const symbolsInRange = symbols.filter(s => s.line >= startLine && s.line <= endLine);
75
+ // Symbols defined before the range that might be referenced
76
+ const symbolsBeforeRange = symbols.filter(s => s.line < startLine);
77
+ // Build context information
78
+ const parts = [];
79
+ // Always include imports (crucial for understanding dependencies)
80
+ if (imports.length > 0) {
81
+ const importList = imports
82
+ .slice(0, 10) // Limit to avoid excessive tokens
83
+ .map(s => ` • ${s.name} (line ${s.line})`)
84
+ .join('\n');
85
+ parts.push(`📦 Imports:\n${importList}`);
86
+ }
87
+ // Symbols defined in the current range
88
+ if (symbolsInRange.length > 0) {
89
+ const rangeSymbols = symbolsInRange
90
+ .slice(0, 15)
91
+ .map(s => ` • ${s.type}: ${s.name} (line ${s.line})${s.signature ? ` - ${s.signature.slice(0, 60)}` : ''}`)
92
+ .join('\n');
93
+ parts.push(`🎯 Symbols in this range:\n${rangeSymbols}`);
94
+ }
95
+ // Key definitions before this range (that might be referenced)
96
+ if (symbolsBeforeRange.length > 0 && startLine > 1) {
97
+ const relevantBefore = symbolsBeforeRange
98
+ .filter(s => s.type === 'function' || s.type === 'class')
99
+ .slice(-5) // Last 5 before the range
100
+ .map(s => ` • ${s.type}: ${s.name} (line ${s.line})`)
101
+ .join('\n');
102
+ if (relevantBefore) {
103
+ parts.push(`⬆️ Key definitions above:\n${relevantBefore}`);
104
+ }
105
+ }
106
+ // Exports (important for understanding module interface)
107
+ if (exports.length > 0) {
108
+ const exportList = exports
109
+ .slice(0, 10)
110
+ .map(s => ` • ${s.name} (line ${s.line})`)
111
+ .join('\n');
112
+ parts.push(`📤 Exports:\n${exportList}`);
113
+ }
114
+ if (parts.length === 0) {
115
+ return '';
116
+ }
117
+ return ('\n\n' +
118
+ '='.repeat(60) +
119
+ '\n📚 SYMBOL INDEX & DEFINITIONS:\n' +
120
+ '='.repeat(60) +
121
+ '\n' +
122
+ parts.join('\n\n'));
123
+ }
55
124
  /**
56
125
  * Get the content of a file with optional line range
126
+ * Enhanced with symbol information for better AI context
57
127
  * @param filePath - Path to the file (relative to base path or absolute) or array of file paths or array of file config objects
58
128
  * @param startLine - Starting line number (1-indexed, inclusive, optional - defaults to 1). Used for single file or as default for array of strings
59
129
  * @param endLine - Ending line number (1-indexed, inclusive, optional - defaults to file end). Used for single file or as default for array of strings
@@ -127,7 +197,18 @@ export class FilesystemMCPService {
127
197
  const lineNum = start + index;
128
198
  return `${lineNum}→${line}`;
129
199
  });
130
- const fileContent = `📄 ${file} (lines ${start}-${end}/${totalLines})\n${numberedLines.join('\n')}`;
200
+ let fileContent = `📄 ${file} (lines ${start}-${end}/${totalLines})\n${numberedLines.join('\n')}`;
201
+ // Parse and append symbol information
202
+ try {
203
+ const symbols = await parseFileSymbols(fullPath, content, this.basePath);
204
+ const symbolInfo = this.extractRelevantSymbols(symbols, start, end, totalLines);
205
+ if (symbolInfo) {
206
+ fileContent += symbolInfo;
207
+ }
208
+ }
209
+ catch {
210
+ // Silently fail symbol parsing
211
+ }
131
212
  allContents.push(fileContent);
132
213
  filesData.push({
133
214
  path: file,
@@ -197,7 +278,19 @@ export class FilesystemMCPService {
197
278
  const lineNum = start + index;
198
279
  return `${lineNum}→${line}`;
199
280
  });
200
- const partialContent = numberedLines.join('\n');
281
+ let partialContent = numberedLines.join('\n');
282
+ // Parse and append symbol information to provide better context for AI
283
+ try {
284
+ const symbols = await parseFileSymbols(fullPath, content, this.basePath);
285
+ const symbolInfo = this.extractRelevantSymbols(symbols, start, end, totalLines);
286
+ if (symbolInfo) {
287
+ partialContent += symbolInfo;
288
+ }
289
+ }
290
+ catch (error) {
291
+ // Silently fail symbol parsing - don't block file reading
292
+ // This is optional context enhancement, not critical
293
+ }
201
294
  return {
202
295
  content: partialContent,
203
296
  startLine: start,
@@ -6,80 +6,399 @@
6
6
  */
7
7
  export const LANGUAGE_CONFIG = {
8
8
  typescript: {
9
- extensions: ['.ts', '.tsx'],
9
+ extensions: ['.ts', '.tsx', '.mts', '.cts'],
10
10
  parser: 'typescript',
11
11
  symbolPatterns: {
12
- function: /(?:export\s+)?(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
13
- class: /(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/,
14
- variable: /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/,
15
- import: /import\s+(?:{[^}]+}|\w+)\s+from\s+['"]([^'"]+)['"]/,
16
- export: /export\s+(?:default\s+)?(?:class|function|const|let|var|interface|type|enum)\s+(\w+)/,
12
+ function: /(?:export\s+)?(?:async\s+)?(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>)|(?:@\w+\s+)*(?:public|private|protected|static)?\s*(?:async)?\s*(\w+)\s*[<(]/,
13
+ class: /(?:export\s+)?(?:abstract\s+)?(?:class|interface)\s+(\w+)|(?:export\s+)?type\s+(\w+)\s*=|(?:export\s+)?enum\s+(\w+)|(?:export\s+)?namespace\s+(\w+)/,
14
+ variable: /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*(?::|=)|(?:@\w+\s+)*(?:public|private|protected|readonly|static)?\s+(\w+)\s*[?:]/,
15
+ import: /import\s+(?:type\s+)?(?:{[^}]+}|\w+|\*\s+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/,
16
+ export: /export\s+(?:default\s+)?(?:class|function|const|let|var|interface|type|enum|namespace|abstract\s+class)\s+(\w+)/,
17
17
  },
18
18
  },
19
19
  javascript: {
20
- extensions: ['.js', '.jsx', '.mjs', '.cjs'],
20
+ extensions: ['.js', '.jsx', '.mjs', '.cjs', '.es', '.es6'],
21
21
  parser: 'javascript',
22
22
  symbolPatterns: {
23
- function: /(?:export\s+)?(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
23
+ function: /(?:export\s+)?(?:async\s+)?(?:function\s*\*?\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function\s*\*?\s*)?(?:\([^)]*\)\s*=>|\([^)]*\)\s*\{))|(\w+)\s*\([^)]*\)\s*\{/,
24
24
  class: /(?:export\s+)?class\s+(\w+)/,
25
25
  variable: /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/,
26
- import: /import\s+(?:{[^}]+}|\w+)\s+from\s+['"]([^'"]+)['"]/,
26
+ import: /import\s+(?:{[^}]+}|\w+|\*\s+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/,
27
27
  export: /export\s+(?:default\s+)?(?:class|function|const|let|var)\s+(\w+)/,
28
28
  },
29
29
  },
30
30
  python: {
31
- extensions: ['.py', '.pyx', '.pyi'],
31
+ extensions: ['.py', '.pyx', '.pyi', '.pyw', '.pyz'],
32
32
  parser: 'python',
33
33
  symbolPatterns: {
34
- function: /def\s+(\w+)\s*\(/,
35
- class: /class\s+(\w+)\s*[(:]/,
36
- variable: /(\w+)\s*=\s*[^=]/,
37
- import: /(?:from\s+[\w.]+\s+)?import\s+([\w, ]+)/,
38
- export: /^(\w+)\s*=\s*/, // Python doesn't have explicit exports
34
+ function: /(?:@\w+\s+)*(?:async\s+)?def\s+(\w+)\s*\(/,
35
+ class: /(?:@\w+\s+)*class\s+(\w+)\s*[(:]/,
36
+ variable: /^(?:[\t ]*)([\w_][\w\d_]*)\s*(?::.*)?=\s*(?![=\s])|^([\w_][\w\d_]*)\s*:\s*(?!.*=)/m,
37
+ import: /(?:from\s+([\w.]+)\s+import\s+[\w, *]+|import\s+([\w.]+(?:\s+as\s+\w+)?))/,
38
+ export: /^(?:__all__\s*=|def\s+(\w+)|class\s+(\w+))/, // Python exports via __all__ or top-level
39
39
  },
40
40
  },
41
41
  go: {
42
42
  extensions: ['.go'],
43
43
  parser: 'go',
44
44
  symbolPatterns: {
45
- function: /func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/,
46
- class: /type\s+(\w+)\s+struct/,
47
- variable: /(?:var|const)\s+(\w+)\s+/,
48
- import: /import\s+(?:"([^"]+)"|[(]([^)]+)[)])/,
49
- export: /^(?:func|type|var|const)\s+([A-Z]\w+)/, // Go exports start with capital letter
45
+ function: /func\s+(?:\([^)]+\)\s+)?(\w+)\s*[<(]/,
46
+ class: /type\s+(\w+)\s+(?:struct|interface)/,
47
+ variable: /(?:var|const)\s+(\w+)\s+[\w\[\]*{]|(?:var|const)\s+\(\s*(\w+)/,
48
+ import: /import\s+(?:"([^"]+)"|_\s+"([^"]+)"|\w+\s+"([^"]+)")/,
49
+ export: /^(?:func|type|var|const)\s+([A-Z]\w+)|^type\s+([A-Z]\w+)\s+(?:struct|interface)/, // Go exports start with capital letter
50
50
  },
51
51
  },
52
52
  rust: {
53
53
  extensions: ['.rs'],
54
54
  parser: 'rust',
55
55
  symbolPatterns: {
56
- function: /(?:pub\s+)?(?:async\s+)?fn\s+(\w+)\s*[<(]/,
57
- class: /(?:pub\s+)?struct\s+(\w+)|(?:pub\s+)?enum\s+(\w+)|(?:pub\s+)?trait\s+(\w+)/,
58
- variable: /(?:pub\s+)?(?:static|const)\s+(\w+)\s*:/,
59
- import: /use\s+([^;]+);/,
60
- export: /pub\s+(?:fn|struct|enum|trait|const|static)\s+(\w+)/,
56
+ function: /(?:pub(?:\s*\([^)]+\))?\s+)?(?:unsafe\s+)?(?:async\s+)?(?:const\s+)?(?:extern\s+(?:"[^"]+"\s+)?)?fn\s+(\w+)\s*[<(]/,
57
+ class: /(?:pub(?:\s*\([^)]+\))?\s+)?(?:struct|enum|trait|union|type)\s+(\w+)|impl(?:\s+<[^>]+>)?\s+(?:\w+::)*(\w+)/,
58
+ variable: /(?:pub(?:\s*\([^)]+\))?\s+)?(?:static|const|mut)?\s*(?:let\s+(?:mut\s+)?)?(\w+)\s*[:=]/,
59
+ import: /use\s+([^;]+);|extern\s+crate\s+(\w+);/,
60
+ export: /pub(?:\s*\([^)]+\))?\s+(?:fn|struct|enum|trait|const|static|type|mod|use)\s+(\w+)/,
61
61
  },
62
62
  },
63
63
  java: {
64
64
  extensions: ['.java'],
65
65
  parser: 'java',
66
66
  symbolPatterns: {
67
- function: /(?:public|private|protected|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/,
68
- class: /(?:public|private|protected)?\s*(?:abstract|final)?\s*class\s+(\w+)/,
69
- variable: /(?:public|private|protected|static|final|\s)+[\w<>\[\]]+\s+(\w+)\s*[=;]/,
70
- import: /import\s+([\w.]+);/,
71
- export: /public\s+(?:class|interface|enum)\s+(\w+)/,
67
+ function: /(?:@\w+\s+)*(?:public|private|protected|static|final|synchronized|native|abstract|\s)+[\w<>\[\]]+\s+(\w+)\s*\([^)]*\)\s*(?:throws\s+[\w,\s]+)?\s*[{;]/,
68
+ class: /(?:@\w+\s+)*(?:public|private|protected)?\s*(?:abstract|final|static)?\s*(?:class|interface|enum|record|@interface)\s+(\w+)/,
69
+ variable: /(?:@\w+\s+)*(?:public|private|protected|static|final|transient|volatile|\s)+[\w<>\[\]]+\s+(\w+)\s*[=;]/,
70
+ import: /import\s+(?:static\s+)?([\w.*]+);/,
71
+ export: /public\s+(?:class|interface|enum|record|@interface)\s+(\w+)/,
72
72
  },
73
73
  },
74
74
  csharp: {
75
75
  extensions: ['.cs'],
76
76
  parser: 'csharp',
77
77
  symbolPatterns: {
78
- function: /(?:public|private|protected|internal|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/,
79
- class: /(?:public|private|protected|internal)?\s*(?:abstract|sealed|static)?\s*class\s+(\w+)/,
80
- variable: /(?:public|private|protected|internal|static|readonly|\s)+[\w<>\[\]]+\s+(\w+)\s*[=;]/,
81
- import: /using\s+([\w.]+);/,
82
- export: /public\s+(?:class|interface|enum|struct)\s+(\w+)/,
78
+ function: /(?:\[[\w\s,()]+\]\s+)*(?:public|private|protected|internal|static|virtual|override|abstract|async|\s)+[\w<>\[\]?]+\s+(\w+)\s*[<(]/,
79
+ class: /(?:\[[\w\s,()]+\]\s+)*(?:public|private|protected|internal)?\s*(?:abstract|sealed|static|partial)?\s*(?:class|interface|struct|record|enum)\s+(\w+)/,
80
+ variable: /(?:\[[\w\s,()]+\]\s+)*(?:public|private|protected|internal|static|readonly|const|volatile|\s)+[\w<>\[\]?]+\s+(\w+)\s*[{=;]|(?:public|private|protected|internal)?\s*[\w<>\[\]?]+\s+(\w+)\s*\{\s*get/,
81
+ import: /using\s+(?:static\s+)?([\w.]+);/,
82
+ export: /public\s+(?:class|interface|enum|struct|record|delegate)\s+(\w+)/,
83
+ },
84
+ },
85
+ c: {
86
+ extensions: ['.c', '.h'],
87
+ parser: 'c',
88
+ symbolPatterns: {
89
+ function: /(?:static|extern|inline)?\s*[\w\s\*]+\s+(\w+)\s*\([^)]*\)\s*\{/,
90
+ class: /(?:struct|union|enum)\s+(\w+)\s*\{/,
91
+ variable: /(?:extern|static|const)?\s*[\w\s\*]+\s+(\w+)\s*[=;]/,
92
+ import: /#include\s+[<"]([^>"]+)[>"]/,
93
+ export: /^[\w\s\*]+\s+(\w+)\s*\([^)]*\)\s*;/, // Function declarations
94
+ },
95
+ },
96
+ cpp: {
97
+ extensions: ['.cpp', '.cc', '.cxx', '.hpp', '.hh', '.hxx', '.h++', '.c++'],
98
+ parser: 'cpp',
99
+ symbolPatterns: {
100
+ function: /(?:static|extern|inline|virtual|explicit|constexpr)?\s*[\w\s\*&:<>,]+\s+(\w+)\s*\([^)]*\)\s*(?:const)?\s*(?:override)?\s*\{/,
101
+ class: /(?:class|struct|union|enum\s+class|enum\s+struct)\s+(\w+)(?:\s*:\s*(?:public|private|protected)\s+[\w,\s<>]+)?\s*\{/,
102
+ variable: /(?:extern|static|const|constexpr|inline)?\s*[\w\s\*&:<>,]+\s+(\w+)\s*[=;]/,
103
+ import: /#include\s+[<"]([^>"]+)[>"]/,
104
+ export: /^[\w\s\*&:<>,]+\s+(\w+)\s*\([^)]*\)\s*;/,
105
+ },
106
+ },
107
+ php: {
108
+ extensions: ['.php', '.phtml', '.php3', '.php4', '.php5', '.phps'],
109
+ parser: 'php',
110
+ symbolPatterns: {
111
+ function: /(?:public|private|protected|static)?\s*function\s+(\w+)\s*\(/,
112
+ class: /(?:abstract|final)?\s*class\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w,\s]+)?\s*\{/,
113
+ variable: /(?:public|private|protected|static)?\s*\$(\w+)\s*[=;]/,
114
+ import: /(?:require|require_once|include|include_once)\s*[('"]([^'"]+)['"]/,
115
+ export: /^(?:public\s+)?(?:function|class|interface|trait)\s+(\w+)/,
116
+ },
117
+ },
118
+ ruby: {
119
+ extensions: ['.rb', '.rake', '.gemspec', '.ru', '.rbw'],
120
+ parser: 'ruby',
121
+ symbolPatterns: {
122
+ function: /def\s+(?:self\.)?(\w+)/,
123
+ class: /class\s+(\w+)(?:\s+<\s+[\w:]+)?/,
124
+ variable: /(?:@|@@|\$)?(\w+)\s*=(?!=)/,
125
+ import: /require(?:_relative)?\s+['"]([^'"]+)['"]/,
126
+ export: /module_function\s+:(\w+)|^def\s+(\w+)/, // Ruby's module exports
127
+ },
128
+ },
129
+ swift: {
130
+ extensions: ['.swift'],
131
+ parser: 'swift',
132
+ symbolPatterns: {
133
+ function: /(?:public|private|internal|fileprivate|open)?\s*(?:static|class)?\s*func\s+(\w+)\s*[<(]/,
134
+ class: /(?:public|private|internal|fileprivate|open)?\s*(?:final)?\s*(?:class|struct|enum|protocol|actor)\s+(\w+)/,
135
+ variable: /(?:public|private|internal|fileprivate|open)?\s*(?:static|class)?\s*(?:let|var)\s+(\w+)\s*[:=]/,
136
+ import: /import\s+(?:class|struct|enum|protocol)?\s*([\w.]+)/,
137
+ export: /public\s+(?:func|class|struct|enum|protocol|var|let)\s+(\w+)/,
138
+ },
139
+ },
140
+ kotlin: {
141
+ extensions: ['.kt', '.kts'],
142
+ parser: 'kotlin',
143
+ symbolPatterns: {
144
+ function: /(?:public|private|protected|internal)?\s*(?:suspend|inline|infix|operator)?\s*fun\s+(\w+)\s*[<(]/,
145
+ class: /(?:public|private|protected|internal)?\s*(?:abstract|open|final|sealed|data|inline|value)?\s*(?:class|interface|object|enum\s+class)\s+(\w+)/,
146
+ variable: /(?:public|private|protected|internal)?\s*(?:const)?\s*(?:val|var)\s+(\w+)\s*[:=]/,
147
+ import: /import\s+([\w.]+)/,
148
+ export: /^(?:public\s+)?(?:fun|class|interface|object|val|var)\s+(\w+)/,
149
+ },
150
+ },
151
+ dart: {
152
+ extensions: ['.dart'],
153
+ parser: 'dart',
154
+ symbolPatterns: {
155
+ function: /(?:static|abstract|external)?\s*[\w<>?,\s]+\s+(\w+)\s*\([^)]*\)\s*(?:async|sync\*)?\s*\{/,
156
+ class: /(?:abstract)?\s*class\s+(\w+)(?:\s+extends\s+[\w<>]+)?(?:\s+with\s+[\w,\s<>]+)?(?:\s+implements\s+[\w,\s<>]+)?\s*\{/,
157
+ variable: /(?:static|final|const|late)?\s*(?:var|[\w<>?,\s]+)\s+(\w+)\s*[=;]/,
158
+ import: /import\s+['"]([^'"]+)['"]/,
159
+ export: /^(?:class|abstract\s+class|enum|mixin)\s+(\w+)/,
160
+ },
161
+ },
162
+ shell: {
163
+ extensions: ['.sh', '.bash', '.zsh', '.ksh', '.fish'],
164
+ parser: 'shell',
165
+ symbolPatterns: {
166
+ function: /(?:function\s+)?(\w+)\s*\(\s*\)\s*\{/,
167
+ class: /^$/, // Shell doesn't have classes
168
+ variable: /(?:export\s+)?(\w+)=/,
169
+ import: /(?:source|\.)\s+([^\s;]+)/,
170
+ export: /export\s+(?:function\s+)?(\w+)/,
171
+ },
172
+ },
173
+ scala: {
174
+ extensions: ['.scala', '.sc'],
175
+ parser: 'scala',
176
+ symbolPatterns: {
177
+ function: /def\s+(\w+)\s*[:\[(]/,
178
+ class: /(?:sealed|abstract|final|implicit)?\s*(?:class|trait|object|case\s+class|case\s+object)\s+(\w+)/,
179
+ variable: /(?:val|var|lazy\s+val)\s+(\w+)\s*[:=]/,
180
+ import: /import\s+([\w.{},\s=>]+)/,
181
+ export: /^(?:object|class|trait)\s+(\w+)/,
182
+ },
183
+ },
184
+ r: {
185
+ extensions: ['.r', '.R', '.rmd', '.Rmd'],
186
+ parser: 'r',
187
+ symbolPatterns: {
188
+ function: /(\w+)\s*<-\s*function\s*\(|^(\w+)\s*=\s*function\s*\(/,
189
+ class: /setClass\s*\(\s*['"](\w+)['"]/,
190
+ variable: /(\w+)\s*(?:<-|=)\s*(?!function)/,
191
+ import: /(?:library|require)\s*\(\s*['"]?(\w+)['"]?\s*\)/,
192
+ export: /^(\w+)\s*<-\s*function/, // R exports at top level
193
+ },
194
+ },
195
+ lua: {
196
+ extensions: ['.lua'],
197
+ parser: 'lua',
198
+ symbolPatterns: {
199
+ function: /(?:local\s+)?function\s+(?:[\w.]+[.:])?(\w+)\s*\(/,
200
+ class: /(\w+)\s*=\s*\{\s*\}|(\w+)\s*=\s*class\s*\(/,
201
+ variable: /(?:local\s+)?(\w+)\s*=/,
202
+ import: /require\s*\(?['"]([^'"]+)['"]\)?/,
203
+ export: /return\s+(\w+)|module\s*\(\s*['"]([^'"]+)['"]/,
204
+ },
205
+ },
206
+ perl: {
207
+ extensions: ['.pl', '.pm', '.t', '.pod'],
208
+ parser: 'perl',
209
+ symbolPatterns: {
210
+ function: /sub\s+(\w+)\s*\{/,
211
+ class: /package\s+([\w:]+)\s*;/,
212
+ variable: /(?:my|our|local)\s*[\$@%](\w+)\s*=/,
213
+ import: /(?:use|require)\s+([\w:]+)/,
214
+ export: /^sub\s+(\w+)|our\s+[\$@%](\w+)/,
215
+ },
216
+ },
217
+ objectivec: {
218
+ extensions: ['.m', '.mm', '.h'],
219
+ parser: 'objectivec',
220
+ symbolPatterns: {
221
+ function: /[-+]\s*\([^)]+\)\s*(\w+)(?::|;|\s*\{)/,
222
+ class: /@(?:interface|implementation|protocol)\s+(\w+)/,
223
+ variable: /@property\s+[^;]+\s+(\w+);|^[\w\s\*]+\s+(\w+)\s*[=;]/,
224
+ import: /#import\s+[<"]([^>"]+)[>"]/,
225
+ export: /@interface\s+(\w+)|@protocol\s+(\w+)/,
226
+ },
227
+ },
228
+ haskell: {
229
+ extensions: ['.hs', '.lhs'],
230
+ parser: 'haskell',
231
+ symbolPatterns: {
232
+ function: /^(\w+)\s*::/,
233
+ class: /(?:class|instance)\s+(\w+)/,
234
+ variable: /^(\w+)\s*=/,
235
+ import: /import\s+(?:qualified\s+)?([\w.]+)/,
236
+ export: /module\s+[\w.]+\s*\(([^)]+)\)/,
237
+ },
238
+ },
239
+ elixir: {
240
+ extensions: ['.ex', '.exs'],
241
+ parser: 'elixir',
242
+ symbolPatterns: {
243
+ function: /def(?:p|macro|macrop)?\s+(\w+)(?:\(|,|\s+do)/,
244
+ class: /defmodule\s+([\w.]+)\s+do/,
245
+ variable: /@(\w+)\s+|(\w+)\s*=\s*(?!fn)/,
246
+ import: /(?:import|alias|require|use)\s+([\w.]+)/,
247
+ export: /^def\s+(\w+)/,
248
+ },
249
+ },
250
+ clojure: {
251
+ extensions: ['.clj', '.cljs', '.cljc', '.edn'],
252
+ parser: 'clojure',
253
+ symbolPatterns: {
254
+ function: /\(defn-?\s+(\w+)/,
255
+ class: /\(defrecord\s+(\w+)|\(deftype\s+(\w+)|\(defprotocol\s+(\w+)/,
256
+ variable: /\(def\s+(\w+)/,
257
+ import: /\(:require\s+\[([^\]]+)\]/,
258
+ export: /\(defn-?\s+(\w+)/,
259
+ },
260
+ },
261
+ fsharp: {
262
+ extensions: ['.fs', '.fsx', '.fsi'],
263
+ parser: 'fsharp',
264
+ symbolPatterns: {
265
+ function: /let\s+(?:rec\s+)?(\w+)(?:\s+\w+)*\s*=/,
266
+ class: /type\s+(\w+)\s*(?:=|<|\()/,
267
+ variable: /let\s+(?:mutable\s+)?(\w+)\s*=/,
268
+ import: /open\s+([\w.]+)/,
269
+ export: /^(?:let|type)\s+(\w+)/,
270
+ },
271
+ },
272
+ vbnet: {
273
+ extensions: ['.vb', '.vbs'],
274
+ parser: 'vbnet',
275
+ symbolPatterns: {
276
+ function: /(?:Public|Private|Protected|Friend)?\s*(?:Shared)?\s*(?:Function|Sub)\s+(\w+)/i,
277
+ class: /(?:Public|Private|Protected|Friend)?\s*(?:MustInherit|NotInheritable)?\s*Class\s+(\w+)/i,
278
+ variable: /(?:Public|Private|Protected|Friend|Dim|Const)?\s*(\w+)\s+As\s+/i,
279
+ import: /Imports\s+([\w.]+)/i,
280
+ export: /Public\s+(?:Class|Module|Function|Sub)\s+(\w+)/i,
281
+ },
282
+ },
283
+ matlab: {
284
+ extensions: ['.m', '.mlx'],
285
+ parser: 'matlab',
286
+ symbolPatterns: {
287
+ function: /function\s+(?:\[[^\]]+\]\s*=\s*|[\w,\s]+\s*=\s*)?(\w+)\s*\(/,
288
+ class: /classdef\s+(\w+)/,
289
+ variable: /(\w+)\s*=\s*(?!function)/,
290
+ import: /import\s+([\w.*]+)/,
291
+ export: /^function\s+(?:\[[^\]]+\]\s*=\s*)?(\w+)/,
292
+ },
293
+ },
294
+ sql: {
295
+ extensions: ['.sql', '.ddl', '.dml'],
296
+ parser: 'sql',
297
+ symbolPatterns: {
298
+ function: /CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE)\s+(\w+)/i,
299
+ class: /CREATE\s+(?:TABLE|VIEW)\s+(\w+)/i,
300
+ variable: /DECLARE\s+@?(\w+)/i,
301
+ import: /^$/, // SQL doesn't have imports
302
+ export: /^CREATE\s+(?:FUNCTION|PROCEDURE|VIEW)\s+(\w+)/i,
303
+ },
304
+ },
305
+ html: {
306
+ extensions: ['.html', '.htm', '.xhtml'],
307
+ parser: 'html',
308
+ symbolPatterns: {
309
+ function: /<script[^>]*>[\s\S]*?function\s+(\w+)/,
310
+ class: /class\s*=\s*["']([^"']+)["']/,
311
+ variable: /id\s*=\s*["']([^"']+)["']/,
312
+ import: /<(?:link|script)[^>]+(?:href|src)\s*=\s*["']([^"']+)["']/,
313
+ export: /<(?:div|section|article|header|footer)[^>]+id\s*=\s*["']([^"']+)["']/,
314
+ },
315
+ },
316
+ css: {
317
+ extensions: ['.css', '.scss', '.sass', '.less', '.styl'],
318
+ parser: 'css',
319
+ symbolPatterns: {
320
+ function: /@mixin\s+(\w+)|@function\s+(\w+)/,
321
+ class: /\.(\w+(?:-\w+)*)\s*\{/,
322
+ variable: /--(\w+(?:-\w+)*):|@(\w+):|(\$\w+):/,
323
+ import: /@import\s+(?:url\()?['"]([^'"]+)['"]/,
324
+ export: /@mixin\s+(\w+)|@function\s+(\w+)/,
325
+ },
326
+ },
327
+ vue: {
328
+ extensions: ['.vue'],
329
+ parser: 'vue',
330
+ symbolPatterns: {
331
+ function: /<script[^>]*>[\s\S]*?(?:export\s+default\s*\{[\s\S]*?)?(?:function|const|let|var)\s+(\w+)|methods\s*:\s*\{[\s\S]*?(\w+)\s*\(/,
332
+ class: /<template[^>]*>[\s\S]*?<(\w+)/,
333
+ variable: /<script[^>]*>[\s\S]*?(?:data\s*\(\s*\)\s*\{[\s\S]*?return\s*\{[\s\S]*?(\w+)|(?:const|let|var)\s+(\w+)\s*=)/,
334
+ import: /<script[^>]*>[\s\S]*?import\s+(?:{[^}]+}|\w+)\s+from\s+['"]([^'"]+)['"]/,
335
+ export: /<script[^>]*>[\s\S]*?export\s+default/,
336
+ },
337
+ },
338
+ svelte: {
339
+ extensions: ['.svelte'],
340
+ parser: 'svelte',
341
+ symbolPatterns: {
342
+ function: /<script[^>]*>[\s\S]*?(?:function|const|let|var)\s+(\w+)\s*[=(]/,
343
+ class: /<[\w-]+/,
344
+ variable: /<script[^>]*>[\s\S]*?(?:let|const|var)\s+(\w+)\s*=/,
345
+ import: /<script[^>]*>[\s\S]*?import\s+(?:{[^}]+}|\w+)\s+from\s+['"]([^'"]+)['"]/,
346
+ export: /<script[^>]*>[\s\S]*?export\s+(?:let|const|function)\s+(\w+)/,
347
+ },
348
+ },
349
+ xml: {
350
+ extensions: ['.xml', '.xsd', '.xsl', '.xslt', '.svg'],
351
+ parser: 'xml',
352
+ symbolPatterns: {
353
+ function: /<xsl:template[^>]+name\s*=\s*["']([^"']+)["']/,
354
+ class: /<(?:xsd:)?(?:complexType|simpleType)[^>]+name\s*=\s*["']([^"']+)["']/,
355
+ variable: /<(?:xsd:)?element[^>]+name\s*=\s*["']([^"']+)["']/,
356
+ import: /<(?:xsd:)?import[^>]+schemaLocation\s*=\s*["']([^"']+)["']/,
357
+ export: /<(?:xsd:)?element[^>]+name\s*=\s*["']([^"']+)["']/,
358
+ },
359
+ },
360
+ yaml: {
361
+ extensions: ['.yaml', '.yml'],
362
+ parser: 'yaml',
363
+ symbolPatterns: {
364
+ function: /^(\w+):\s*\|/m,
365
+ class: /^(\w+):$/m,
366
+ variable: /^(\w+):\s*[^|>]/m,
367
+ import: /^$/, // YAML doesn't have imports
368
+ export: /^(\w+):$/m,
369
+ },
370
+ },
371
+ json: {
372
+ extensions: ['.json', '.jsonc', '.json5'],
373
+ parser: 'json',
374
+ symbolPatterns: {
375
+ function: /^$/,
376
+ class: /^$/,
377
+ variable: /"(\w+)"\s*:/,
378
+ import: /^$/,
379
+ export: /^$/,
380
+ },
381
+ },
382
+ toml: {
383
+ extensions: ['.toml'],
384
+ parser: 'toml',
385
+ symbolPatterns: {
386
+ function: /^$/,
387
+ class: /^\[(\w+(?:\.\w+)*)\]/,
388
+ variable: /^(\w+)\s*=/,
389
+ import: /^$/,
390
+ export: /^\[(\w+(?:\.\w+)*)\]/,
391
+ },
392
+ },
393
+ markdown: {
394
+ extensions: ['.md', '.markdown', '.mdown', '.mkd'],
395
+ parser: 'markdown',
396
+ symbolPatterns: {
397
+ function: /```[\w]*\n[\s\S]*?function\s+(\w+)/,
398
+ class: /^#{1,6}\s+(.+)$/m,
399
+ variable: /\[([^\]]+)\]:/,
400
+ import: /\[([^\]]+)\]\(([^)]+)\)/,
401
+ export: /^#{1,6}\s+(.+)$/m,
83
402
  },
84
403
  },
85
404
  };
@@ -88,6 +88,7 @@ const toolCategories = [
88
88
  export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = false, agentId, }) {
89
89
  const [agentName, setAgentName] = useState('');
90
90
  const [description, setDescription] = useState('');
91
+ const [role, setRole] = useState('');
91
92
  const [selectedTools, setSelectedTools] = useState(new Set());
92
93
  const [currentField, setCurrentField] = useState('name');
93
94
  const [selectedCategoryIndex, setSelectedCategoryIndex] = useState(0);
@@ -105,6 +106,7 @@ export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = fals
105
106
  if (agent) {
106
107
  setAgentName(agent.name);
107
108
  setDescription(agent.description);
109
+ setRole(agent.role || '');
108
110
  setSelectedTools(new Set(agent.tools));
109
111
  }
110
112
  }
@@ -207,12 +209,13 @@ export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = fals
207
209
  updateSubAgent(agentId, {
208
210
  name: agentName,
209
211
  description: description,
212
+ role: role || undefined,
210
213
  tools: Array.from(selectedTools),
211
214
  });
212
215
  }
213
216
  else {
214
217
  // Create new agent
215
- createSubAgent(agentName, description, Array.from(selectedTools));
218
+ createSubAgent(agentName, description, Array.from(selectedTools), role || undefined);
216
219
  }
217
220
  setShowSuccess(true);
218
221
  setTimeout(() => {
@@ -223,7 +226,15 @@ export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = fals
223
226
  catch (error) {
224
227
  setSaveError(error instanceof Error ? error.message : 'Failed to save sub-agent');
225
228
  }
226
- }, [agentName, description, selectedTools, onSave, isEditMode, agentId]);
229
+ }, [
230
+ agentName,
231
+ description,
232
+ role,
233
+ selectedTools,
234
+ onSave,
235
+ isEditMode,
236
+ agentId,
237
+ ]);
227
238
  useInput((rawInput, key) => {
228
239
  const input = stripFocusArtifacts(rawInput);
229
240
  // Ignore focus events completely
@@ -247,6 +258,10 @@ export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = fals
247
258
  setCurrentField('name');
248
259
  return;
249
260
  }
261
+ else if (currentField === 'role') {
262
+ setCurrentField('description');
263
+ return;
264
+ }
250
265
  else if (currentField === 'tools') {
251
266
  // Navigate within tools
252
267
  if (selectedToolIndex > 0) {
@@ -258,8 +273,8 @@ export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = fals
258
273
  setSelectedToolIndex(prevCategory ? prevCategory.tools.length - 1 : 0);
259
274
  }
260
275
  else {
261
- // At top of tools, go to description
262
- setCurrentField('description');
276
+ // At top of tools, go to role
277
+ setCurrentField('role');
263
278
  }
264
279
  return;
265
280
  }
@@ -270,6 +285,10 @@ export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = fals
270
285
  return;
271
286
  }
272
287
  else if (currentField === 'description') {
288
+ setCurrentField('role');
289
+ return;
290
+ }
291
+ else if (currentField === 'role') {
273
292
  setCurrentField('tools');
274
293
  setSelectedCategoryIndex(0);
275
294
  setSelectedToolIndex(0);
@@ -387,6 +406,10 @@ export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = fals
387
406
  React.createElement(Text, { bold: true, color: currentField === 'description' ? 'green' : 'white' }, "Description:"),
388
407
  React.createElement(Box, { marginLeft: 2 },
389
408
  React.createElement(TextInput, { value: description, onChange: value => setDescription(stripFocusArtifacts(value)), placeholder: "Enter agent description...", focus: currentField === 'description' }))),
409
+ React.createElement(Box, { flexDirection: "column" },
410
+ React.createElement(Text, { bold: true, color: currentField === 'role' ? 'green' : 'white' }, "Role (Optional):"),
411
+ React.createElement(Box, { marginLeft: 2 },
412
+ React.createElement(TextInput, { value: role, onChange: value => setRole(stripFocusArtifacts(value)), placeholder: "Specify agent role to guide output and focus...", focus: currentField === 'role' }))),
390
413
  renderToolSelection(),
391
414
  React.createElement(Box, { marginTop: 1 },
392
415
  React.createElement(Text, { color: "gray", dimColor: true }, "\u2191\u2193: Navigate | \u2190\u2192: Switch category | Space: Toggle | A: Toggle all | Enter: Save | Esc: Back")))));
@@ -18,27 +18,30 @@ export function convertSessionMessagesToUI(sessionMessages) {
18
18
  // Handle sub-agent internal tool call messages
19
19
  if (msg.subAgentInternal && msg.role === 'assistant' && msg.tool_calls) {
20
20
  for (const toolCall of msg.tool_calls) {
21
- const toolDisplay = formatToolCallMessage(toolCall);
22
- let toolArgs;
23
- try {
24
- toolArgs = JSON.parse(toolCall.function.arguments);
25
- }
26
- catch (e) {
27
- toolArgs = {};
21
+ // 只有耗时工具才创建"进行中"消息
22
+ if (isToolNeedTwoStepDisplay(toolCall.function.name)) {
23
+ const toolDisplay = formatToolCallMessage(toolCall);
24
+ let toolArgs;
25
+ try {
26
+ toolArgs = JSON.parse(toolCall.function.arguments);
27
+ }
28
+ catch (e) {
29
+ toolArgs = {};
30
+ }
31
+ uiMessages.push({
32
+ role: 'subagent',
33
+ content: `\x1b[38;2;184;122;206m⚇⚡ ${toolDisplay.toolName}\x1b[0m`,
34
+ streaming: false,
35
+ toolCall: {
36
+ name: toolCall.function.name,
37
+ arguments: toolArgs,
38
+ },
39
+ toolDisplay,
40
+ toolCallId: toolCall.id,
41
+ toolPending: false,
42
+ subAgentInternal: true,
43
+ });
28
44
  }
29
- uiMessages.push({
30
- role: 'subagent',
31
- content: `\x1b[38;2;184;122;206m⚇⚡ ${toolDisplay.toolName}\x1b[0m`,
32
- streaming: false,
33
- toolCall: {
34
- name: toolCall.function.name,
35
- arguments: toolArgs,
36
- },
37
- toolDisplay,
38
- toolCallId: toolCall.id,
39
- toolPending: false,
40
- subAgentInternal: true,
41
- });
42
45
  processedToolCalls.add(toolCall.id);
43
46
  }
44
47
  continue;
@@ -2,6 +2,7 @@ export interface SubAgent {
2
2
  id: string;
3
3
  name: string;
4
4
  description: string;
5
+ role?: string;
5
6
  tools: string[];
6
7
  createdAt: string;
7
8
  updatedAt: string;
@@ -20,13 +21,14 @@ export declare function getSubAgent(id: string): SubAgent | null;
20
21
  /**
21
22
  * Create a new sub-agent
22
23
  */
23
- export declare function createSubAgent(name: string, description: string, tools: string[]): SubAgent;
24
+ export declare function createSubAgent(name: string, description: string, tools: string[], role?: string): SubAgent;
24
25
  /**
25
26
  * Update an existing sub-agent
26
27
  */
27
28
  export declare function updateSubAgent(id: string, updates: {
28
29
  name?: string;
29
30
  description?: string;
31
+ role?: string;
30
32
  tools?: string[];
31
33
  }): SubAgent | null;
32
34
  /**
@@ -53,13 +53,14 @@ function saveSubAgents(agents) {
53
53
  /**
54
54
  * Create a new sub-agent
55
55
  */
56
- export function createSubAgent(name, description, tools) {
56
+ export function createSubAgent(name, description, tools, role) {
57
57
  const agents = getSubAgents();
58
58
  const now = new Date().toISOString();
59
59
  const newAgent = {
60
60
  id: generateId(),
61
61
  name,
62
62
  description,
63
+ role,
63
64
  tools,
64
65
  createdAt: now,
65
66
  updatedAt: now,
@@ -85,6 +86,7 @@ export function updateSubAgent(id, updates) {
85
86
  id: existingAgent.id,
86
87
  name: updates.name ?? existingAgent.name,
87
88
  description: updates.description ?? existingAgent.description,
89
+ role: updates.role ?? existingAgent.role,
88
90
  tools: updates.tools ?? existingAgent.tools,
89
91
  createdAt: existingAgent.createdAt,
90
92
  updatedAt: new Date().toISOString(),
@@ -50,10 +50,15 @@ export async function executeSubAgent(agentId, prompt, onMessage, abortSignal, r
50
50
  };
51
51
  }
52
52
  // Build conversation history for sub-agent
53
+ // Append role to prompt if configured
54
+ let finalPrompt = prompt;
55
+ if (agent.role) {
56
+ finalPrompt = `${prompt}\n\n${agent.role}`;
57
+ }
53
58
  const messages = [
54
59
  {
55
60
  role: 'user',
56
- content: prompt,
61
+ content: finalPrompt,
57
62
  },
58
63
  ];
59
64
  // Stream sub-agent execution
@@ -30,7 +30,14 @@ declare class VSCodeConnectionManager {
30
30
  private editorContext;
31
31
  private listeners;
32
32
  private currentWorkingDirectory;
33
+ private connectingPromise;
34
+ private connectionTimeout;
35
+ private readonly CONNECTION_TIMEOUT;
33
36
  start(): Promise<void>;
37
+ /**
38
+ * Clean up connection state and resources
39
+ */
40
+ private cleanupConnection;
34
41
  /**
35
42
  * Normalize path for cross-platform compatibility
36
43
  * - Converts Windows backslashes to forward slashes
@@ -89,15 +89,44 @@ class VSCodeConnectionManager {
89
89
  writable: true,
90
90
  value: process.cwd()
91
91
  });
92
+ // Connection state management
93
+ Object.defineProperty(this, "connectingPromise", {
94
+ enumerable: true,
95
+ configurable: true,
96
+ writable: true,
97
+ value: null
98
+ });
99
+ Object.defineProperty(this, "connectionTimeout", {
100
+ enumerable: true,
101
+ configurable: true,
102
+ writable: true,
103
+ value: null
104
+ });
105
+ Object.defineProperty(this, "CONNECTION_TIMEOUT", {
106
+ enumerable: true,
107
+ configurable: true,
108
+ writable: true,
109
+ value: 10000
110
+ }); // 10 seconds timeout for initial connection
92
111
  }
93
112
  async start() {
94
113
  // If already connected, just return success
95
114
  if (this.client?.readyState === WebSocket.OPEN) {
96
115
  return Promise.resolve();
97
116
  }
117
+ // If already connecting, return the existing promise to avoid duplicate connections
118
+ if (this.connectingPromise) {
119
+ return this.connectingPromise;
120
+ }
98
121
  // Try to find the correct port for this workspace
99
122
  const targetPort = this.findPortForWorkspace();
100
- return new Promise((resolve, reject) => {
123
+ // Create a new connection promise and store it
124
+ this.connectingPromise = new Promise((resolve, reject) => {
125
+ // Set connection timeout
126
+ this.connectionTimeout = setTimeout(() => {
127
+ this.cleanupConnection();
128
+ reject(new Error('Connection timeout after 10 seconds'));
129
+ }, this.CONNECTION_TIMEOUT);
101
130
  const tryConnect = (port) => {
102
131
  // Check both VSCode and JetBrains port ranges
103
132
  if (port > this.VSCODE_MAX_PORT && port < this.JETBRAINS_BASE_PORT) {
@@ -106,6 +135,7 @@ class VSCodeConnectionManager {
106
135
  return;
107
136
  }
108
137
  if (port > this.JETBRAINS_MAX_PORT) {
138
+ this.cleanupConnection();
109
139
  reject(new Error(`Failed to connect: no IDE server found on ports ${this.VSCODE_BASE_PORT}-${this.VSCODE_MAX_PORT} or ${this.JETBRAINS_BASE_PORT}-${this.JETBRAINS_MAX_PORT}`));
110
140
  return;
111
141
  }
@@ -115,6 +145,12 @@ class VSCodeConnectionManager {
115
145
  // Reset reconnect attempts on successful connection
116
146
  this.reconnectAttempts = 0;
117
147
  this.port = port;
148
+ // Clear connection state
149
+ if (this.connectionTimeout) {
150
+ clearTimeout(this.connectionTimeout);
151
+ this.connectionTimeout = null;
152
+ }
153
+ this.connectingPromise = null;
118
154
  resolve();
119
155
  });
120
156
  this.client.on('message', message => {
@@ -148,6 +184,34 @@ class VSCodeConnectionManager {
148
184
  };
149
185
  tryConnect(targetPort);
150
186
  });
187
+ // Return the promise and clean up state when it completes or fails
188
+ return this.connectingPromise.finally(() => {
189
+ this.connectingPromise = null;
190
+ if (this.connectionTimeout) {
191
+ clearTimeout(this.connectionTimeout);
192
+ this.connectionTimeout = null;
193
+ }
194
+ });
195
+ }
196
+ /**
197
+ * Clean up connection state and resources
198
+ */
199
+ cleanupConnection() {
200
+ this.connectingPromise = null;
201
+ if (this.connectionTimeout) {
202
+ clearTimeout(this.connectionTimeout);
203
+ this.connectionTimeout = null;
204
+ }
205
+ if (this.client) {
206
+ try {
207
+ this.client.removeAllListeners();
208
+ this.client.close();
209
+ }
210
+ catch (error) {
211
+ // Ignore errors during cleanup
212
+ }
213
+ this.client = null;
214
+ }
151
215
  }
152
216
  /**
153
217
  * Normalize path for cross-platform compatibility
@@ -228,8 +292,21 @@ class VSCodeConnectionManager {
228
292
  clearTimeout(this.reconnectTimer);
229
293
  this.reconnectTimer = null;
230
294
  }
295
+ // Clear connection timeout
296
+ if (this.connectionTimeout) {
297
+ clearTimeout(this.connectionTimeout);
298
+ this.connectionTimeout = null;
299
+ }
300
+ // Clear connecting promise
301
+ this.connectingPromise = null;
231
302
  if (this.client) {
232
- this.client.close();
303
+ try {
304
+ this.client.removeAllListeners();
305
+ this.client.close();
306
+ }
307
+ catch (error) {
308
+ // Ignore errors during cleanup
309
+ }
233
310
  this.client = null;
234
311
  }
235
312
  this.reconnectAttempts = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.3.28",
3
+ "version": "0.3.29",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {