snow-ai 0.2.20 → 0.2.21

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.
@@ -34,18 +34,18 @@ export function useVSCodeState() {
34
34
  if (vscodeConnectionStatus !== 'connecting') {
35
35
  return;
36
36
  }
37
- // Set timeout for connecting state (30 seconds to allow for VSCode extension reconnection)
37
+ // Set timeout for connecting state (30 seconds to allow for IDE plugin reconnection)
38
38
  const connectingTimeout = setTimeout(() => {
39
39
  const isConnected = vscodeConnection.isConnected();
40
- const isServerRunning = vscodeConnection.isServerRunning();
40
+ const isClientRunning = vscodeConnection.isClientRunning();
41
41
  // Only set error if still not connected after timeout
42
42
  if (!isConnected) {
43
- if (isServerRunning) {
44
- // Server is running but no connection - show error with helpful message
43
+ if (isClientRunning) {
44
+ // Client is running but no connection - show error with helpful message
45
45
  setVscodeConnectionStatus('error');
46
46
  }
47
47
  else {
48
- // Server not running - go back to disconnected
48
+ // Client not running - go back to disconnected
49
49
  setVscodeConnectionStatus('disconnected');
50
50
  }
51
51
  }
@@ -177,7 +177,7 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
177
177
  return (React.createElement(Box, { paddingX: 1, marginTop: 1, flexDirection: "column" },
178
178
  React.createElement(Box, { marginBottom: 1 },
179
179
  React.createElement(Text, { color: "blue", bold: true },
180
- "\uD83D\uDDD0 Files",
180
+ "\u2261 Files",
181
181
  ' ',
182
182
  allFilteredFiles.length > effectiveMaxItems &&
183
183
  `(${selectedIndex + 1}/${allFilteredFiles.length})`)),
@@ -691,12 +691,12 @@ export default function ChatScreen({}) {
691
691
  "\u25CF",
692
692
  ' ',
693
693
  vscodeState.vscodeConnectionStatus === 'connecting'
694
- ? 'Waiting for VSCode extension to connect...'
694
+ ? 'Connecting to IDE...'
695
695
  : vscodeState.vscodeConnectionStatus === 'connected'
696
- ? 'VSCode Connected'
696
+ ? 'IDE Connected'
697
697
  : vscodeState.vscodeConnectionStatus === 'error'
698
- ? 'Connection Failed - Make sure Snow CLI extension is installed and active in VSCode'
699
- : 'VSCode',
698
+ ? 'Connection Failed - Make sure Snow CLI plugin is installed and active in your IDE'
699
+ : 'IDE',
700
700
  vscodeState.vscodeConnectionStatus === 'connected' &&
701
701
  vscodeState.editorContext.activeFile &&
702
702
  ` | ${vscodeState.editorContext.activeFile}`,
@@ -3,42 +3,30 @@ import { vscodeConnection } from '../vscodeConnection.js';
3
3
  // IDE connection command handler
4
4
  registerCommand('ide', {
5
5
  execute: async () => {
6
- // Check if server is already running in THIS process (allow multiple clients to connect)
7
- if (vscodeConnection.isServerRunning()) {
8
- const isConnected = vscodeConnection.isConnected();
6
+ // Check if already connected to IDE plugin
7
+ if (vscodeConnection.isConnected()) {
9
8
  return {
10
9
  success: true,
11
10
  action: 'info',
12
- alreadyConnected: isConnected, // Add this flag to indicate connection status
13
- message: isConnected
14
- ? `Connected to VSCode (server running on port ${vscodeConnection.getPort()})`
15
- : `VSCode connection server is running on port ${vscodeConnection.getPort()}\nWaiting for VSCode extension to connect...`
11
+ alreadyConnected: true,
12
+ message: `Already connected to IDE (port ${vscodeConnection.getPort()})`
16
13
  };
17
14
  }
18
- // Start the server
15
+ // Try to connect to IDE plugin server
19
16
  try {
20
17
  await vscodeConnection.start();
21
18
  return {
22
19
  success: true,
23
20
  action: 'info',
24
- message: `VSCode connection server started on port ${vscodeConnection.getPort()}\nWaiting for VSCode extension to connect...`
21
+ message: `Connected to IDE on port ${vscodeConnection.getPort()}\nMake sure your IDE plugin (VSCode/JetBrains) is active and running.`
25
22
  };
26
23
  }
27
24
  catch (error) {
28
- // Handle EADDRINUSE error specifically
29
- if (error instanceof Error && 'code' in error && error.code === 'EADDRINUSE') {
30
- // Port is in use by another Snow CLI process - this is OK!
31
- // Return success and indicate already connected
32
- return {
33
- success: true,
34
- action: 'info',
35
- alreadyConnected: true, // Treat as already connected since another terminal has the connection
36
- message: `VSCode connection is already active in another Snow CLI terminal.\nNo additional connection needed - context will be shared through VSCode extension.`
37
- };
38
- }
39
25
  return {
40
26
  success: false,
41
- message: error instanceof Error ? error.message : 'Failed to start IDE connection'
27
+ message: error instanceof Error
28
+ ? `Failed to connect to IDE: ${error.message}\nMake sure your IDE plugin is installed and active.`
29
+ : 'Failed to connect to IDE. Make sure your IDE plugin is installed and active.'
42
30
  };
43
31
  }
44
32
  }
@@ -16,26 +16,48 @@ interface Diagnostic {
16
16
  code?: string | number;
17
17
  }
18
18
  declare class VSCodeConnectionManager {
19
- private server;
20
- private clients;
19
+ private client;
20
+ private reconnectTimer;
21
+ private reconnectAttempts;
22
+ private readonly MAX_RECONNECT_ATTEMPTS;
23
+ private readonly BASE_RECONNECT_DELAY;
24
+ private readonly MAX_RECONNECT_DELAY;
25
+ private readonly VSCODE_BASE_PORT;
26
+ private readonly VSCODE_MAX_PORT;
27
+ private readonly JETBRAINS_BASE_PORT;
28
+ private readonly JETBRAINS_MAX_PORT;
21
29
  private port;
22
30
  private editorContext;
23
31
  private listeners;
32
+ private currentWorkingDirectory;
24
33
  start(): Promise<void>;
34
+ /**
35
+ * Find the correct port for the current workspace
36
+ */
37
+ private findPortForWorkspace;
38
+ /**
39
+ * Check if we should handle this message based on workspace folder
40
+ */
41
+ private shouldHandleMessage;
42
+ private scheduleReconnect;
25
43
  stop(): void;
26
44
  isConnected(): boolean;
27
- isServerRunning(): boolean;
45
+ isClientRunning(): boolean;
28
46
  getContext(): EditorContext;
29
47
  onContextUpdate(listener: (context: EditorContext) => void): () => void;
30
48
  private handleMessage;
31
49
  private notifyListeners;
32
50
  getPort(): number;
33
51
  /**
34
- * Request diagnostics for a specific file from VS Code
52
+ * Request diagnostics for a specific file from IDE
35
53
  * @param filePath - The file path to get diagnostics for
36
54
  * @returns Promise that resolves with diagnostics array
37
55
  */
38
56
  requestDiagnostics(filePath: string): Promise<Diagnostic[]>;
57
+ /**
58
+ * Reset reconnection attempts (e.g., when user manually triggers reconnect)
59
+ */
60
+ resetReconnectAttempts(): void;
39
61
  }
40
62
  export declare const vscodeConnection: VSCodeConnectionManager;
41
63
  export type { EditorContext, Diagnostic };
@@ -1,17 +1,69 @@
1
- import { WebSocketServer, WebSocket } from 'ws';
1
+ import { WebSocket } from 'ws';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
2
5
  class VSCodeConnectionManager {
3
6
  constructor() {
4
- Object.defineProperty(this, "server", {
7
+ Object.defineProperty(this, "client", {
5
8
  enumerable: true,
6
9
  configurable: true,
7
10
  writable: true,
8
11
  value: null
9
12
  });
10
- Object.defineProperty(this, "clients", {
13
+ Object.defineProperty(this, "reconnectTimer", {
11
14
  enumerable: true,
12
15
  configurable: true,
13
16
  writable: true,
14
- value: new Set()
17
+ value: null
18
+ });
19
+ Object.defineProperty(this, "reconnectAttempts", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: 0
24
+ });
25
+ Object.defineProperty(this, "MAX_RECONNECT_ATTEMPTS", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: 10
30
+ });
31
+ Object.defineProperty(this, "BASE_RECONNECT_DELAY", {
32
+ enumerable: true,
33
+ configurable: true,
34
+ writable: true,
35
+ value: 2000
36
+ }); // 2 seconds
37
+ Object.defineProperty(this, "MAX_RECONNECT_DELAY", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: 30000
42
+ }); // 30 seconds
43
+ // Port ranges: VSCode uses 9527-9537, JetBrains uses 9538-9548
44
+ Object.defineProperty(this, "VSCODE_BASE_PORT", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: 9527
49
+ });
50
+ Object.defineProperty(this, "VSCODE_MAX_PORT", {
51
+ enumerable: true,
52
+ configurable: true,
53
+ writable: true,
54
+ value: 9537
55
+ });
56
+ Object.defineProperty(this, "JETBRAINS_BASE_PORT", {
57
+ enumerable: true,
58
+ configurable: true,
59
+ writable: true,
60
+ value: 9538
61
+ });
62
+ Object.defineProperty(this, "JETBRAINS_MAX_PORT", {
63
+ enumerable: true,
64
+ configurable: true,
65
+ writable: true,
66
+ value: 9548
15
67
  });
16
68
  Object.defineProperty(this, "port", {
17
69
  enumerable: true,
@@ -31,64 +83,147 @@ class VSCodeConnectionManager {
31
83
  writable: true,
32
84
  value: []
33
85
  });
86
+ Object.defineProperty(this, "currentWorkingDirectory", {
87
+ enumerable: true,
88
+ configurable: true,
89
+ writable: true,
90
+ value: process.cwd()
91
+ });
34
92
  }
35
93
  async start() {
36
- // If already running, just return success
37
- if (this.server) {
94
+ // If already connected, just return success
95
+ if (this.client?.readyState === WebSocket.OPEN) {
38
96
  return Promise.resolve();
39
97
  }
98
+ // Try to find the correct port for this workspace
99
+ const targetPort = this.findPortForWorkspace();
40
100
  return new Promise((resolve, reject) => {
41
- try {
42
- this.server = new WebSocketServer({ port: this.port });
43
- this.server.on('connection', ws => {
44
- // Add new client to the set (allow multiple connections)
45
- this.clients.add(ws);
46
- ws.on('message', message => {
101
+ const tryConnect = (port) => {
102
+ // Check both VSCode and JetBrains port ranges
103
+ if (port > this.VSCODE_MAX_PORT && port < this.JETBRAINS_BASE_PORT) {
104
+ // Jump from VSCode range to JetBrains range
105
+ tryConnect(this.JETBRAINS_BASE_PORT);
106
+ return;
107
+ }
108
+ if (port > this.JETBRAINS_MAX_PORT) {
109
+ 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
+ return;
111
+ }
112
+ try {
113
+ this.client = new WebSocket(`ws://localhost:${port}`);
114
+ this.client.on('open', () => {
115
+ // Reset reconnect attempts on successful connection
116
+ this.reconnectAttempts = 0;
117
+ this.port = port;
118
+ resolve();
119
+ });
120
+ this.client.on('message', message => {
47
121
  try {
48
122
  const data = JSON.parse(message.toString());
49
- this.handleMessage(data);
123
+ // Filter messages by workspace folder
124
+ if (this.shouldHandleMessage(data)) {
125
+ this.handleMessage(data);
126
+ }
50
127
  }
51
128
  catch (error) {
52
129
  // Ignore invalid JSON
53
130
  }
54
131
  });
55
- ws.on('close', () => {
56
- this.clients.delete(ws);
132
+ this.client.on('close', () => {
133
+ this.client = null;
134
+ this.scheduleReconnect();
57
135
  });
58
- ws.on('error', () => {
59
- // Silently handle errors
60
- this.clients.delete(ws);
136
+ this.client.on('error', _error => {
137
+ // On initial connection, try next port
138
+ if (this.reconnectAttempts === 0) {
139
+ this.client = null;
140
+ tryConnect(port + 1);
141
+ }
142
+ // For reconnections, silently handle and let close event trigger reconnect
61
143
  });
62
- });
63
- this.server.on('listening', () => {
64
- resolve();
65
- });
66
- this.server.on('error', error => {
67
- reject(error);
68
- });
69
- }
70
- catch (error) {
71
- reject(error);
72
- }
144
+ }
145
+ catch (error) {
146
+ tryConnect(port + 1);
147
+ }
148
+ };
149
+ tryConnect(targetPort);
73
150
  });
74
151
  }
152
+ /**
153
+ * Find the correct port for the current workspace
154
+ */
155
+ findPortForWorkspace() {
156
+ try {
157
+ const portInfoPath = path.join(os.tmpdir(), 'snow-cli-ports.json');
158
+ if (fs.existsSync(portInfoPath)) {
159
+ const portInfo = JSON.parse(fs.readFileSync(portInfoPath, 'utf8'));
160
+ // Try to match current working directory
161
+ const cwd = this.currentWorkingDirectory;
162
+ // Direct match
163
+ if (portInfo[cwd]) {
164
+ return portInfo[cwd];
165
+ }
166
+ // Check if cwd is within any of the workspace folders
167
+ for (const [workspace, port] of Object.entries(portInfo)) {
168
+ if (cwd.startsWith(workspace)) {
169
+ return port;
170
+ }
171
+ }
172
+ }
173
+ }
174
+ catch (error) {
175
+ // Ignore errors, will fall back to VSCODE_BASE_PORT
176
+ }
177
+ // Start with VSCode port range by default
178
+ return this.VSCODE_BASE_PORT;
179
+ }
180
+ /**
181
+ * Check if we should handle this message based on workspace folder
182
+ */
183
+ shouldHandleMessage(data) {
184
+ // If no workspace folder in message, accept it (backwards compatibility)
185
+ if (!data.workspaceFolder) {
186
+ return true;
187
+ }
188
+ // Check if message's workspace folder matches our current working directory
189
+ const cwd = this.currentWorkingDirectory;
190
+ // Bidirectional check: either cwd is within IDE workspace, or IDE workspace is within cwd
191
+ const cwdInWorkspace = cwd.startsWith(data.workspaceFolder);
192
+ const workspaceInCwd = data.workspaceFolder.startsWith(cwd);
193
+ const matches = cwdInWorkspace || workspaceInCwd;
194
+ return matches;
195
+ }
196
+ scheduleReconnect() {
197
+ if (this.reconnectTimer) {
198
+ clearTimeout(this.reconnectTimer);
199
+ }
200
+ this.reconnectAttempts++;
201
+ if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
202
+ return;
203
+ }
204
+ const delay = Math.min(this.BASE_RECONNECT_DELAY * Math.pow(1.5, this.reconnectAttempts - 1), this.MAX_RECONNECT_DELAY);
205
+ this.reconnectTimer = setTimeout(() => {
206
+ this.start().catch(() => {
207
+ // Silently handle reconnection failures
208
+ });
209
+ }, delay);
210
+ }
75
211
  stop() {
76
- // Close all connected clients
77
- for (const client of this.clients) {
78
- client.close();
212
+ if (this.reconnectTimer) {
213
+ clearTimeout(this.reconnectTimer);
214
+ this.reconnectTimer = null;
79
215
  }
80
- this.clients.clear();
81
- if (this.server) {
82
- this.server.close();
83
- this.server = null;
216
+ if (this.client) {
217
+ this.client.close();
218
+ this.client = null;
84
219
  }
220
+ this.reconnectAttempts = 0;
85
221
  }
86
222
  isConnected() {
87
- return (this.clients.size > 0 &&
88
- Array.from(this.clients).some(client => client.readyState === WebSocket.OPEN));
223
+ return this.client?.readyState === WebSocket.OPEN;
89
224
  }
90
- isServerRunning() {
91
- return this.server !== null;
225
+ isClientRunning() {
226
+ return this.client !== null;
92
227
  }
93
228
  getContext() {
94
229
  return { ...this.editorContext };
@@ -119,15 +254,13 @@ class VSCodeConnectionManager {
119
254
  return this.port;
120
255
  }
121
256
  /**
122
- * Request diagnostics for a specific file from VS Code
257
+ * Request diagnostics for a specific file from IDE
123
258
  * @param filePath - The file path to get diagnostics for
124
259
  * @returns Promise that resolves with diagnostics array
125
260
  */
126
261
  async requestDiagnostics(filePath) {
127
262
  return new Promise(resolve => {
128
- // Get first connected client
129
- const client = Array.from(this.clients).find(c => c.readyState === WebSocket.OPEN);
130
- if (!client) {
263
+ if (!this.client || this.client.readyState !== WebSocket.OPEN) {
131
264
  resolve([]); // Return empty array if not connected
132
265
  return;
133
266
  }
@@ -150,15 +283,21 @@ class VSCodeConnectionManager {
150
283
  };
151
284
  const cleanup = () => {
152
285
  clearTimeout(timeout);
153
- client?.removeListener('message', handler);
286
+ this.client?.removeListener('message', handler);
154
287
  };
155
- client.on('message', handler);
156
- client.send(JSON.stringify({
288
+ this.client.on('message', handler);
289
+ this.client.send(JSON.stringify({
157
290
  type: 'getDiagnostics',
158
291
  requestId,
159
292
  filePath,
160
293
  }));
161
294
  });
162
295
  }
296
+ /**
297
+ * Reset reconnection attempts (e.g., when user manually triggers reconnect)
298
+ */
299
+ resetReconnectAttempts() {
300
+ this.reconnectAttempts = 0;
301
+ }
163
302
  }
164
303
  export const vscodeConnection = new VSCodeConnectionManager();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {