squidclaw 2.6.0 โ 2.7.0
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/lib/channels/telegram/bot.js +2 -1
- package/lib/engine.js +12 -0
- package/lib/features/sandbox.js +299 -0
- package/lib/middleware/commands.js +42 -0
- package/lib/tools/router.js +52 -10
- package/package.json +1 -1
|
@@ -208,7 +208,8 @@ export class TelegramManager {
|
|
|
208
208
|
const chatId = contactId.replace('tg_', '');
|
|
209
209
|
const { readFileSync } = await import('fs');
|
|
210
210
|
const buffer = readFileSync(filePath);
|
|
211
|
-
|
|
211
|
+
const { InputFile: IF } = await import("grammy");
|
|
212
|
+
await botInfo.bot.api.sendDocument(chatId, new IF(buffer, fileName || 'file'), {
|
|
212
213
|
caption: caption || '',
|
|
213
214
|
});
|
|
214
215
|
logger.info('telegram', 'Sent document: ' + fileName);
|
package/lib/engine.js
CHANGED
|
@@ -228,6 +228,18 @@ export class SquidclawEngine {
|
|
|
228
228
|
if (pending.c > 0) console.log(` โฐ Reminders: ${pending.c} pending`);
|
|
229
229
|
} catch {}
|
|
230
230
|
|
|
231
|
+
// Sandbox
|
|
232
|
+
try {
|
|
233
|
+
const { Sandbox } = await import('./features/sandbox.js');
|
|
234
|
+
this.sandbox = new Sandbox({
|
|
235
|
+
timeout: 15000,
|
|
236
|
+
maxMemory: 64,
|
|
237
|
+
maxFiles: 100,
|
|
238
|
+
});
|
|
239
|
+
// Cleanup old files every hour
|
|
240
|
+
setInterval(() => this.sandbox.cleanup(), 3600000);
|
|
241
|
+
} catch (err) { logger.error('engine', 'Sandbox init failed: ' + err.message); }
|
|
242
|
+
|
|
231
243
|
// Plugins
|
|
232
244
|
try {
|
|
233
245
|
const { PluginManager } = await import('./features/plugins.js');
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ๐ฆ Proper Sandbox
|
|
3
|
+
* Isolated code execution with resource limits
|
|
4
|
+
* - VM isolation (vm module)
|
|
5
|
+
* - CPU timeout
|
|
6
|
+
* - Memory limits
|
|
7
|
+
* - File system jail
|
|
8
|
+
* - Network restrictions
|
|
9
|
+
* - Safe eval for user code
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { logger } from '../core/logger.js';
|
|
13
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, statSync, unlinkSync } from 'fs';
|
|
14
|
+
import { join, resolve } from 'path';
|
|
15
|
+
import { execSync, spawn } from 'child_process';
|
|
16
|
+
import vm from 'vm';
|
|
17
|
+
|
|
18
|
+
export class Sandbox {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.jailDir = options.jailDir || '/tmp/squidclaw-sandbox';
|
|
21
|
+
this.timeout = options.timeout || 10000; // 10s default
|
|
22
|
+
this.maxMemory = options.maxMemory || 64; // 64MB
|
|
23
|
+
this.maxFileSize = options.maxFileSize || 1024 * 1024; // 1MB
|
|
24
|
+
this.maxFiles = options.maxFiles || 50;
|
|
25
|
+
this.maxOutputSize = options.maxOutputSize || 50000; // 50KB
|
|
26
|
+
this.allowNetwork = options.allowNetwork || false;
|
|
27
|
+
|
|
28
|
+
mkdirSync(this.jailDir, { recursive: true });
|
|
29
|
+
|
|
30
|
+
// Blocked patterns for shell commands
|
|
31
|
+
this.blockedCommands = [
|
|
32
|
+
/rm\s+(-rf?|--recursive)\s+\//, /mkfs/, /dd\s+if=/, /:\(\)\{/,
|
|
33
|
+
/chmod\s+777\s+\//, /chown\s.*\//, /passwd/, /userdel/, /useradd/,
|
|
34
|
+
/shutdown/, /reboot/, /halt/, /poweroff/,
|
|
35
|
+
/iptables/, /ufw\s/, /firewall/,
|
|
36
|
+
/wget.*\|.*sh/, /curl.*\|.*sh/, /eval\s*\(/, // download & execute
|
|
37
|
+
/\/etc\/shadow/, /\/etc\/passwd/,
|
|
38
|
+
/ssh\s/, /scp\s/, /rsync\s/, // no remote access
|
|
39
|
+
/npm\s+install\s+-g/, /pip\s+install/, // no global installs
|
|
40
|
+
/systemctl/, /service\s/, // no service control
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// โโ JavaScript VM Execution โโ
|
|
45
|
+
|
|
46
|
+
evalJS(code, context = {}) {
|
|
47
|
+
const sandbox = {
|
|
48
|
+
console: {
|
|
49
|
+
log: (...args) => { output.push(args.map(String).join(' ')); },
|
|
50
|
+
error: (...args) => { output.push('ERROR: ' + args.map(String).join(' ')); },
|
|
51
|
+
warn: (...args) => { output.push('WARN: ' + args.map(String).join(' ')); },
|
|
52
|
+
},
|
|
53
|
+
Math, Date, JSON, parseInt, parseFloat, isNaN, isFinite,
|
|
54
|
+
String, Number, Boolean, Array, Object, Map, Set, RegExp,
|
|
55
|
+
setTimeout: undefined, setInterval: undefined, // blocked
|
|
56
|
+
fetch: undefined, // blocked unless allowNetwork
|
|
57
|
+
require: undefined, // blocked
|
|
58
|
+
process: { env: {} }, // sanitized
|
|
59
|
+
Buffer: undefined, // blocked
|
|
60
|
+
...context,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const output = [];
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const vmContext = vm.createContext(sandbox, {
|
|
67
|
+
codeGeneration: { strings: false, wasm: false },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const script = new vm.Script(code, {
|
|
71
|
+
timeout: this.timeout,
|
|
72
|
+
filename: 'sandbox.js',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const result = script.runInContext(vmContext, {
|
|
76
|
+
timeout: this.timeout,
|
|
77
|
+
breakOnSigint: true,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (result !== undefined && !output.length) {
|
|
81
|
+
output.push(String(result));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const finalOutput = output.join('\n').slice(0, this.maxOutputSize);
|
|
85
|
+
logger.info('sandbox', `JS eval OK (${code.length} chars)`);
|
|
86
|
+
return { success: true, output: finalOutput, type: 'javascript' };
|
|
87
|
+
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const errMsg = err.code === 'ERR_SCRIPT_EXECUTION_TIMEOUT'
|
|
90
|
+
? 'Execution timeout (' + (this.timeout / 1000) + 's limit)'
|
|
91
|
+
: err.message;
|
|
92
|
+
return { success: false, error: errMsg, output: output.join('\n'), type: 'javascript' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// โโ Python Execution (process-isolated) โโ
|
|
97
|
+
|
|
98
|
+
async runPython(code, options = {}) {
|
|
99
|
+
const filename = 'run_' + Date.now() + '.py';
|
|
100
|
+
const filepath = join(this.jailDir, filename);
|
|
101
|
+
const timeout = options.timeout || this.timeout;
|
|
102
|
+
|
|
103
|
+
// Safety: wrap in resource limits
|
|
104
|
+
const wrappedCode = `
|
|
105
|
+
import sys, os, signal
|
|
106
|
+
signal.alarm(${Math.ceil(timeout / 1000)})
|
|
107
|
+
sys.path = ['.']
|
|
108
|
+
os.chdir('${this.jailDir}')
|
|
109
|
+
${code}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
writeFileSync(filepath, wrappedCode);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const result = await this._execProcess('python3', [filepath], {
|
|
116
|
+
timeout,
|
|
117
|
+
cwd: this.jailDir,
|
|
118
|
+
maxOutput: this.maxOutputSize,
|
|
119
|
+
});
|
|
120
|
+
return { ...result, type: 'python' };
|
|
121
|
+
} finally {
|
|
122
|
+
try { unlinkSync(filepath); } catch {}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// โโ Shell Execution (sandboxed) โโ
|
|
127
|
+
|
|
128
|
+
async runShell(command, options = {}) {
|
|
129
|
+
// Security check
|
|
130
|
+
for (const pattern of this.blockedCommands) {
|
|
131
|
+
if (pattern.test(command)) {
|
|
132
|
+
return { success: false, error: 'Blocked: dangerous command pattern', type: 'shell' };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const timeout = options.timeout || this.timeout;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const output = execSync(command, {
|
|
140
|
+
cwd: options.cwd || this.jailDir,
|
|
141
|
+
timeout,
|
|
142
|
+
maxBuffer: this.maxOutputSize,
|
|
143
|
+
encoding: 'utf8',
|
|
144
|
+
env: {
|
|
145
|
+
PATH: '/usr/local/bin:/usr/bin:/bin',
|
|
146
|
+
HOME: this.jailDir,
|
|
147
|
+
LANG: 'en_US.UTF-8',
|
|
148
|
+
// No AWS keys, no tokens, no secrets
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
logger.info('sandbox', `Shell OK: ${command.slice(0, 50)}`);
|
|
153
|
+
return { success: true, output: output.trim().slice(0, this.maxOutputSize), type: 'shell' };
|
|
154
|
+
} catch (err) {
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
output: (err.stdout || '').trim().slice(0, 5000),
|
|
158
|
+
error: (err.stderr || err.message).trim().slice(0, 5000),
|
|
159
|
+
exitCode: err.status || 1,
|
|
160
|
+
type: 'shell',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// โโ File System (jailed) โโ
|
|
166
|
+
|
|
167
|
+
writeFile(name, content) {
|
|
168
|
+
const safePath = this._safePath(name);
|
|
169
|
+
|
|
170
|
+
if (content.length > this.maxFileSize) {
|
|
171
|
+
throw new Error('File too large: ' + (content.length / 1024).toFixed(0) + 'KB (max ' + (this.maxFileSize / 1024) + 'KB)');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const fileCount = this._countFiles();
|
|
175
|
+
if (fileCount >= this.maxFiles) {
|
|
176
|
+
throw new Error('Too many files in sandbox (max ' + this.maxFiles + ')');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const dir = resolve(safePath, '..');
|
|
180
|
+
mkdirSync(dir, { recursive: true });
|
|
181
|
+
writeFileSync(safePath, content);
|
|
182
|
+
return { path: safePath, size: content.length };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
readFile(name) {
|
|
186
|
+
const safePath = this._safePath(name);
|
|
187
|
+
if (!existsSync(safePath)) throw new Error('File not found: ' + name);
|
|
188
|
+
|
|
189
|
+
const stat = statSync(safePath);
|
|
190
|
+
if (stat.size > this.maxFileSize) throw new Error('File too large to read');
|
|
191
|
+
|
|
192
|
+
return readFileSync(safePath, 'utf8');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
listFiles(subdir = '.') {
|
|
196
|
+
const safePath = this._safePath(subdir);
|
|
197
|
+
if (!existsSync(safePath)) return [];
|
|
198
|
+
|
|
199
|
+
return readdirSync(safePath, { withFileTypes: true }).map(d => ({
|
|
200
|
+
name: d.name,
|
|
201
|
+
type: d.isDirectory() ? 'dir' : 'file',
|
|
202
|
+
size: d.isFile() ? statSync(join(safePath, d.name)).size : 0,
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
deleteFile(name) {
|
|
207
|
+
const safePath = this._safePath(name);
|
|
208
|
+
if (existsSync(safePath)) unlinkSync(safePath);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// โโ Cleanup โโ
|
|
212
|
+
|
|
213
|
+
cleanup() {
|
|
214
|
+
try {
|
|
215
|
+
execSync(`find ${this.jailDir} -type f -mmin +60 -delete 2>/dev/null`, { timeout: 5000 });
|
|
216
|
+
logger.info('sandbox', 'Cleaned old files');
|
|
217
|
+
} catch {}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// โโ Stats โโ
|
|
221
|
+
|
|
222
|
+
getStats() {
|
|
223
|
+
const files = this._countFiles();
|
|
224
|
+
let totalSize = 0;
|
|
225
|
+
try {
|
|
226
|
+
const output = execSync(`du -sb ${this.jailDir} 2>/dev/null`, { encoding: 'utf8' });
|
|
227
|
+
totalSize = parseInt(output.split('\t')[0]) || 0;
|
|
228
|
+
} catch {}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
files,
|
|
232
|
+
maxFiles: this.maxFiles,
|
|
233
|
+
totalSize,
|
|
234
|
+
maxFileSize: this.maxFileSize,
|
|
235
|
+
timeout: this.timeout,
|
|
236
|
+
maxMemory: this.maxMemory,
|
|
237
|
+
jailDir: this.jailDir,
|
|
238
|
+
allowNetwork: this.allowNetwork,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// โโ Internal โโ
|
|
243
|
+
|
|
244
|
+
_safePath(name) {
|
|
245
|
+
const resolved = resolve(this.jailDir, name);
|
|
246
|
+
if (!resolved.startsWith(this.jailDir)) {
|
|
247
|
+
throw new Error('Path traversal blocked');
|
|
248
|
+
}
|
|
249
|
+
return resolved;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_countFiles() {
|
|
253
|
+
try {
|
|
254
|
+
const output = execSync(`find ${this.jailDir} -type f | wc -l`, { encoding: 'utf8', timeout: 3000 });
|
|
255
|
+
return parseInt(output.trim()) || 0;
|
|
256
|
+
} catch { return 0; }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async _execProcess(cmd, args, options = {}) {
|
|
260
|
+
return new Promise((resolve) => {
|
|
261
|
+
let stdout = '';
|
|
262
|
+
let stderr = '';
|
|
263
|
+
const timeout = options.timeout || this.timeout;
|
|
264
|
+
const maxOutput = options.maxOutput || this.maxOutputSize;
|
|
265
|
+
|
|
266
|
+
const proc = spawn(cmd, args, {
|
|
267
|
+
cwd: options.cwd || this.jailDir,
|
|
268
|
+
timeout,
|
|
269
|
+
env: {
|
|
270
|
+
PATH: '/usr/local/bin:/usr/bin:/bin',
|
|
271
|
+
HOME: this.jailDir,
|
|
272
|
+
LANG: 'en_US.UTF-8',
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const timer = setTimeout(() => {
|
|
277
|
+
proc.kill('SIGKILL');
|
|
278
|
+
resolve({ success: false, error: 'Timeout (' + (timeout / 1000) + 's)', output: stdout.slice(0, maxOutput) });
|
|
279
|
+
}, timeout);
|
|
280
|
+
|
|
281
|
+
proc.stdout.on('data', d => { stdout += d; if (stdout.length > maxOutput) proc.kill(); });
|
|
282
|
+
proc.stderr.on('data', d => { stderr += d; });
|
|
283
|
+
|
|
284
|
+
proc.on('close', (code) => {
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
if (code === 0) {
|
|
287
|
+
resolve({ success: true, output: stdout.trim().slice(0, maxOutput) });
|
|
288
|
+
} else {
|
|
289
|
+
resolve({ success: false, output: stdout.trim().slice(0, 5000), error: stderr.trim().slice(0, 5000), exitCode: code });
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
proc.on('error', (err) => {
|
|
294
|
+
clearTimeout(timer);
|
|
295
|
+
resolve({ success: false, error: err.message });
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -145,6 +145,48 @@ export async function commandsMiddleware(ctx, next) {
|
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
if (cmd === '/sandbox') {
|
|
149
|
+
if (!ctx.engine.sandbox) { await ctx.reply('โ Sandbox not available'); return; }
|
|
150
|
+
const args = msg.slice(9).trim();
|
|
151
|
+
|
|
152
|
+
if (!args || args === 'stats') {
|
|
153
|
+
const stats = ctx.engine.sandbox.getStats();
|
|
154
|
+
await ctx.reply('๐ *Sandbox*\n\n๐ Files: ' + stats.files + '/' + stats.maxFiles + '\n๐พ Size: ' + (stats.totalSize / 1024).toFixed(0) + ' KB\nโฑ๏ธ Timeout: ' + (stats.timeout / 1000) + 's\n๐ง Max memory: ' + stats.maxMemory + ' MB\n๐ Network: ' + (stats.allowNetwork ? 'โ
' : '๐ซ'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (args === 'files') {
|
|
159
|
+
const files = ctx.engine.sandbox.listFiles();
|
|
160
|
+
if (files.length === 0) { await ctx.reply('๐ Sandbox is empty'); return; }
|
|
161
|
+
const lines = files.map(f => (f.type === 'dir' ? '๐ ' : '๐ ') + f.name + (f.size ? ' (' + (f.size / 1024).toFixed(1) + ' KB)' : ''));
|
|
162
|
+
await ctx.reply('๐ *Sandbox Files*\n\n' + lines.join('\n'));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (args === 'clean') {
|
|
167
|
+
ctx.engine.sandbox.cleanup();
|
|
168
|
+
await ctx.reply('๐งน Sandbox cleaned');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (args.startsWith('js ')) {
|
|
173
|
+
const code = args.slice(3);
|
|
174
|
+
const result = ctx.engine.sandbox.evalJS(code);
|
|
175
|
+
await ctx.reply(result.success ? '```\n' + (result.output || '(no output)') + '\n```' : 'โ ' + result.error);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (args.startsWith('py ')) {
|
|
180
|
+
const code = args.slice(3);
|
|
181
|
+
const result = await ctx.engine.sandbox.runPython(code);
|
|
182
|
+
await ctx.reply(result.success ? '```\n' + (result.output || '(no output)') + '\n```' : 'โ ' + (result.error || 'Failed'));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await ctx.reply('๐ *Sandbox Commands*\n\n/sandbox โ stats\n/sandbox files โ list files\n/sandbox clean โ remove old files\n/sandbox js <code> โ run JavaScript\n/sandbox py <code> โ run Python');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
148
190
|
if (cmd === '/plugins' || cmd === '/plugin') {
|
|
149
191
|
if (!ctx.engine.plugins) { await ctx.reply('โ Plugin system not available'); return; }
|
|
150
192
|
const args = msg.split(/\s+/).slice(1);
|
package/lib/tools/router.js
CHANGED
|
@@ -169,6 +169,13 @@ export class ToolRouter {
|
|
|
169
169
|
'---TOOL:handoff:reason---',
|
|
170
170
|
'Transfer the conversation to a human agent. Use when you cannot help further.');
|
|
171
171
|
|
|
172
|
+
tools.push('', '### Run JavaScript (Sandboxed)',
|
|
173
|
+
'---TOOL:js:console.log(2 + 2)---',
|
|
174
|
+
'Execute JavaScript in a secure VM sandbox. No network, no filesystem, no require. Safe for math, logic, data processing.',
|
|
175
|
+
'', '### Sandbox Info',
|
|
176
|
+
'---TOOL:sandbox_info:stats---',
|
|
177
|
+
'Show sandbox stats (files, size, limits).');
|
|
178
|
+
|
|
172
179
|
tools.push('', '### Run Command',
|
|
173
180
|
'---TOOL:exec:ls -la---',
|
|
174
181
|
'Execute a shell command. Output is returned. Sandboxed for safety.',
|
|
@@ -611,10 +618,15 @@ export class ToolRouter {
|
|
|
611
618
|
case 'exec':
|
|
612
619
|
case 'shell':
|
|
613
620
|
case 'run': {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
621
|
+
if (this._engine?.sandbox) {
|
|
622
|
+
const result = await this._engine.sandbox.runShell(toolArg);
|
|
623
|
+
toolResult = result.success ? (result.output || '(no output)') : 'Error: ' + (result.error || 'Unknown');
|
|
624
|
+
} else {
|
|
625
|
+
const { ShellTool } = await import('./shell.js');
|
|
626
|
+
const sh = new ShellTool();
|
|
627
|
+
const result = sh.exec(toolArg);
|
|
628
|
+
toolResult = result.error ? 'Error: ' + result.error : result.output || '(no output)';
|
|
629
|
+
}
|
|
618
630
|
break;
|
|
619
631
|
}
|
|
620
632
|
case 'readfile': {
|
|
@@ -643,14 +655,44 @@ export class ToolRouter {
|
|
|
643
655
|
toolResult = result.error || result.files.map(f => (f.type === 'dir' ? '๐ ' : '๐ ') + f.name).join('\n');
|
|
644
656
|
break;
|
|
645
657
|
}
|
|
658
|
+
case 'js':
|
|
659
|
+
case 'javascript':
|
|
660
|
+
case 'eval': {
|
|
661
|
+
if (this._engine?.sandbox) {
|
|
662
|
+
const result = this._engine.sandbox.evalJS(toolArg);
|
|
663
|
+
toolResult = result.success ? (result.output || '(no output)') : 'Error: ' + (result.error || 'Unknown');
|
|
664
|
+
} else {
|
|
665
|
+
toolResult = 'Sandbox not available';
|
|
666
|
+
}
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
646
669
|
case 'python':
|
|
647
670
|
case 'py': {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
671
|
+
if (this._engine?.sandbox) {
|
|
672
|
+
const result = await this._engine.sandbox.runPython(toolArg);
|
|
673
|
+
toolResult = result.success ? (result.output || '(no output)') : 'Error: ' + (result.error || 'Unknown');
|
|
674
|
+
} else {
|
|
675
|
+
const { ShellTool } = await import('./shell.js');
|
|
676
|
+
const sh = new ShellTool();
|
|
677
|
+
sh.writeFile('_run.py', toolArg);
|
|
678
|
+
const result = sh.exec('python3 _run.py');
|
|
679
|
+
toolResult = result.error ? 'Error: ' + result.error : result.output || '(no output)';
|
|
680
|
+
}
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
case 'sandbox_info': {
|
|
684
|
+
if (this._engine?.sandbox) {
|
|
685
|
+
const stats = this._engine.sandbox.getStats();
|
|
686
|
+
toolResult = '๐ Sandbox Stats:\n' +
|
|
687
|
+
'๐ Files: ' + stats.files + '/' + stats.maxFiles + '\n' +
|
|
688
|
+
'๐พ Size: ' + (stats.totalSize / 1024).toFixed(0) + ' KB\n' +
|
|
689
|
+
'โฑ๏ธ Timeout: ' + (stats.timeout / 1000) + 's\n' +
|
|
690
|
+
'๐ง Max memory: ' + stats.maxMemory + ' MB\n' +
|
|
691
|
+
'๐ Network: ' + (stats.allowNetwork ? 'allowed' : 'blocked') + '\n' +
|
|
692
|
+
'๐ Jail: ' + stats.jailDir;
|
|
693
|
+
} else {
|
|
694
|
+
toolResult = 'Sandbox not available';
|
|
695
|
+
}
|
|
654
696
|
break;
|
|
655
697
|
}
|
|
656
698
|
case 'spawn': {
|