// sandbox-worker.js — WebWorker entry point for isolated agent execution // Receives: { taskId, task, capabilities, vfsSnapshot, allowedCommands, apiProxy } // Returns: { taskId, result, error, toolCalls, duration } let _vfs = null; let _apiProxyOrigin = null; self.onmessage = async (e) => { const { taskId, task, capabilities, vfsSnapshot, allowedCommands, apiProxy } = e.data; _apiProxyOrigin = apiProxy ? new URL(apiProxy).origin : null; const startTime = performance.now(); let toolCalls = 0; let result = ''; let error = null; try { // Rebuild VFS from snapshot _vfs = vfsSnapshot ? JSON.parse(vfsSnapshot) : { root: { type: 'dir', content: {} }, currentPath: [] }; // Log start console.log(`[Sandbox ${taskId}] Starting task: "${task.substring(0, 60)}"`); // Parse the task for tool use const toolResults = {}; const commandWhitelist = allowedCommands || ['ls', 'cat', 'echo', 'find', 'grep', 'help', 'status']; // Execute basic commands const parts = task.trim().split(/\s+/); const cmd = parts[0].toLowerCase(); if (cmd === 'ls' || cmd === 'list') { result = sandbox_ls(parts[1] || '/'); toolCalls++; } else if (cmd === 'cat' || cmd === 'read') { result = sandbox_cat(parts[1] || ''); toolCalls++; } else if (cmd === 'find' || cmd === 'search') { result = sandbox_find(parts.slice(1).join(' ') || ''); toolCalls++; } else if (cmd === 'grep') { result = sandbox_grep(parts.slice(1).join(' ')); toolCalls++; } else if (cmd === 'echo') { result = parts.slice(1).join(' '); toolCalls++; } else if (cmd === 'help') { result = 'Available commands: ' + commandWhitelist.join(', '); toolCalls++; } else if (cmd === 'status') { result = sandbox_status(); toolCalls++; } else if (capabilities.includes('run_command') && commandWhitelist.includes(cmd)) { result = sandbox_run(cmd, parts.slice(1)); toolCalls++; } else { // If the task is descriptive (not a command), run smart handling if (capabilities.includes('list_files') || capabilities.includes('read_file')) { // Smart mode: interpret task const lower = task.toLowerCase(); if (lower.includes('list') || lower.includes('file')) { const dir = parts.find(p => p.startsWith('/') || p.startsWith('.')) || '/'; result = sandbox_ls(dir); } else if (lower.includes('find') || lower.includes('search')) { const q = task.replace(/find|search/i, '').trim(); result = sandbox_find(q || ''); } else if (lower.includes('count') || lower.includes('how many')) { result = sandbox_ls('/'); const count = (result.match(/\[file\]|\[dir\]/g) || []).length; result = `Found ${count} items in total.\\n${result}`; } else if (lower.includes('status') || lower.includes('health')) { result = sandbox_status(); } else { result = `Task interpreted: "${task}"\\nCapabilities: ${capabilities.join(', ')}\\nTip: Be more specific (e.g. "list files in /")`; } toolCalls++; } else { result = `Task: "${task}"\\nNo matching capability for command "${cmd}"`; } } } catch (e) { error = e.message; console.error(`[Sandbox ${taskId}] Error:`, e); } const duration = ((performance.now() - startTime) / 1000).toFixed(2); self.postMessage({ taskId, result, error, toolCalls, duration: parseFloat(duration), vfsSnapshot: JSON.stringify(_vfs) }); }; // ─── Sandboxed VFS Operations ─── function sandbox_ls(path) { path = path || '/'; const entries = _vfs_get_entries(path); if (!entries) return `ls: ${path}: No such directory`; const lines = entries.map(e => { const icon = e.type === 'dir' ? '[dir]' : '[file]'; const size = e.size !== undefined ? ` (${e.size}B)` : ''; return `${icon} ${e.name}${size}`; }); return lines.join('\\n') || '(empty)'; } function sandbox_cat(path) { const content = _vfs_get_content(path); if (content === null) return `cat: ${path}: No such file`; if (typeof content === 'object') return `cat: ${path}: Is a directory`; if (content.length > 8000) return content.substring(0, 8000) + '\\n... (truncated at 8KB)'; return content; } function sandbox_find(query) { const results = []; const q = query ? query.toLowerCase() : ''; _vfs_walk('/', (path, node) => { if (node.type === 'file') { if (!q || path.toLowerCase().includes(q)) { results.push(path); } } }); if (results.length === 0) return `No files found${query ? ` matching "${query}"` : ''}`; return results.join('\\n'); } function sandbox_grep(query) { if (!query) return 'grep: missing pattern'; const q = query.toLowerCase(); const results = []; _vfs_walk('/', (path, node) => { if (node.type === 'file' && typeof node.content === 'string') { const lines = node.content.split('\\n'); for (let i = 0; i < lines.length; i++) { if (lines[i].toLowerCase().includes(q)) { results.push(`${path}:${i + 1}: ${lines[i].trim().substring(0, 100)}`); } } } }); if (results.length === 0) return `grep: no matches for "${query}"`; return results.slice(0, 50).join('\\n') + (results.length > 50 ? `\\n... (+${results.length - 50} more)` : ''); } function sandbox_status() { let fileCount = 0, dirCount = 0; _vfs_walk('/', (path, node) => { if (node.type === 'file') fileCount++; else dirCount++; }); const vfsSize = JSON.stringify(_vfs).length; return `Sandbox VFS Status:\\n Files: ${fileCount}\\n Directories: ${dirCount}\\n VFS Size: ${(vfsSize / 1024).toFixed(1)}KB`; } function sandbox_run(cmd, args) { return `[sandbox] ${cmd} ${args.join(' ')}\\n(Command execution limited in sandbox mode)`; } // ─── VFS Helpers ─── function _vfs_get_entries(path) { const dir = _vfs_resolve(path); if (!dir || dir.type !== 'dir') return null; return Object.entries(dir.content || {}).map(([name, node]) => ({ name, type: node.type || 'file', size: typeof node.content === 'string' ? node.content.length : undefined })).sort((a, b) => a.name.localeCompare(b.name)); } function _vfs_get_content(path) { const node = _vfs_resolve(path); if (!node || node.type === 'dir') return null; return node.content; } function _vfs_resolve(path) { if (!_vfs || !_vfs.root) return null; if (path === '/' || path === '') return _vfs.root; const parts = path.split('/').filter(p => p); let current = _vfs.root; for (const part of parts) { if (!current.content || !current.content[part]) return null; current = current.content[part]; } return current; } function _vfs_walk(path, cb) { const node = _vfs_resolve(path); if (!node) return; cb(path, node); if (node.type === 'dir' && node.content) { for (const [name, child] of Object.entries(node.content)) { _vfs_walk(path === '/' ? '/' + name : path + '/' + name, cb); } } }