feat: Add agent workspace system and runtime management

- Add agent core modules (agent_core, factory, registry, skill_loader)
- Add runtime system for agent execution management
- Add REST API for agents, workspaces, and runtime control
- Add process supervisor for agent lifecycle management
- Add workspace template system with agent profiles
- Add frontend RuntimeView and runtime API integration
- Add per-agent skill workspaces for smoke_fullstack run
- Refactor skill system with active/installed separation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 16:43:29 +08:00
parent 2daf5717ba
commit 59b44545d0
121 changed files with 8384 additions and 358 deletions

41
backend/process/models.py Normal file
View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
"""Data models for lightweight process supervision."""
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Dict
class ProcessRunState(str, Enum):
"""Execution state for supervised runs."""
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
@dataclass
class ProcessRun:
"""Represents a supervised process run."""
run_id: str
command: str
scope_key: str
state: ProcessRunState = ProcessRunState.PENDING
metadata: Dict[str, Any] = field(default_factory=dict)
created_at: datetime = field(default_factory=datetime.utcnow)
updated_at: datetime = field(default_factory=datetime.utcnow)
def to_dict(self) -> Dict[str, Any]:
return {
"run_id": self.run_id,
"command": self.command,
"scope_key": self.scope_key,
"state": self.state.value,
"metadata": self.metadata,
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat(),
}

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
"""Registry for managing supervised process metadata."""
from threading import Lock
from typing import Dict, Iterable, Optional
from .models import ProcessRun
class RunRegistry:
"""In-memory registry for tracked process runs."""
def __init__(self) -> None:
self._runs: Dict[str, ProcessRun] = {}
self._lock = Lock()
def add(self, run: ProcessRun) -> None:
with self._lock:
self._runs[run.run_id] = run
def get(self, run_id: str) -> Optional[ProcessRun]:
with self._lock:
return self._runs.get(run_id)
def list(self) -> Iterable[ProcessRun]:
with self._lock:
return list(self._runs.values())
def update(self, run: ProcessRun) -> None:
with self._lock:
self._runs[run.run_id] = run
def remove(self, run_id: str) -> None:
with self._lock:
self._runs.pop(run_id, None)

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
"""Minimal supervisor for scripted tasks and long-running utilities."""
from datetime import datetime
from typing import Any, Dict, Iterable, Optional
from .models import ProcessRun, ProcessRunState
from .registry import RunRegistry
class ProcessSupervisor:
"""Tracks supervised runs without executing real processes yet."""
def __init__(self, registry: Optional[RunRegistry] = None) -> None:
self.registry = registry or RunRegistry()
def spawn(
self,
run_id: str,
command: str,
scope_key: str,
metadata: Optional[Dict[str, Any]] = None,
) -> ProcessRun:
run = ProcessRun(
run_id=run_id,
command=command,
scope_key=scope_key,
metadata=metadata or {},
)
run.state = ProcessRunState.RUNNING
run.updated_at = datetime.utcnow()
self.registry.add(run)
return run
def update_state(
self,
run_id: str,
state: ProcessRunState,
metadata: Optional[Dict[str, Any]] = None,
) -> Optional[ProcessRun]:
run = self.registry.get(run_id)
if not run:
return None
run.state = state
run.metadata.update(metadata or {})
run.updated_at = datetime.utcnow()
self.registry.update(run)
return run
def cancel(self, run_id: str, reason: Optional[str] = None) -> Optional[ProcessRun]:
run = self.registry.get(run_id)
if not run:
return None
run.state = ProcessRunState.CANCELLED
run.metadata.setdefault("cancel_reason", reason or "manual")
run.updated_at = datetime.utcnow()
self.registry.update(run)
return run
def list_runs(self) -> Iterable[ProcessRun]:
return self.registry.list()