Merge branch 'master' into feature/expand-project-with-ai

This commit is contained in:
Leon van Zyl
2026-01-10 10:22:12 +02:00
committed by GitHub
23 changed files with 985 additions and 183 deletions

View File

@@ -74,6 +74,7 @@ class AgentProcessManager:
self.started_at: datetime | None = None
self._output_task: asyncio.Task | None = None
self.yolo_mode: bool = False # YOLO mode for rapid prototyping
self.model: str | None = None # Model being used
# Support multiple callbacks (for multiple WebSocket clients)
self._output_callbacks: Set[Callable[[str], Awaitable[None]]] = set()
@@ -214,12 +215,13 @@ class AgentProcessManager:
self.status = "stopped"
self._remove_lock()
async def start(self, yolo_mode: bool = False) -> tuple[bool, str]:
async def start(self, yolo_mode: bool = False, model: str | None = None) -> tuple[bool, str]:
"""
Start the agent as a subprocess.
Args:
yolo_mode: If True, run in YOLO mode (no browser testing)
model: Model to use (e.g., claude-opus-4-5-20251101)
Returns:
Tuple of (success, message)
@@ -230,8 +232,9 @@ class AgentProcessManager:
if not self._check_lock():
return False, "Another agent instance is already running for this project"
# Store YOLO mode for status queries
# Store for status queries
self.yolo_mode = yolo_mode
self.model = model
# Build command - pass absolute path to project directory
cmd = [
@@ -241,6 +244,10 @@ class AgentProcessManager:
str(self.project_dir.resolve()),
]
# Add --model flag if model is specified
if model:
cmd.extend(["--model", model])
# Add --yolo flag if YOLO mode is enabled
if yolo_mode:
cmd.append("--yolo")
@@ -306,6 +313,7 @@ class AgentProcessManager:
self.process = None
self.started_at = None
self.yolo_mode = False # Reset YOLO mode
self.model = None # Reset model
return True, "Agent stopped"
except Exception as e:
@@ -387,6 +395,7 @@ class AgentProcessManager:
"pid": self.pid,
"started_at": self.started_at.isoformat() if self.started_at else None,
"yolo_mode": self.yolo_mode,
"model": self.model,
}
@@ -423,3 +432,73 @@ async def cleanup_all_managers() -> None:
with _managers_lock:
_managers.clear()
def cleanup_orphaned_locks() -> int:
"""
Clean up orphaned lock files from previous server runs.
Scans all registered projects for .agent.lock files and removes them
if the referenced process is no longer running.
Returns:
Number of orphaned lock files cleaned up
"""
import sys
root = Path(__file__).parent.parent.parent
if str(root) not in sys.path:
sys.path.insert(0, str(root))
from registry import list_registered_projects
cleaned = 0
try:
projects = list_registered_projects()
for name, info in projects.items():
project_path = Path(info.get("path", ""))
if not project_path.exists():
continue
lock_file = project_path / ".agent.lock"
if not lock_file.exists():
continue
try:
pid_str = lock_file.read_text().strip()
pid = int(pid_str)
# Check if process is still running
if psutil.pid_exists(pid):
try:
proc = psutil.Process(pid)
cmdline = " ".join(proc.cmdline())
if "autonomous_agent_demo.py" in cmdline:
# Process is still running, don't remove
logger.info(
"Found running agent for project '%s' (PID %d)",
name, pid
)
continue
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
# Process not running or not our agent - remove stale lock
lock_file.unlink(missing_ok=True)
cleaned += 1
logger.info("Removed orphaned lock file for project '%s'", name)
except (ValueError, OSError) as e:
# Invalid lock file content - remove it
logger.warning(
"Removing invalid lock file for project '%s': %s", name, e
)
lock_file.unlink(missing_ok=True)
cleaned += 1
except Exception as e:
logger.error("Error during orphan cleanup: %s", e)
if cleaned:
logger.info("Cleaned up %d orphaned lock file(s)", cleaned)
return cleaned