Fix: update agents/sandbox_agent.py
Browse files- agents/sandbox_agent.py +111 -109
agents/sandbox_agent.py
CHANGED
|
@@ -1,40 +1,30 @@
|
|
| 1 |
"""
|
| 2 |
-
SandboxAgent β
|
| 3 |
-
|
| 4 |
-
Falls back to local subprocess if HF VSCode unavailable
|
| 5 |
"""
|
| 6 |
import asyncio
|
| 7 |
import json
|
| 8 |
import os
|
|
|
|
|
|
|
| 9 |
from typing import Dict, List, Optional
|
| 10 |
-
|
| 11 |
-
import httpx
|
| 12 |
import structlog
|
| 13 |
from .base_agent import BaseAgent
|
| 14 |
|
| 15 |
log = structlog.get_logger()
|
| 16 |
|
| 17 |
-
WORKSPACE
|
| 18 |
-
HF_VSCODE_URL = os.environ.get("HF_VSCODE_URL", "") # e.g. https://user-vscode.hf.space
|
| 19 |
-
HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
| 20 |
|
| 21 |
|
| 22 |
class SandboxAgent(BaseAgent):
|
| 23 |
-
"""
|
| 24 |
-
Sandbox execution agent.
|
| 25 |
-
Priority: HF VS Code Space HTTP API β local subprocess
|
| 26 |
-
"""
|
| 27 |
-
|
| 28 |
def __init__(self, ws_manager=None, ai_router=None):
|
| 29 |
super().__init__("SandboxAgent", ws_manager, ai_router)
|
| 30 |
os.makedirs(WORKSPACE, exist_ok=True)
|
| 31 |
-
self._hf_available: Optional[bool] = None # lazy probe
|
| 32 |
-
|
| 33 |
-
# βββ Router βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 34 |
|
| 35 |
async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
|
| 36 |
session_id = kwargs.get("session_id", "")
|
| 37 |
-
task_id
|
|
|
|
| 38 |
task_lower = task.lower()
|
| 39 |
|
| 40 |
if "execute" in task_lower or "run" in task_lower or "terminal" in task_lower:
|
|
@@ -42,7 +32,7 @@ class SandboxAgent(BaseAgent):
|
|
| 42 |
return await self.execute(cmd, task_id=task_id, session_id=session_id)
|
| 43 |
elif "write file" in task_lower or "create file" in task_lower:
|
| 44 |
filename = context.get("filename", "output.txt")
|
| 45 |
-
content
|
| 46 |
return await self.write_file(filename, content, task_id=task_id, session_id=session_id)
|
| 47 |
elif "read file" in task_lower:
|
| 48 |
filename = context.get("filename", "")
|
|
@@ -52,70 +42,30 @@ class SandboxAgent(BaseAgent):
|
|
| 52 |
else:
|
| 53 |
return await self.execute(task, task_id=task_id, session_id=session_id)
|
| 54 |
|
| 55 |
-
# βββ HF VS Code Probe βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 56 |
-
|
| 57 |
-
async def _probe_hf_vscode(self) -> bool:
|
| 58 |
-
"""Check if HF VS Code Space is reachable."""
|
| 59 |
-
if not HF_VSCODE_URL:
|
| 60 |
-
return False
|
| 61 |
-
try:
|
| 62 |
-
async with httpx.AsyncClient(timeout=5.0) as client:
|
| 63 |
-
resp = await client.get(f"{HF_VSCODE_URL}/", headers=_hf_headers())
|
| 64 |
-
return resp.status_code < 500
|
| 65 |
-
except Exception:
|
| 66 |
-
return False
|
| 67 |
-
|
| 68 |
-
async def _hf_execute(self, command: str) -> str:
|
| 69 |
-
"""Execute command via HF VS Code Space HTTP API."""
|
| 70 |
-
headers = _hf_headers()
|
| 71 |
-
payload = {"command": command, "cwd": WORKSPACE}
|
| 72 |
-
async with httpx.AsyncClient(timeout=60.0) as client:
|
| 73 |
-
resp = await client.post(
|
| 74 |
-
f"{HF_VSCODE_URL}/api/execute",
|
| 75 |
-
headers=headers,
|
| 76 |
-
json=payload,
|
| 77 |
-
)
|
| 78 |
-
resp.raise_for_status()
|
| 79 |
-
data = resp.json()
|
| 80 |
-
return data.get("output", data.get("result", str(data)))
|
| 81 |
-
|
| 82 |
# βββ Terminal Execution βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 83 |
|
| 84 |
async def execute(
|
| 85 |
self,
|
| 86 |
command: str,
|
| 87 |
cwd: str = "",
|
| 88 |
-
timeout: int =
|
| 89 |
task_id: str = "",
|
| 90 |
session_id: str = "",
|
| 91 |
) -> str:
|
|
|
|
| 92 |
work_dir = cwd or WORKSPACE
|
| 93 |
|
| 94 |
# Safety: block dangerous commands
|
| 95 |
blocked = ["rm -rf /", ":(){ :|:& };:", "mkfs", "shutdown", "reboot", "halt", "dd if=/dev/"]
|
| 96 |
for b in blocked:
|
| 97 |
if b in command:
|
| 98 |
-
return f"β Blocked dangerous command: {command[:
|
| 99 |
-
|
| 100 |
-
await self.emit(task_id, "sandbox_exec", {
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
self._hf_available = await self._probe_hf_vscode()
|
| 106 |
-
if self._hf_available:
|
| 107 |
-
try:
|
| 108 |
-
result = await self._hf_execute(command)
|
| 109 |
-
await self.emit(task_id, "sandbox_result", {
|
| 110 |
-
"source": "hf_vscode", "command": command[:100],
|
| 111 |
-
"success": True, "output_length": len(result),
|
| 112 |
-
}, session_id)
|
| 113 |
-
return result[:4000]
|
| 114 |
-
except Exception as e:
|
| 115 |
-
log.warning("HF VSCode execution failed, falling back to local", error=str(e))
|
| 116 |
-
self._hf_available = False
|
| 117 |
-
|
| 118 |
-
# Local subprocess fallback
|
| 119 |
try:
|
| 120 |
proc = await asyncio.create_subprocess_shell(
|
| 121 |
command,
|
|
@@ -125,15 +75,17 @@ class SandboxAgent(BaseAgent):
|
|
| 125 |
)
|
| 126 |
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
| 127 |
output = stdout.decode("utf-8", errors="replace")
|
| 128 |
-
err
|
| 129 |
|
| 130 |
-
result = output[:
|
| 131 |
if err and proc.returncode != 0:
|
| 132 |
result += f"\nβ οΈ stderr:\n{err[:500]}"
|
| 133 |
|
| 134 |
await self.emit(task_id, "sandbox_result", {
|
| 135 |
-
"
|
| 136 |
-
"exit_code": proc.returncode,
|
|
|
|
|
|
|
| 137 |
}, session_id)
|
| 138 |
|
| 139 |
return result or f"Command executed (exit code: {proc.returncode})"
|
|
@@ -144,27 +96,44 @@ class SandboxAgent(BaseAgent):
|
|
| 144 |
|
| 145 |
# βββ File Operations ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 146 |
|
| 147 |
-
async def write_file(
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
filepath = os.path.join(WORKSPACE, filename)
|
| 150 |
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
| 151 |
try:
|
| 152 |
with open(filepath, "w", encoding="utf-8") as f:
|
| 153 |
f.write(content)
|
| 154 |
await self.emit(task_id, "file_written", {
|
| 155 |
-
"filename": filename,
|
| 156 |
-
"
|
|
|
|
|
|
|
| 157 |
}, session_id)
|
| 158 |
-
return f"β
File written: `{filename}` ({len(content)} chars)"
|
| 159 |
except Exception as e:
|
| 160 |
return f"β Write failed: {str(e)}"
|
| 161 |
|
| 162 |
-
async def read_file(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
filepath = os.path.join(WORKSPACE, filename)
|
| 164 |
try:
|
| 165 |
with open(filepath, "r", encoding="utf-8") as f:
|
| 166 |
content = f.read()
|
| 167 |
-
await self.emit(task_id, "file_read", {
|
|
|
|
|
|
|
|
|
|
| 168 |
return content[:5000]
|
| 169 |
except FileNotFoundError:
|
| 170 |
return f"β File not found: {filename}"
|
|
@@ -172,76 +141,109 @@ class SandboxAgent(BaseAgent):
|
|
| 172 |
return f"β Read failed: {str(e)}"
|
| 173 |
|
| 174 |
async def list_files(self, path: str = "") -> List[str]:
|
|
|
|
| 175 |
target = os.path.join(WORKSPACE, path) if path else WORKSPACE
|
| 176 |
-
result = []
|
| 177 |
try:
|
|
|
|
| 178 |
for root, dirs, files in os.walk(target):
|
| 179 |
-
|
|
|
|
| 180 |
for f in files:
|
| 181 |
rel = os.path.relpath(os.path.join(root, f), WORKSPACE)
|
| 182 |
result.append(rel)
|
| 183 |
-
if len(result) >
|
| 184 |
break
|
|
|
|
| 185 |
except Exception:
|
| 186 |
-
|
| 187 |
-
return result
|
| 188 |
|
| 189 |
# βββ Git Operations βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 190 |
|
| 191 |
-
async def git_operation(
|
| 192 |
-
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
task_lower = task.lower()
|
| 195 |
|
| 196 |
if "clone" in task_lower:
|
|
|
|
| 197 |
words = task.split()
|
| 198 |
-
urls
|
| 199 |
if urls:
|
| 200 |
-
|
| 201 |
-
|
| 202 |
return "β No git URL found in task."
|
|
|
|
| 203 |
elif "commit" in task_lower:
|
| 204 |
-
msg
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
elif "push" in task_lower:
|
| 209 |
return await self.execute("git push", cwd=work_dir, task_id=task_id, session_id=session_id)
|
|
|
|
| 210 |
elif "status" in task_lower:
|
| 211 |
return await self.execute("git status", cwd=work_dir, task_id=task_id, session_id=session_id)
|
|
|
|
| 212 |
elif "log" in task_lower:
|
| 213 |
return await self.execute("git log --oneline -10", cwd=work_dir, task_id=task_id, session_id=session_id)
|
|
|
|
| 214 |
elif "init" in task_lower:
|
| 215 |
return await self.execute("git init && git add -A", cwd=work_dir, task_id=task_id, session_id=session_id)
|
|
|
|
| 216 |
else:
|
| 217 |
return await self.execute(task, cwd=work_dir, task_id=task_id, session_id=session_id)
|
| 218 |
|
| 219 |
# βββ Package Management βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 220 |
|
| 221 |
-
async def install_packages(
|
| 222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
pkg_str = " ".join(packages)
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
return await self.execute(cmd, task_id=task_id, session_id=session_id)
|
| 229 |
|
|
|
|
|
|
|
| 230 |
async def get_workspace_info(self) -> Dict:
|
|
|
|
| 231 |
files = await self.list_files()
|
| 232 |
try:
|
| 233 |
disk = await self.execute("df -h /tmp | tail -1")
|
| 234 |
except Exception:
|
| 235 |
disk = "N/A"
|
| 236 |
return {
|
| 237 |
-
"path": WORKSPACE,
|
| 238 |
-
"
|
| 239 |
-
"
|
|
|
|
| 240 |
}
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
def _hf_headers() -> Dict:
|
| 244 |
-
h = {"Content-Type": "application/json"}
|
| 245 |
-
if HF_TOKEN:
|
| 246 |
-
h["Authorization"] = f"Bearer {HF_TOKEN}"
|
| 247 |
-
return h
|
|
|
|
| 1 |
"""
|
| 2 |
+
SandboxAgent β Persistent VS Code sandbox execution (Devin-style)
|
| 3 |
+
Controls file system, terminal, git operations in workspace
|
|
|
|
| 4 |
"""
|
| 5 |
import asyncio
|
| 6 |
import json
|
| 7 |
import os
|
| 8 |
+
import subprocess
|
| 9 |
+
import tempfile
|
| 10 |
from typing import Dict, List, Optional
|
|
|
|
|
|
|
| 11 |
import structlog
|
| 12 |
from .base_agent import BaseAgent
|
| 13 |
|
| 14 |
log = structlog.get_logger()
|
| 15 |
|
| 16 |
+
WORKSPACE = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
|
|
|
|
|
|
|
| 17 |
|
| 18 |
|
| 19 |
class SandboxAgent(BaseAgent):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
def __init__(self, ws_manager=None, ai_router=None):
|
| 21 |
super().__init__("SandboxAgent", ws_manager, ai_router)
|
| 22 |
os.makedirs(WORKSPACE, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
|
| 25 |
session_id = kwargs.get("session_id", "")
|
| 26 |
+
task_id = kwargs.get("task_id", "")
|
| 27 |
+
|
| 28 |
task_lower = task.lower()
|
| 29 |
|
| 30 |
if "execute" in task_lower or "run" in task_lower or "terminal" in task_lower:
|
|
|
|
| 32 |
return await self.execute(cmd, task_id=task_id, session_id=session_id)
|
| 33 |
elif "write file" in task_lower or "create file" in task_lower:
|
| 34 |
filename = context.get("filename", "output.txt")
|
| 35 |
+
content = context.get("content", "")
|
| 36 |
return await self.write_file(filename, content, task_id=task_id, session_id=session_id)
|
| 37 |
elif "read file" in task_lower:
|
| 38 |
filename = context.get("filename", "")
|
|
|
|
| 42 |
else:
|
| 43 |
return await self.execute(task, task_id=task_id, session_id=session_id)
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
# βββ Terminal Execution βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 46 |
|
| 47 |
async def execute(
|
| 48 |
self,
|
| 49 |
command: str,
|
| 50 |
cwd: str = "",
|
| 51 |
+
timeout: int = 30,
|
| 52 |
task_id: str = "",
|
| 53 |
session_id: str = "",
|
| 54 |
) -> str:
|
| 55 |
+
"""Execute shell command in sandbox workspace."""
|
| 56 |
work_dir = cwd or WORKSPACE
|
| 57 |
|
| 58 |
# Safety: block dangerous commands
|
| 59 |
blocked = ["rm -rf /", ":(){ :|:& };:", "mkfs", "shutdown", "reboot", "halt", "dd if=/dev/"]
|
| 60 |
for b in blocked:
|
| 61 |
if b in command:
|
| 62 |
+
return f"β Blocked dangerous command: {command[:50]}"
|
| 63 |
+
|
| 64 |
+
await self.emit(task_id, "sandbox_exec", {
|
| 65 |
+
"command": command[:200],
|
| 66 |
+
"cwd": work_dir,
|
| 67 |
+
}, session_id)
|
| 68 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
try:
|
| 70 |
proc = await asyncio.create_subprocess_shell(
|
| 71 |
command,
|
|
|
|
| 75 |
)
|
| 76 |
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
| 77 |
output = stdout.decode("utf-8", errors="replace")
|
| 78 |
+
err = stderr.decode("utf-8", errors="replace")
|
| 79 |
|
| 80 |
+
result = output[:3000]
|
| 81 |
if err and proc.returncode != 0:
|
| 82 |
result += f"\nβ οΈ stderr:\n{err[:500]}"
|
| 83 |
|
| 84 |
await self.emit(task_id, "sandbox_result", {
|
| 85 |
+
"command": command[:100],
|
| 86 |
+
"exit_code": proc.returncode,
|
| 87 |
+
"output_length": len(output),
|
| 88 |
+
"success": proc.returncode == 0,
|
| 89 |
}, session_id)
|
| 90 |
|
| 91 |
return result or f"Command executed (exit code: {proc.returncode})"
|
|
|
|
| 96 |
|
| 97 |
# βββ File Operations ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 98 |
|
| 99 |
+
async def write_file(
|
| 100 |
+
self,
|
| 101 |
+
filename: str,
|
| 102 |
+
content: str,
|
| 103 |
+
task_id: str = "",
|
| 104 |
+
session_id: str = "",
|
| 105 |
+
) -> str:
|
| 106 |
+
"""Write file to workspace."""
|
| 107 |
filepath = os.path.join(WORKSPACE, filename)
|
| 108 |
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
| 109 |
try:
|
| 110 |
with open(filepath, "w", encoding="utf-8") as f:
|
| 111 |
f.write(content)
|
| 112 |
await self.emit(task_id, "file_written", {
|
| 113 |
+
"filename": filename,
|
| 114 |
+
"size": len(content),
|
| 115 |
+
"lines": len(content.split("\n")),
|
| 116 |
+
"path": filepath,
|
| 117 |
}, session_id)
|
| 118 |
+
return f"β
File written: `{filename}` ({len(content)} chars, {len(content.split(chr(10)))} lines)"
|
| 119 |
except Exception as e:
|
| 120 |
return f"β Write failed: {str(e)}"
|
| 121 |
|
| 122 |
+
async def read_file(
|
| 123 |
+
self,
|
| 124 |
+
filename: str,
|
| 125 |
+
task_id: str = "",
|
| 126 |
+
session_id: str = "",
|
| 127 |
+
) -> str:
|
| 128 |
+
"""Read file from workspace."""
|
| 129 |
filepath = os.path.join(WORKSPACE, filename)
|
| 130 |
try:
|
| 131 |
with open(filepath, "r", encoding="utf-8") as f:
|
| 132 |
content = f.read()
|
| 133 |
+
await self.emit(task_id, "file_read", {
|
| 134 |
+
"filename": filename,
|
| 135 |
+
"size": len(content),
|
| 136 |
+
}, session_id)
|
| 137 |
return content[:5000]
|
| 138 |
except FileNotFoundError:
|
| 139 |
return f"β File not found: {filename}"
|
|
|
|
| 141 |
return f"β Read failed: {str(e)}"
|
| 142 |
|
| 143 |
async def list_files(self, path: str = "") -> List[str]:
|
| 144 |
+
"""List files in workspace."""
|
| 145 |
target = os.path.join(WORKSPACE, path) if path else WORKSPACE
|
|
|
|
| 146 |
try:
|
| 147 |
+
result = []
|
| 148 |
for root, dirs, files in os.walk(target):
|
| 149 |
+
# Skip hidden and cache dirs
|
| 150 |
+
dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__" and d != "node_modules"]
|
| 151 |
for f in files:
|
| 152 |
rel = os.path.relpath(os.path.join(root, f), WORKSPACE)
|
| 153 |
result.append(rel)
|
| 154 |
+
if len(result) > 100:
|
| 155 |
break
|
| 156 |
+
return result
|
| 157 |
except Exception:
|
| 158 |
+
return []
|
|
|
|
| 159 |
|
| 160 |
# βββ Git Operations βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 161 |
|
| 162 |
+
async def git_operation(
|
| 163 |
+
self,
|
| 164 |
+
task: str,
|
| 165 |
+
repo_path: str = "",
|
| 166 |
+
task_id: str = "",
|
| 167 |
+
session_id: str = "",
|
| 168 |
+
) -> str:
|
| 169 |
+
"""Perform git operations in workspace."""
|
| 170 |
+
work_dir = repo_path or WORKSPACE
|
| 171 |
task_lower = task.lower()
|
| 172 |
|
| 173 |
if "clone" in task_lower:
|
| 174 |
+
# Extract URL
|
| 175 |
words = task.split()
|
| 176 |
+
urls = [w for w in words if "github.com" in w or "gitlab.com" in w or ".git" in w]
|
| 177 |
if urls:
|
| 178 |
+
url = urls[0]
|
| 179 |
+
return await self.execute(f"git clone {url}", cwd=WORKSPACE, task_id=task_id, session_id=session_id)
|
| 180 |
return "β No git URL found in task."
|
| 181 |
+
|
| 182 |
elif "commit" in task_lower:
|
| 183 |
+
msg = task.replace("commit", "").strip() or "God Agent automated commit"
|
| 184 |
+
cmds = [
|
| 185 |
+
"git add -A",
|
| 186 |
+
f'git commit -m "{msg}"',
|
| 187 |
+
]
|
| 188 |
+
results = []
|
| 189 |
+
for cmd in cmds:
|
| 190 |
+
r = await self.execute(cmd, cwd=work_dir, task_id=task_id, session_id=session_id)
|
| 191 |
+
results.append(r)
|
| 192 |
+
return "\n".join(results)
|
| 193 |
+
|
| 194 |
elif "push" in task_lower:
|
| 195 |
return await self.execute("git push", cwd=work_dir, task_id=task_id, session_id=session_id)
|
| 196 |
+
|
| 197 |
elif "status" in task_lower:
|
| 198 |
return await self.execute("git status", cwd=work_dir, task_id=task_id, session_id=session_id)
|
| 199 |
+
|
| 200 |
elif "log" in task_lower:
|
| 201 |
return await self.execute("git log --oneline -10", cwd=work_dir, task_id=task_id, session_id=session_id)
|
| 202 |
+
|
| 203 |
elif "init" in task_lower:
|
| 204 |
return await self.execute("git init && git add -A", cwd=work_dir, task_id=task_id, session_id=session_id)
|
| 205 |
+
|
| 206 |
else:
|
| 207 |
return await self.execute(task, cwd=work_dir, task_id=task_id, session_id=session_id)
|
| 208 |
|
| 209 |
# βββ Package Management βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 210 |
|
| 211 |
+
async def install_packages(
|
| 212 |
+
self,
|
| 213 |
+
packages: List[str],
|
| 214 |
+
manager: str = "pip",
|
| 215 |
+
task_id: str = "",
|
| 216 |
+
session_id: str = "",
|
| 217 |
+
) -> str:
|
| 218 |
+
"""Install packages in workspace."""
|
| 219 |
pkg_str = " ".join(packages)
|
| 220 |
+
if manager == "pip":
|
| 221 |
+
cmd = f"pip install {pkg_str}"
|
| 222 |
+
elif manager == "npm":
|
| 223 |
+
cmd = f"npm install {pkg_str}"
|
| 224 |
+
elif manager == "pnpm":
|
| 225 |
+
cmd = f"pnpm add {pkg_str}"
|
| 226 |
+
else:
|
| 227 |
+
cmd = f"{manager} install {pkg_str}"
|
| 228 |
+
|
| 229 |
+
await self.emit(task_id, "installing_packages", {
|
| 230 |
+
"manager": manager,
|
| 231 |
+
"packages": packages,
|
| 232 |
+
}, session_id)
|
| 233 |
return await self.execute(cmd, task_id=task_id, session_id=session_id)
|
| 234 |
|
| 235 |
+
# βββ Workspace Info βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 236 |
+
|
| 237 |
async def get_workspace_info(self) -> Dict:
|
| 238 |
+
"""Get workspace status."""
|
| 239 |
files = await self.list_files()
|
| 240 |
try:
|
| 241 |
disk = await self.execute("df -h /tmp | tail -1")
|
| 242 |
except Exception:
|
| 243 |
disk = "N/A"
|
| 244 |
return {
|
| 245 |
+
"path": WORKSPACE,
|
| 246 |
+
"file_count": len(files),
|
| 247 |
+
"files": files[:20],
|
| 248 |
+
"disk_usage": disk,
|
| 249 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|