Antigravity-cloud / tasks /command_engine.py
Starboy001
Deploy Antigravity 24/7 Cloud Engine on ZeroGPU
ff9194c
Raw History Blame Contribute Delete
11.2 kB
"""
Universal 24/7 Command Engine & Multi-Project Sandboxing
Production-grade execution of arbitrary bash commands with process tracking,
timeout protection, and multi-project workspace isolation.
"""
import os
import sys
import time
import shutil
import asyncio
import logging
import psutil
from typing import Dict, List, Optional, Any
from pydantic import BaseModel, Field
logger = logging.getLogger("antigravity.command_engine")
# Base directory configuration
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECTS_ROOT = os.environ.get("PROJECTS_DIR", os.path.join(BASE_DIR, "projects"))
os.makedirs(PROJECTS_ROOT, exist_ok=True)
class CommandResult(BaseModel):
command: str
project_id: str
exit_code: int
stdout: str
stderr: str
duration_seconds: float
timestamp: float
is_success: bool
class ProjectMeta(BaseModel):
project_id: str
name: str
path: str
size_bytes: int
file_count: int
created_at: float
updated_at: float
class FileItem(BaseModel):
name: str
relative_path: str
is_directory: bool
size_bytes: int
modified_at: float
class RunningProcessTracker:
"""Tracks active subprocesses to enable cancellation and monitoring."""
def __init__(self):
self._active_processes: Dict[str, asyncio.subprocess.Process] = {}
def register(self, task_id: str, process: asyncio.subprocess.Process):
self._active_processes[task_id] = process
def unregister(self, task_id: str):
self._active_processes.pop(task_id, None)
async def terminate(self, task_id: str, timeout: float = 3.0) -> bool:
process = self._active_processes.get(task_id)
if not process:
return False
try:
if process.returncode is None:
process.terminate()
try:
await asyncio.wait_for(process.wait(), timeout=timeout)
except asyncio.TimeoutError:
process.kill()
await process.wait()
return True
except Exception as e:
logger.error(f"Error terminating process {task_id}: {e}")
return False
finally:
self._active_processes.pop(task_id, None)
# Global process tracker instance
process_tracker = RunningProcessTracker()
def get_project_dir(project_id: str) -> str:
"""Sanitize and resolve project sandbox directory."""
# Sanitize project_id to avoid path traversal
safe_id = "".join(c for c in project_id if c.isalnum() or c in ("-", "_", ".")).strip(".")
if not safe_id:
safe_id = "default"
project_path = os.path.abspath(os.path.join(PROJECTS_ROOT, safe_id))
# Ensure resolved path is within PROJECTS_ROOT
if not project_path.startswith(os.path.abspath(PROJECTS_ROOT)):
project_path = os.path.join(PROJECTS_ROOT, "default")
os.makedirs(project_path, exist_ok=True)
return project_path
async def run_bash_command(
command: str,
project_id: str = "default",
task_id: Optional[str] = None,
timeout_seconds: int = 600,
custom_env: Optional[Dict[str, str]] = None,
log_callback: Optional[Any] = None
) -> CommandResult:
"""
Executes an arbitrary bash command inside the sandboxed project directory.
Captures stdout/stderr in real-time and provides timeout & cancellation protection.
"""
start_time = time.time()
cwd = get_project_dir(project_id)
env = os.environ.copy()
env["HOME"] = os.path.expanduser("~")
env["PROJECT_DIR"] = cwd
env["PYTHONUNBUFFERED"] = "1"
if custom_env:
env.update(custom_env)
logger.info(f"[{project_id}] Executing command: {command} (timeout={timeout_seconds}s)")
if log_callback:
await log_callback(f"[$] {command}\n")
try:
process = await asyncio.create_subprocess_shell(
command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=cwd,
env=env,
shell=True
)
if task_id:
process_tracker.register(task_id, process)
stdout_chunks: List[str] = []
stderr_chunks: List[str] = []
async def read_stream(stream, chunks: List[str], prefix: str = ""):
while True:
line = await stream.readline()
if not line:
break
decoded = line.decode("utf-8", errors="replace")
chunks.append(decoded)
if log_callback:
await log_callback(f"{prefix}{decoded}")
# Stream stdout and stderr concurrently with timeout
try:
await asyncio.wait_for(
asyncio.gather(
read_stream(process.stdout, stdout_chunks),
read_stream(process.stderr, stderr_chunks, prefix="[STDERR] "),
process.wait()
),
timeout=float(timeout_seconds)
)
exit_code = process.returncode if process.returncode is not None else 0
except asyncio.TimeoutError:
logger.warning(f"Command timed out after {timeout_seconds}s: {command}")
process.kill()
await process.wait()
exit_code = -1
stderr_chunks.append(f"\n[ERROR] Command timed out after {timeout_seconds} seconds.\n")
duration = round(time.time() - start_time, 3)
full_stdout = "".join(stdout_chunks)
full_stderr = "".join(stderr_chunks)
return CommandResult(
command=command,
project_id=project_id,
exit_code=exit_code,
stdout=full_stdout,
stderr=full_stderr,
duration_seconds=duration,
timestamp=start_time,
is_success=(exit_code == 0)
)
except Exception as e:
duration = round(time.time() - start_time, 3)
err_msg = f"Failed to execute command: {str(e)}"
logger.error(err_msg, exc_info=True)
return CommandResult(
command=command,
project_id=project_id,
exit_code=1,
stdout="",
stderr=err_msg,
duration_seconds=duration,
timestamp=start_time,
is_success=False
)
finally:
if task_id:
process_tracker.unregister(task_id)
def list_projects() -> List[ProjectMeta]:
"""Lists all sandboxed project workspaces with metadata and disk footprints."""
projects: List[ProjectMeta] = []
if not os.path.exists(PROJECTS_ROOT):
return projects
for entry in os.scandir(PROJECTS_ROOT):
if entry.is_dir():
project_path = entry.path
total_size = 0
file_count = 0
try:
for root, _, files in os.walk(project_path):
for f in files:
fp = os.path.join(root, f)
if not os.path.islink(fp):
try:
total_size += os.path.getsize(fp)
file_count += 1
except OSError:
pass
stat = entry.stat()
projects.append(ProjectMeta(
project_id=entry.name,
name=entry.name,
path=project_path,
size_bytes=total_size,
file_count=file_count,
created_at=stat.st_ctime,
updated_at=stat.st_mtime
))
except Exception as e:
logger.warning(f"Error reading project {entry.name}: {e}")
# Sort most recently updated first
projects.sort(key=lambda p: p.updated_at, reverse=True)
return projects
def create_project(project_id: str, name: Optional[str] = None) -> ProjectMeta:
"""Creates a new project sandbox directory."""
path = get_project_dir(project_id)
stat = os.stat(path)
return ProjectMeta(
project_id=os.path.basename(path),
name=name or os.path.basename(path),
path=path,
size_bytes=0,
file_count=0,
created_at=stat.st_ctime,
updated_at=stat.st_mtime
)
def list_project_files(project_id: str, relative_subpath: str = "") -> List[FileItem]:
"""Lists files and folders inside a specific project workspace."""
base_proj_dir = get_project_dir(project_id)
target_dir = os.path.abspath(os.path.join(base_proj_dir, relative_subpath.lstrip("/")))
if not target_dir.startswith(base_proj_dir):
target_dir = base_proj_dir
if not os.path.exists(target_dir):
return []
items: List[FileItem] = []
try:
for entry in os.scandir(target_dir):
try:
stat = entry.stat()
rel_path = os.path.relpath(entry.path, base_proj_dir)
items.append(FileItem(
name=entry.name,
relative_path=rel_path,
is_directory=entry.is_dir(),
size_bytes=stat.st_size if not entry.is_dir() else 0,
modified_at=stat.st_mtime
))
except OSError:
continue
except Exception as e:
logger.error(f"Failed to list files for project {project_id}: {e}")
items.sort(key=lambda x: (not x.is_directory, x.name.lower()))
return items
def delete_project(project_id: str) -> bool:
"""Safely removes a project sandbox directory."""
if project_id.lower() in ("default", "system"):
return False
path = get_project_dir(project_id)
if os.path.exists(path) and path.startswith(os.path.abspath(PROJECTS_ROOT)):
shutil.rmtree(path, ignore_errors=True)
return True
return False
def get_system_telemetry() -> Dict[str, Any]:
"""Retrieves hardware, CPU, RAM, 50GB disk capacity, and OS telemetry."""
cpu_percent = psutil.cpu_percent(interval=None)
cpu_count = psutil.cpu_count(logical=True)
virtual_mem = psutil.virtual_memory()
# Hugging Face Spaces free tier gives 50GB disk
disk_usage = shutil.disk_usage(BASE_DIR)
total_gb = round(disk_usage.total / (1024 ** 3), 2)
used_gb = round(disk_usage.used / (1024 ** 3), 2)
free_gb = round(disk_usage.free / (1024 ** 3), 2)
disk_percent = round((disk_usage.used / disk_usage.total) * 100, 1)
boot_time = psutil.boot_time()
uptime_seconds = int(time.time() - boot_time)
return {
"cpu_usage_percent": cpu_percent,
"cpu_cores": cpu_count,
"memory_total_mb": round(virtual_mem.total / (1024 ** 2), 1),
"memory_used_mb": round(virtual_mem.used / (1024 ** 2), 1),
"memory_free_mb": round(virtual_mem.available / (1024 ** 2), 1),
"memory_percent": virtual_mem.percent,
"disk_total_gb": total_gb,
"disk_used_gb": used_gb,
"disk_free_gb": free_gb,
"disk_percent": disk_percent,
"uptime_seconds": uptime_seconds,
"active_processes_count": len(process_tracker._active_processes)
}