snow-ai 0.2.19 → 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.
- package/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +0 -5
- package/dist/cli.js +25 -0
- package/dist/hooks/useCommandHandler.js +24 -16
- package/dist/hooks/useConversation.js +84 -136
- package/dist/hooks/useKeyboardInput.js +9 -0
- package/dist/hooks/useVSCodeState.js +5 -5
- package/dist/mcp/filesystem.d.ts +0 -4
- package/dist/mcp/filesystem.js +68 -54
- package/dist/ui/components/FileList.js +1 -1
- package/dist/ui/components/MarkdownRenderer.js +65 -16
- package/dist/ui/pages/ChatScreen.js +55 -13
- package/dist/utils/commandExecutor.d.ts +1 -0
- package/dist/utils/commands/ide.js +8 -20
- package/dist/utils/vscodeConnection.d.ts +23 -9
- package/dist/utils/vscodeConnection.js +179 -84
- package/package.json +1 -1
|
@@ -16,34 +16,48 @@ interface Diagnostic {
|
|
|
16
16
|
code?: string | number;
|
|
17
17
|
}
|
|
18
18
|
declare class VSCodeConnectionManager {
|
|
19
|
-
private server;
|
|
20
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
|
-
|
|
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
|
|
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[]>;
|
|
39
57
|
/**
|
|
40
|
-
*
|
|
41
|
-
* @param filePath - The file path for the diff
|
|
42
|
-
* @param oldContent - Original content with line numbers
|
|
43
|
-
* @param newContent - Modified content with line numbers
|
|
44
|
-
* @returns Promise that resolves with user's approval response
|
|
58
|
+
* Reset reconnection attempts (e.g., when user manually triggers reconnect)
|
|
45
59
|
*/
|
|
46
|
-
|
|
60
|
+
resetReconnectAttempts(): void;
|
|
47
61
|
}
|
|
48
62
|
export declare const vscodeConnection: VSCodeConnectionManager;
|
|
49
63
|
export type { EditorContext, Diagnostic };
|
|
@@ -1,18 +1,70 @@
|
|
|
1
|
-
import {
|
|
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, "
|
|
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, "
|
|
13
|
+
Object.defineProperty(this, "reconnectTimer", {
|
|
11
14
|
enumerable: true,
|
|
12
15
|
configurable: true,
|
|
13
16
|
writable: true,
|
|
14
17
|
value: null
|
|
15
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
|
|
67
|
+
});
|
|
16
68
|
Object.defineProperty(this, "port", {
|
|
17
69
|
enumerable: true,
|
|
18
70
|
configurable: true,
|
|
@@ -31,66 +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
|
|
37
|
-
if (this.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
this.
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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 => {
|
|
50
121
|
try {
|
|
51
122
|
const data = JSON.parse(message.toString());
|
|
52
|
-
|
|
123
|
+
// Filter messages by workspace folder
|
|
124
|
+
if (this.shouldHandleMessage(data)) {
|
|
125
|
+
this.handleMessage(data);
|
|
126
|
+
}
|
|
53
127
|
}
|
|
54
128
|
catch (error) {
|
|
55
129
|
// Ignore invalid JSON
|
|
56
130
|
}
|
|
57
131
|
});
|
|
58
|
-
|
|
59
|
-
|
|
132
|
+
this.client.on('close', () => {
|
|
133
|
+
this.client = null;
|
|
134
|
+
this.scheduleReconnect();
|
|
135
|
+
});
|
|
136
|
+
this.client.on('error', _error => {
|
|
137
|
+
// On initial connection, try next port
|
|
138
|
+
if (this.reconnectAttempts === 0) {
|
|
60
139
|
this.client = null;
|
|
140
|
+
tryConnect(port + 1);
|
|
61
141
|
}
|
|
142
|
+
// For reconnections, silently handle and let close event trigger reconnect
|
|
62
143
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
70
|
-
this.server.on('error', (error) => {
|
|
71
|
-
reject(error);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
reject(error);
|
|
76
|
-
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
tryConnect(port + 1);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
tryConnect(targetPort);
|
|
77
150
|
});
|
|
78
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
|
+
}
|
|
79
211
|
stop() {
|
|
212
|
+
if (this.reconnectTimer) {
|
|
213
|
+
clearTimeout(this.reconnectTimer);
|
|
214
|
+
this.reconnectTimer = null;
|
|
215
|
+
}
|
|
80
216
|
if (this.client) {
|
|
81
217
|
this.client.close();
|
|
82
218
|
this.client = null;
|
|
83
219
|
}
|
|
84
|
-
|
|
85
|
-
this.server.close();
|
|
86
|
-
this.server = null;
|
|
87
|
-
}
|
|
220
|
+
this.reconnectAttempts = 0;
|
|
88
221
|
}
|
|
89
222
|
isConnected() {
|
|
90
|
-
return this.client
|
|
223
|
+
return this.client?.readyState === WebSocket.OPEN;
|
|
91
224
|
}
|
|
92
|
-
|
|
93
|
-
return this.
|
|
225
|
+
isClientRunning() {
|
|
226
|
+
return this.client !== null;
|
|
94
227
|
}
|
|
95
228
|
getContext() {
|
|
96
229
|
return { ...this.editorContext };
|
|
@@ -98,7 +231,7 @@ class VSCodeConnectionManager {
|
|
|
98
231
|
onContextUpdate(listener) {
|
|
99
232
|
this.listeners.push(listener);
|
|
100
233
|
return () => {
|
|
101
|
-
this.listeners = this.listeners.filter(
|
|
234
|
+
this.listeners = this.listeners.filter(l => l !== listener);
|
|
102
235
|
};
|
|
103
236
|
}
|
|
104
237
|
handleMessage(data) {
|
|
@@ -107,7 +240,7 @@ class VSCodeConnectionManager {
|
|
|
107
240
|
activeFile: data.activeFile,
|
|
108
241
|
selectedText: data.selectedText,
|
|
109
242
|
cursorPosition: data.cursorPosition,
|
|
110
|
-
workspaceFolder: data.workspaceFolder
|
|
243
|
+
workspaceFolder: data.workspaceFolder,
|
|
111
244
|
};
|
|
112
245
|
this.notifyListeners();
|
|
113
246
|
}
|
|
@@ -121,12 +254,12 @@ class VSCodeConnectionManager {
|
|
|
121
254
|
return this.port;
|
|
122
255
|
}
|
|
123
256
|
/**
|
|
124
|
-
* Request diagnostics for a specific file from
|
|
257
|
+
* Request diagnostics for a specific file from IDE
|
|
125
258
|
* @param filePath - The file path to get diagnostics for
|
|
126
259
|
* @returns Promise that resolves with diagnostics array
|
|
127
260
|
*/
|
|
128
261
|
async requestDiagnostics(filePath) {
|
|
129
|
-
return new Promise(
|
|
262
|
+
return new Promise(resolve => {
|
|
130
263
|
if (!this.client || this.client.readyState !== WebSocket.OPEN) {
|
|
131
264
|
resolve([]); // Return empty array if not connected
|
|
132
265
|
return;
|
|
@@ -156,53 +289,15 @@ class VSCodeConnectionManager {
|
|
|
156
289
|
this.client.send(JSON.stringify({
|
|
157
290
|
type: 'getDiagnostics',
|
|
158
291
|
requestId,
|
|
159
|
-
filePath
|
|
292
|
+
filePath,
|
|
160
293
|
}));
|
|
161
294
|
});
|
|
162
295
|
}
|
|
163
296
|
/**
|
|
164
|
-
*
|
|
165
|
-
* @param filePath - The file path for the diff
|
|
166
|
-
* @param oldContent - Original content with line numbers
|
|
167
|
-
* @param newContent - Modified content with line numbers
|
|
168
|
-
* @returns Promise that resolves with user's approval response
|
|
297
|
+
* Reset reconnection attempts (e.g., when user manually triggers reconnect)
|
|
169
298
|
*/
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!this.client || this.client.readyState !== WebSocket.OPEN) {
|
|
173
|
-
resolve('approve'); // If not connected, default to approve (fallback to CLI confirmation)
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
const requestId = Math.random().toString(36).substring(7);
|
|
177
|
-
const timeout = setTimeout(() => {
|
|
178
|
-
cleanup();
|
|
179
|
-
resolve('approve'); // Timeout, default to approve
|
|
180
|
-
}, 30000); // 30 second timeout for user decision
|
|
181
|
-
const handler = (message) => {
|
|
182
|
-
try {
|
|
183
|
-
const data = JSON.parse(message.toString());
|
|
184
|
-
if (data.type === 'diffApplyResult' && data.requestId === requestId) {
|
|
185
|
-
cleanup();
|
|
186
|
-
resolve(data.result || 'approve');
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
// Ignore invalid JSON
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
const cleanup = () => {
|
|
194
|
-
clearTimeout(timeout);
|
|
195
|
-
this.client?.removeListener('message', handler);
|
|
196
|
-
};
|
|
197
|
-
this.client.on('message', handler);
|
|
198
|
-
this.client.send(JSON.stringify({
|
|
199
|
-
type: 'diffApply',
|
|
200
|
-
requestId,
|
|
201
|
-
filePath,
|
|
202
|
-
oldContent,
|
|
203
|
-
newContent
|
|
204
|
-
}));
|
|
205
|
-
});
|
|
299
|
+
resetReconnectAttempts() {
|
|
300
|
+
this.reconnectAttempts = 0;
|
|
206
301
|
}
|
|
207
302
|
}
|
|
208
303
|
export const vscodeConnection = new VSCodeConnectionManager();
|