@wonderwhy-er/desktop-commander 0.2.5 → 0.2.7
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/index-dxt.js +10 -47
- package/dist/server.js +6 -0
- package/dist/tools/filesystem.js +37 -4
- package/dist/utils/usageTracker.js +8 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/dist/REPLSessionManager.d.ts +0 -109
- package/dist/REPLSessionManager.js +0 -364
- package/dist/REPLSessionManager.test.d.ts +0 -1
- package/dist/REPLSessionManager.test.js +0 -75
- package/dist/client/replClient.d.ts +0 -63
- package/dist/client/replClient.js +0 -217
- package/dist/client/sshClient.d.ts +0 -82
- package/dist/client/sshClient.js +0 -200
- package/dist/command-manager.js.map +0 -1
- package/dist/config-manager.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/custom-stdio.js.map +0 -1
- package/dist/error-handlers.js.map +0 -1
- package/dist/handlers/command-handlers.d.ts +0 -13
- package/dist/handlers/command-handlers.js +0 -43
- package/dist/handlers/edit-search-handlers.js.map +0 -1
- package/dist/handlers/filesystem-handlers.js.map +0 -1
- package/dist/handlers/fuzzy-search-log-handlers.d.ts +0 -13
- package/dist/handlers/fuzzy-search-log-handlers.js +0 -179
- package/dist/handlers/index.js.map +0 -1
- package/dist/handlers/process-handlers.js.map +0 -1
- package/dist/handlers/repl-handlers.d.ts +0 -21
- package/dist/handlers/repl-handlers.js +0 -37
- package/dist/handlers/replCommandHandler.d.ts +0 -125
- package/dist/handlers/replCommandHandler.js +0 -255
- package/dist/handlers/replCommandHandler.test.d.ts +0 -1
- package/dist/handlers/replCommandHandler.test.js +0 -103
- package/dist/handlers/terminal-handlers.js.map +0 -1
- package/dist/index-with-startup-detection.d.ts +0 -5
- package/dist/index-with-startup-detection.js +0 -180
- package/dist/index.js.map +0 -1
- package/dist/logging.d.ts +0 -2
- package/dist/logging.js +0 -28
- package/dist/polyform-license-src/edit/edit.d.ts +0 -15
- package/dist/polyform-license-src/edit/edit.js +0 -163
- package/dist/polyform-license-src/edit/fuzzySearch.d.ts +0 -30
- package/dist/polyform-license-src/edit/fuzzySearch.js +0 -121
- package/dist/polyform-license-src/edit/handlers.d.ts +0 -16
- package/dist/polyform-license-src/edit/handlers.js +0 -24
- package/dist/polyform-license-src/edit/index.d.ts +0 -12
- package/dist/polyform-license-src/edit/index.js +0 -13
- package/dist/polyform-license-src/edit/schemas.d.ts +0 -25
- package/dist/polyform-license-src/edit/schemas.js +0 -16
- package/dist/polyform-license-src/index.d.ts +0 -9
- package/dist/polyform-license-src/index.js +0 -10
- package/dist/repl-manager.d.ts +0 -73
- package/dist/repl-manager.js +0 -407
- package/dist/replIntegration.d.ts +0 -14
- package/dist/replIntegration.js +0 -27
- package/dist/sandbox/index.d.ts +0 -9
- package/dist/sandbox/index.js +0 -50
- package/dist/sandbox/mac-sandbox.d.ts +0 -19
- package/dist/sandbox/mac-sandbox.js +0 -174
- package/dist/server.js.map +0 -1
- package/dist/setup.log +0 -32
- package/dist/terminal-manager.js.map +0 -1
- package/dist/tools/client.d.ts +0 -10
- package/dist/tools/client.js +0 -13
- package/dist/tools/command-block.d.ts +0 -18
- package/dist/tools/command-block.js +0 -62
- package/dist/tools/config.js.map +0 -1
- package/dist/tools/debug-path.d.ts +0 -1
- package/dist/tools/debug-path.js +0 -44
- package/dist/tools/edit.js.map +0 -1
- package/dist/tools/enhanced-read-output.js +0 -69
- package/dist/tools/enhanced-send-input.js +0 -111
- package/dist/tools/environment.d.ts +0 -55
- package/dist/tools/environment.js +0 -65
- package/dist/tools/execute.d.ts +0 -10
- package/dist/tools/execute.js +0 -158
- package/dist/tools/execute.js.map +0 -1
- package/dist/tools/filesystem-fixed.d.ts +0 -22
- package/dist/tools/filesystem-fixed.js +0 -176
- package/dist/tools/filesystem.js.map +0 -1
- package/dist/tools/fuzzySearch.js.map +0 -1
- package/dist/tools/mime-types.js.map +0 -1
- package/dist/tools/pdf-reader.d.ts +0 -13
- package/dist/tools/pdf-reader.js +0 -214
- package/dist/tools/process.js.map +0 -1
- package/dist/tools/progress.d.ts +0 -20
- package/dist/tools/progress.js +0 -59
- package/dist/tools/repl.d.ts +0 -21
- package/dist/tools/repl.js +0 -217
- package/dist/tools/schemas.js.map +0 -1
- package/dist/tools/search.js.map +0 -1
- package/dist/tools/send-input.d.ts +0 -2
- package/dist/tools/send-input.js +0 -45
- package/dist/types.js.map +0 -1
- package/dist/utils/capture.js.map +0 -1
- package/dist/utils/early-logger.d.ts +0 -4
- package/dist/utils/early-logger.js +0 -35
- package/dist/utils/fuzzySearchLogger.js.map +0 -1
- package/dist/utils/lineEndingHandler.js.map +0 -1
- package/dist/utils/lineEndingHandler_optimized.d.ts +0 -21
- package/dist/utils/lineEndingHandler_optimized.js +0 -77
- package/dist/utils/mcp-logger.d.ts +0 -30
- package/dist/utils/mcp-logger.js +0 -59
- package/dist/utils/smithery-detector.d.ts +0 -94
- package/dist/utils/smithery-detector.js +0 -292
- package/dist/utils/startup-detector.d.ts +0 -65
- package/dist/utils/startup-detector.js +0 -390
- package/dist/utils/trackTools.js.map +0 -1
- package/dist/utils/withTimeout.js.map +0 -1
- package/dist/utils.d.ts +0 -26
- package/dist/utils.js +0 -227
- package/dist/version.js.map +0 -1
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import * as os from 'os';
|
|
2
|
-
export class REPLSessionManager {
|
|
3
|
-
constructor(terminalManager) {
|
|
4
|
-
this.sessions = new Map();
|
|
5
|
-
this.terminalManager = terminalManager;
|
|
6
|
-
this.defaultPromptPatterns = {
|
|
7
|
-
python: /^(>>>|\.\.\.) /m,
|
|
8
|
-
node: /> $/m,
|
|
9
|
-
bash: /[\w\d\-_]+@[\w\d\-_]+:.*[$#] $/m,
|
|
10
|
-
ssh: /[\w\d\-_]+@[\w\d\-_]+:.*[$#] $/m
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Create a new SSH session
|
|
15
|
-
* @param host - SSH host to connect to
|
|
16
|
-
* @param options - SSH connection options
|
|
17
|
-
* @returns PID of the created SSH session
|
|
18
|
-
*/
|
|
19
|
-
async createSSHSession(host, options = {}) {
|
|
20
|
-
if (!host) {
|
|
21
|
-
throw new Error('SSH host is required');
|
|
22
|
-
}
|
|
23
|
-
const username = options.username || os.userInfo().username;
|
|
24
|
-
const port = options.port || 22;
|
|
25
|
-
let sshCommand = `ssh ${username}@${host}`;
|
|
26
|
-
// Add optional parameters
|
|
27
|
-
if (port !== 22) {
|
|
28
|
-
sshCommand += ` -p ${port}`;
|
|
29
|
-
}
|
|
30
|
-
if (options.identity) {
|
|
31
|
-
sshCommand += ` -i "${options.identity}"`;
|
|
32
|
-
}
|
|
33
|
-
// Start the SSH process
|
|
34
|
-
const result = await this.terminalManager.executeCommand(sshCommand, { timeout: options.timeout || 10000 });
|
|
35
|
-
if (!result || result.pid <= 0) {
|
|
36
|
-
throw new Error(`Failed to start SSH session to ${host}`);
|
|
37
|
-
}
|
|
38
|
-
// Handle password prompt if needed
|
|
39
|
-
if (options.password) {
|
|
40
|
-
// Wait for password prompt
|
|
41
|
-
let output = "";
|
|
42
|
-
const startTime = Date.now();
|
|
43
|
-
const passwordPromptTimeout = 5000;
|
|
44
|
-
while (Date.now() - startTime < passwordPromptTimeout) {
|
|
45
|
-
const newOutput = this.terminalManager.getNewOutput(result.pid);
|
|
46
|
-
if (newOutput && newOutput.length > 0) {
|
|
47
|
-
output += newOutput;
|
|
48
|
-
if (output.toLowerCase().includes('password:')) {
|
|
49
|
-
// Send password
|
|
50
|
-
this.terminalManager.sendInputToProcess(result.pid, options.password + '\n');
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// Store session info
|
|
58
|
-
this.sessions.set(result.pid, {
|
|
59
|
-
type: 'ssh',
|
|
60
|
-
host,
|
|
61
|
-
username,
|
|
62
|
-
pid: result.pid,
|
|
63
|
-
startTime: Date.now(),
|
|
64
|
-
lastActivity: Date.now()
|
|
65
|
-
});
|
|
66
|
-
return result.pid;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Create a new REPL session for a specific language
|
|
70
|
-
* @param language - Language for the REPL (python, node, bash)
|
|
71
|
-
* @param options - Configuration options
|
|
72
|
-
* @returns PID of the created session
|
|
73
|
-
*/
|
|
74
|
-
async createSession(language, options = {}) {
|
|
75
|
-
// Handle SSH sessions separately
|
|
76
|
-
if (language.toLowerCase() === 'ssh') {
|
|
77
|
-
return this.createSSHSession(options.host, options);
|
|
78
|
-
}
|
|
79
|
-
let command;
|
|
80
|
-
let args = [];
|
|
81
|
-
switch (language.toLowerCase()) {
|
|
82
|
-
case 'python':
|
|
83
|
-
command = process.platform === 'win32' ? 'python' : 'python3';
|
|
84
|
-
args = ['-i'];
|
|
85
|
-
break;
|
|
86
|
-
case 'node':
|
|
87
|
-
command = 'node';
|
|
88
|
-
break;
|
|
89
|
-
case 'bash':
|
|
90
|
-
command = process.platform === 'win32' ? 'cmd' : 'bash';
|
|
91
|
-
break;
|
|
92
|
-
default:
|
|
93
|
-
throw new Error(`Unsupported language: ${language}`);
|
|
94
|
-
}
|
|
95
|
-
// Start the process
|
|
96
|
-
const result = await this.terminalManager.executeCommand(command, { args, timeout: options.timeout || 5000 });
|
|
97
|
-
if (!result || result.pid <= 0) {
|
|
98
|
-
throw new Error(`Failed to start ${language} REPL`);
|
|
99
|
-
}
|
|
100
|
-
// Store session info
|
|
101
|
-
this.sessions.set(result.pid, {
|
|
102
|
-
language,
|
|
103
|
-
pid: result.pid,
|
|
104
|
-
startTime: Date.now(),
|
|
105
|
-
lastActivity: Date.now()
|
|
106
|
-
});
|
|
107
|
-
return result.pid;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Execute code in an existing REPL session
|
|
111
|
-
* @param pid - Process ID of the REPL session
|
|
112
|
-
* @param code - Code to execute
|
|
113
|
-
* @param options - Execution options
|
|
114
|
-
* @returns Results including output and status
|
|
115
|
-
*/
|
|
116
|
-
async executeCode(pid, code, options = {}) {
|
|
117
|
-
const session = this.sessions.get(pid);
|
|
118
|
-
if (!session) {
|
|
119
|
-
throw new Error(`No active session with PID ${pid}`);
|
|
120
|
-
}
|
|
121
|
-
// Calculate timeout based on code complexity if not specified
|
|
122
|
-
const timeout = options.timeout || this.calculateTimeout(code);
|
|
123
|
-
// Handle multi-line code
|
|
124
|
-
if (code.includes('\n')) {
|
|
125
|
-
return this.handleMultilineCode(pid, code, session.language || 'bash', timeout);
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
return this.sendAndReadREPL(pid, code, session.language || 'bash', timeout);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Send input to a REPL process and wait for output with timeout
|
|
133
|
-
* @param pid - Process ID
|
|
134
|
-
* @param input - Input to send
|
|
135
|
-
* @param language - REPL language
|
|
136
|
-
* @param timeoutMs - Timeout in milliseconds
|
|
137
|
-
* @returns Result object with output and status
|
|
138
|
-
*/
|
|
139
|
-
async sendAndReadREPL(pid, input, language, timeoutMs = 3000) {
|
|
140
|
-
// Send the input with newline if not already present
|
|
141
|
-
const inputToSend = input.endsWith('\n') ? input : input + '\n';
|
|
142
|
-
const success = this.terminalManager.sendInputToProcess(pid, inputToSend);
|
|
143
|
-
if (!success) {
|
|
144
|
-
return {
|
|
145
|
-
success: false,
|
|
146
|
-
output: null,
|
|
147
|
-
error: "Failed to send input to process"
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
// Wait for output with timeout
|
|
151
|
-
let output = "";
|
|
152
|
-
const startTime = Date.now();
|
|
153
|
-
// Keep checking for output until timeout
|
|
154
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
155
|
-
const newOutput = this.terminalManager.getNewOutput(pid);
|
|
156
|
-
if (newOutput && newOutput.length > 0) {
|
|
157
|
-
output += newOutput;
|
|
158
|
-
// Check if output is complete (using prompt detection)
|
|
159
|
-
if (this.isOutputComplete(output, language)) {
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
164
|
-
}
|
|
165
|
-
// Update last activity time
|
|
166
|
-
if (this.sessions.has(pid)) {
|
|
167
|
-
const session = this.sessions.get(pid);
|
|
168
|
-
if (session) {
|
|
169
|
-
session.lastActivity = Date.now();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Check for errors
|
|
173
|
-
const error = this.detectErrors(output, language);
|
|
174
|
-
return {
|
|
175
|
-
success: true,
|
|
176
|
-
output: this.cleanOutput(output, input, language),
|
|
177
|
-
timeout: Date.now() - startTime >= timeoutMs,
|
|
178
|
-
error: error
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Handle multi-line code input for different languages
|
|
183
|
-
* @param pid - Process ID
|
|
184
|
-
* @param code - Multi-line code
|
|
185
|
-
* @param language - REPL language
|
|
186
|
-
* @param timeout - Timeout in milliseconds
|
|
187
|
-
* @returns Result object
|
|
188
|
-
*/
|
|
189
|
-
async handleMultilineCode(pid, code, language, timeout) {
|
|
190
|
-
const lines = code.split('\n');
|
|
191
|
-
let isBlock = false;
|
|
192
|
-
let fullOutput = '';
|
|
193
|
-
// For Python, we need to handle indentation carefully
|
|
194
|
-
if (language.toLowerCase() === 'python') {
|
|
195
|
-
for (let i = 0; i < lines.length; i++) {
|
|
196
|
-
const line = lines[i];
|
|
197
|
-
const isLastLine = i === lines.length - 1;
|
|
198
|
-
// Send line and wait for prompt
|
|
199
|
-
const result = await this.sendAndReadREPL(pid, line, language,
|
|
200
|
-
// If it's the last line and potentially ending a block, wait longer
|
|
201
|
-
isLastLine && isBlock ? timeout : Math.min(1000, timeout));
|
|
202
|
-
fullOutput += result.output || '';
|
|
203
|
-
// Check if we're in a block (Python indentation)
|
|
204
|
-
if (line.trim() && (line.startsWith(' ') || line.startsWith('\t'))) {
|
|
205
|
-
isBlock = true;
|
|
206
|
-
}
|
|
207
|
-
else if (line.trim() === '' && isBlock) {
|
|
208
|
-
// Empty line ends a block
|
|
209
|
-
isBlock = false;
|
|
210
|
-
}
|
|
211
|
-
else if (line.trim() && !line.startsWith(' ') && !line.startsWith('\t')) {
|
|
212
|
-
// Non-indented, non-empty line
|
|
213
|
-
isBlock = false;
|
|
214
|
-
}
|
|
215
|
-
// Handle errors early
|
|
216
|
-
if (result.error) {
|
|
217
|
-
return {
|
|
218
|
-
success: false,
|
|
219
|
-
output: fullOutput,
|
|
220
|
-
error: result.error
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// Ensure block is closed with empty line if needed
|
|
225
|
-
if (isBlock) {
|
|
226
|
-
const finalResult = await this.sendAndReadREPL(pid, '', language, timeout);
|
|
227
|
-
fullOutput += finalResult.output || '';
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
// For other languages, send the entire block at once
|
|
232
|
-
return await this.sendAndReadREPL(pid, code, language, timeout);
|
|
233
|
-
}
|
|
234
|
-
return {
|
|
235
|
-
success: true,
|
|
236
|
-
output: fullOutput,
|
|
237
|
-
error: this.detectErrors(fullOutput, language)
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Detect if the REPL output is complete and ready for next input
|
|
242
|
-
* @param output - Current output
|
|
243
|
-
* @param language - REPL language or session type
|
|
244
|
-
* @returns Whether output is complete
|
|
245
|
-
*/
|
|
246
|
-
isOutputComplete(output, language) {
|
|
247
|
-
const pattern = this.defaultPromptPatterns[language.toLowerCase()];
|
|
248
|
-
if (!pattern)
|
|
249
|
-
return true; // If no pattern, assume complete
|
|
250
|
-
return pattern.test(output);
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Calculate appropriate timeout based on code complexity
|
|
254
|
-
* @param code - Code to analyze
|
|
255
|
-
* @returns Timeout in milliseconds
|
|
256
|
-
*/
|
|
257
|
-
calculateTimeout(code) {
|
|
258
|
-
// Base timeout
|
|
259
|
-
let timeout = 2000;
|
|
260
|
-
// Add time for loops
|
|
261
|
-
const loopCount = (code.match(/for|while/g) || []).length;
|
|
262
|
-
timeout += loopCount * 1000;
|
|
263
|
-
// Add time for imports or requires
|
|
264
|
-
const importCount = (code.match(/import|require/g) || []).length;
|
|
265
|
-
timeout += importCount * 2000;
|
|
266
|
-
// Add time based on code length
|
|
267
|
-
timeout += Math.min(code.length * 5, 5000);
|
|
268
|
-
// Cap at reasonable maximum
|
|
269
|
-
return Math.min(timeout, 30000);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Detect errors in REPL output
|
|
273
|
-
* @param output - REPL output
|
|
274
|
-
* @param language - REPL language
|
|
275
|
-
* @returns Detected error or null
|
|
276
|
-
*/
|
|
277
|
-
detectErrors(output, language) {
|
|
278
|
-
const errorPatterns = {
|
|
279
|
-
python: /\b(Error|Exception|SyntaxError|ValueError|TypeError)\b:.*$/m,
|
|
280
|
-
node: /\b(Error|SyntaxError|TypeError|ReferenceError)\b:.*$/m,
|
|
281
|
-
bash: /\b(command not found|No such file or directory)\b/m
|
|
282
|
-
};
|
|
283
|
-
const pattern = errorPatterns[language.toLowerCase()];
|
|
284
|
-
if (!pattern)
|
|
285
|
-
return null;
|
|
286
|
-
const match = output.match(pattern);
|
|
287
|
-
return match ? match[0] : null;
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Clean and format REPL output
|
|
291
|
-
* @param output - Raw output
|
|
292
|
-
* @param input - Input that was sent
|
|
293
|
-
* @param language - REPL language
|
|
294
|
-
* @returns Cleaned output
|
|
295
|
-
*/
|
|
296
|
-
cleanOutput(output, input, language) {
|
|
297
|
-
// Remove echoed input if present
|
|
298
|
-
let cleaned = output;
|
|
299
|
-
// Remove the input echo that might appear in the output
|
|
300
|
-
const inputWithoutNewlines = input.replace(/\n/g, '');
|
|
301
|
-
if (inputWithoutNewlines.length > 0) {
|
|
302
|
-
cleaned = cleaned.replace(inputWithoutNewlines, '');
|
|
303
|
-
}
|
|
304
|
-
// Remove common prompt patterns
|
|
305
|
-
if (language.toLowerCase() === 'python') {
|
|
306
|
-
cleaned = cleaned.replace(/^(>>>|\.\.\.) /mg, '');
|
|
307
|
-
}
|
|
308
|
-
else if (language.toLowerCase() === 'node') {
|
|
309
|
-
cleaned = cleaned.replace(/^> /mg, '');
|
|
310
|
-
}
|
|
311
|
-
// Trim whitespace
|
|
312
|
-
cleaned = cleaned.trim();
|
|
313
|
-
return cleaned;
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* List all active REPL sessions
|
|
317
|
-
* @returns List of session objects
|
|
318
|
-
*/
|
|
319
|
-
listSessions() {
|
|
320
|
-
const result = [];
|
|
321
|
-
this.sessions.forEach((session, pid) => {
|
|
322
|
-
result.push({
|
|
323
|
-
pid,
|
|
324
|
-
language: session.language,
|
|
325
|
-
startTime: session.startTime,
|
|
326
|
-
lastActivity: session.lastActivity,
|
|
327
|
-
idleTime: Date.now() - session.lastActivity
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
return result;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Close a specific REPL session
|
|
334
|
-
* @param pid - Process ID to close
|
|
335
|
-
* @returns Success status
|
|
336
|
-
*/
|
|
337
|
-
async closeSession(pid) {
|
|
338
|
-
if (!this.sessions.has(pid)) {
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
const success = await this.terminalManager.terminateProcess(pid);
|
|
342
|
-
if (success) {
|
|
343
|
-
this.sessions.delete(pid);
|
|
344
|
-
}
|
|
345
|
-
return success;
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Close all idle sessions older than specified time
|
|
349
|
-
* @param maxIdleMs - Maximum idle time in milliseconds
|
|
350
|
-
* @returns Number of closed sessions
|
|
351
|
-
*/
|
|
352
|
-
async closeIdleSessions(maxIdleMs = 30 * 60 * 1000) {
|
|
353
|
-
let closedCount = 0;
|
|
354
|
-
for (const [pid, session] of this.sessions.entries()) {
|
|
355
|
-
const idleTime = Date.now() - session.lastActivity;
|
|
356
|
-
if (idleTime > maxIdleMs) {
|
|
357
|
-
const success = await this.closeSession(pid);
|
|
358
|
-
if (success)
|
|
359
|
-
closedCount++;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
return closedCount;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import { describe, it } from 'mocha';
|
|
3
|
-
import { REPLSessionManager } from './REPLSessionManager';
|
|
4
|
-
import * as sinon from 'sinon';
|
|
5
|
-
describe('REPLSessionManager', () => {
|
|
6
|
-
// Create a mock terminal manager for testing
|
|
7
|
-
const createMockTerminalManager = () => {
|
|
8
|
-
return {
|
|
9
|
-
executeCommand: sinon.stub().resolves({ pid: 12345 }),
|
|
10
|
-
sendInputToProcess: sinon.stub().returns(true),
|
|
11
|
-
getNewOutput: sinon.stub().returns('test output\n> '),
|
|
12
|
-
terminateProcess: sinon.stub().resolves(true)
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
it('should create a new instance', () => {
|
|
16
|
-
const terminalManager = createMockTerminalManager();
|
|
17
|
-
const manager = new REPLSessionManager(terminalManager);
|
|
18
|
-
expect(manager).to.be.an.instanceOf(REPLSessionManager);
|
|
19
|
-
});
|
|
20
|
-
it('should create a new session', async () => {
|
|
21
|
-
const terminalManager = createMockTerminalManager();
|
|
22
|
-
const manager = new REPLSessionManager(terminalManager);
|
|
23
|
-
const pid = await manager.createSession('node');
|
|
24
|
-
expect(pid).to.equal(12345);
|
|
25
|
-
expect(terminalManager.executeCommand.calledOnce).to.be.true;
|
|
26
|
-
});
|
|
27
|
-
it('should send and read REPL output', async () => {
|
|
28
|
-
const terminalManager = createMockTerminalManager();
|
|
29
|
-
const manager = new REPLSessionManager(terminalManager);
|
|
30
|
-
const result = await manager.sendAndReadREPL(12345, 'console.log("hello")', 'node', 1000);
|
|
31
|
-
expect(result.success).to.be.true;
|
|
32
|
-
expect(terminalManager.sendInputToProcess.calledOnce).to.be.true;
|
|
33
|
-
expect(terminalManager.getNewOutput.called).to.be.true;
|
|
34
|
-
});
|
|
35
|
-
it('should detect when output is complete', () => {
|
|
36
|
-
const terminalManager = createMockTerminalManager();
|
|
37
|
-
const manager = new REPLSessionManager(terminalManager);
|
|
38
|
-
// Node.js prompt
|
|
39
|
-
expect(manager.isOutputComplete('test\n> ', 'node')).to.be.true;
|
|
40
|
-
// Python prompt
|
|
41
|
-
expect(manager.isOutputComplete('test\n>>> ', 'python')).to.be.true;
|
|
42
|
-
// Incomplete output
|
|
43
|
-
expect(manager.isOutputComplete('test\n', 'node')).to.be.false;
|
|
44
|
-
});
|
|
45
|
-
it('should calculate timeout based on code complexity', () => {
|
|
46
|
-
const terminalManager = createMockTerminalManager();
|
|
47
|
-
const manager = new REPLSessionManager(terminalManager);
|
|
48
|
-
const simpleTimeout = manager.calculateTimeout('x = 1');
|
|
49
|
-
const loopTimeout = manager.calculateTimeout('for (let i = 0; i < 10; i++) { console.log(i); }');
|
|
50
|
-
const importTimeout = manager.calculateTimeout('import math\nimport os\nimport sys');
|
|
51
|
-
expect(loopTimeout).to.be.greaterThan(simpleTimeout);
|
|
52
|
-
expect(importTimeout).to.be.greaterThan(simpleTimeout);
|
|
53
|
-
});
|
|
54
|
-
it('should clean output', () => {
|
|
55
|
-
const terminalManager = createMockTerminalManager();
|
|
56
|
-
const manager = new REPLSessionManager(terminalManager);
|
|
57
|
-
const nodeOutput = manager.cleanOutput('x = 1\n> ', 'x = 1', 'node');
|
|
58
|
-
const pythonOutput = manager.cleanOutput('>>> x = 1\n>>> ', 'x = 1', 'python');
|
|
59
|
-
expect(nodeOutput).to.not.include('>');
|
|
60
|
-
expect(pythonOutput).to.not.include('>>>');
|
|
61
|
-
});
|
|
62
|
-
it('should handle multi-line code', async () => {
|
|
63
|
-
const terminalManager = createMockTerminalManager();
|
|
64
|
-
const manager = new REPLSessionManager(terminalManager);
|
|
65
|
-
// Mock implementation for Python indentation
|
|
66
|
-
terminalManager.getNewOutput.onFirstCall().returns('>>> ');
|
|
67
|
-
terminalManager.getNewOutput.onSecondCall().returns('... ');
|
|
68
|
-
terminalManager.getNewOutput.onThirdCall().returns('... ');
|
|
69
|
-
terminalManager.getNewOutput.onCall(3).returns('result\n>>> ');
|
|
70
|
-
const multilineCode = 'for i in range(5):\n print(i)';
|
|
71
|
-
const result = await manager.handleMultilineCode(12345, multilineCode, 'python', 1000);
|
|
72
|
-
expect(result.success).to.be.true;
|
|
73
|
-
expect(terminalManager.sendInputToProcess.callCount).to.be.at.least(2);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Client-side utility for interacting with the REPL functionality
|
|
3
|
-
*/
|
|
4
|
-
interface ApiClient {
|
|
5
|
-
executeCommand: (command: string, params?: any) => Promise<any>;
|
|
6
|
-
}
|
|
7
|
-
interface ExecuteOptions {
|
|
8
|
-
timeout?: number;
|
|
9
|
-
stopOnError?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export declare class REPLClient {
|
|
12
|
-
private apiClient;
|
|
13
|
-
private sessions;
|
|
14
|
-
constructor(apiClient: ApiClient);
|
|
15
|
-
/**
|
|
16
|
-
* Create a new REPL session for a specific language
|
|
17
|
-
* @param language - Language for the REPL (python, node, bash)
|
|
18
|
-
* @param options - Optional configuration
|
|
19
|
-
* @returns Session information
|
|
20
|
-
*/
|
|
21
|
-
createSession(language: string, options?: Record<string, any>): Promise<any>;
|
|
22
|
-
/**
|
|
23
|
-
* Execute code in an existing REPL session
|
|
24
|
-
* @param pid - Process ID of the session
|
|
25
|
-
* @param code - Code to execute
|
|
26
|
-
* @param options - Optional execution options
|
|
27
|
-
* @returns Execution results
|
|
28
|
-
*/
|
|
29
|
-
executeCode(pid: number, code: string, options?: ExecuteOptions): Promise<any>;
|
|
30
|
-
/**
|
|
31
|
-
* List all active REPL sessions
|
|
32
|
-
* @returns List of active sessions
|
|
33
|
-
*/
|
|
34
|
-
listSessions(): Promise<any>;
|
|
35
|
-
/**
|
|
36
|
-
* Close a specific REPL session
|
|
37
|
-
* @param pid - Process ID to close
|
|
38
|
-
* @returns Result indicating success
|
|
39
|
-
*/
|
|
40
|
-
closeSession(pid: number): Promise<any>;
|
|
41
|
-
/**
|
|
42
|
-
* Close all idle REPL sessions
|
|
43
|
-
* @param maxIdleMs - Maximum idle time in milliseconds
|
|
44
|
-
* @returns Result with number of closed sessions
|
|
45
|
-
*/
|
|
46
|
-
closeIdleSessions(maxIdleMs?: number): Promise<any>;
|
|
47
|
-
/**
|
|
48
|
-
* Execute a multi-line code block in the appropriate REPL session
|
|
49
|
-
* @param codeBlock - Code block with language marker
|
|
50
|
-
* @param options - Optional execution options
|
|
51
|
-
* @returns Execution results
|
|
52
|
-
*/
|
|
53
|
-
executeCodeBlock(codeBlock: string, options?: ExecuteOptions): Promise<any>;
|
|
54
|
-
/**
|
|
55
|
-
* Run a simple one-liner code snippet
|
|
56
|
-
* @param language - The programming language
|
|
57
|
-
* @param code - Code to execute (single line)
|
|
58
|
-
* @param options - Optional execution options
|
|
59
|
-
* @returns Execution results
|
|
60
|
-
*/
|
|
61
|
-
runSnippet(language: string, code: string, options?: ExecuteOptions): Promise<any>;
|
|
62
|
-
}
|
|
63
|
-
export {};
|