feat: add sandbox and local impl

This commit is contained in:
Henry Li
2026-01-14 07:19:34 +08:00
parent 4b5f529903
commit 57a02acb59
8 changed files with 432 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
from .local_sandbox_provider import LocalSandboxProvider
__all__ = ["LocalSandboxProvider"]
+116
View File
@@ -0,0 +1,116 @@
import fnmatch
from pathlib import Path
IGNORE_PATTERNS = [
# Version Control
".git",
".svn",
".hg",
".bzr",
# Dependencies
"node_modules",
"__pycache__",
".venv",
"venv",
".env",
"env",
".tox",
".nox",
".eggs",
"*.egg-info",
"site-packages",
# Build outputs
"dist",
"build",
".next",
".nuxt",
".output",
".turbo",
"target",
"out",
# IDE & Editor
".idea",
".vscode",
"*.swp",
"*.swo",
"*~",
".project",
".classpath",
".settings",
# OS generated
".DS_Store",
"Thumbs.db",
"desktop.ini",
"*.lnk",
# Logs & temp files
"*.log",
"*.tmp",
"*.temp",
"*.bak",
"*.cache",
".cache",
"logs",
# Coverage & test artifacts
".coverage",
"coverage",
".nyc_output",
"htmlcov",
".pytest_cache",
".mypy_cache",
".ruff_cache",
]
def _should_ignore(name: str) -> bool:
"""Check if a file/directory name matches any ignore pattern."""
for pattern in IGNORE_PATTERNS:
if fnmatch.fnmatch(name, pattern):
return True
return False
def list_dir(path: str, max_depth: int = 2) -> list[str]:
"""
List files and directories up to max_depth levels deep.
Args:
path: The root directory path to list.
max_depth: Maximum depth to traverse (default: 2).
1 = only direct children, 2 = children + grandchildren, etc.
Returns:
A list of absolute paths for files and directories,
excluding items matching IGNORE_PATTERNS.
"""
result: list[str] = []
root_path = Path(path).resolve()
if not root_path.is_dir():
return result
def _traverse(current_path: Path, current_depth: int) -> None:
"""Recursively traverse directories up to max_depth."""
if current_depth > max_depth:
return
try:
for item in current_path.iterdir():
if _should_ignore(item.name):
continue
post_fix = "/" if item.is_dir() else ""
result.append(str(item.resolve()) + post_fix)
# Recurse into subdirectories if not at max depth
if item.is_dir() and current_depth < max_depth:
_traverse(item, current_depth + 1)
except PermissionError:
pass
_traverse(root_path, 1)
return sorted(result)
if __name__ == "__main__":
print("\n".join(list_dir("/Users/Henry/Desktop", max_depth=2)))
@@ -0,0 +1,46 @@
import os
import subprocess
from src.sandbox.local.list_dir import list_dir
from src.sandbox.sandbox import Sandbox
class LocalSandbox(Sandbox):
def __init__(self, id: str):
super().__init__(id)
def execute_command(self, command: str) -> str:
result = subprocess.run(
command,
executable="/bin/zsh",
shell=True,
capture_output=True,
text=True,
timeout=30,
)
output = result.stdout
if result.stderr:
output += f"\nStd Error:\n{result.stderr}" if output else result.stderr
if result.returncode != 0:
output += f"\nExit Code: {result.returncode}"
return output if output else "(no output)"
def list_dir(self, path: str, max_depth=2) -> list[str]:
return list_dir(path, max_depth)
def read_file(self, path: str) -> str:
with open(path, "r") as f:
return f.read()
def write_file(self, path: str, content: str, append: bool = False) -> None:
dir_path = os.path.dirname(path)
if dir_path:
os.makedirs(dir_path, exist_ok=True)
mode = "a" if append else "w"
with open(path, mode) as f:
f.write(content)
if __name__ == "__main__":
sandbox = LocalSandbox("test")
print(sandbox.list_dir("/Users/Henry/mnt"))
@@ -0,0 +1,21 @@
from src.sandbox.local.local_sandbox import LocalSandbox
from src.sandbox.sandbox import Sandbox
from src.sandbox.sandbox_provider import SandboxProvider
_singleton: LocalSandbox | None = None
class LocalSandboxProvider(SandboxProvider):
def acquire(self) -> Sandbox:
global _singleton
if _singleton is None:
_singleton = LocalSandbox("local")
return _singleton.id
def get(self, sandbox_id: str) -> None:
if _singleton is None:
self.acquire()
return _singleton
def release(self, sandbox_id: str) -> None:
pass