- 新增 CLIPluginAdapter 统一接口 (backend/app/core/agent_adapter.py) - 新增 LLM 服务层,支持 Anthropic/OpenAI/DeepSeek/Ollama (backend/app/services/llm_service.py) - 新增 Agent 执行引擎,支持文件锁自动管理 (backend/app/services/agent_executor.py) - 新增 NativeLLMAgent 原生 LLM 适配器 (backend/app/adapters/native_llm_agent.py) - 新增进程管理器 (backend/app/services/process_manager.py) - 新增 Agent 控制 API (backend/app/routers/agents_control.py) - 新增 WebSocket 实时通信 (backend/app/routers/websocket.py) - 更新前端 AgentsPage,支持启动/停止 Agent - 测试通过:Agent 启动、批量操作、栅栏同步 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
116 lines
3.7 KiB
Python
116 lines
3.7 KiB
Python
"""
|
|
文件锁服务 - 管理 Agent 对文件的访问锁
|
|
"""
|
|
|
|
import asyncio
|
|
from datetime import datetime
|
|
from typing import Dict, List, Optional
|
|
from dataclasses import dataclass, asdict
|
|
|
|
from .storage import get_storage
|
|
|
|
|
|
@dataclass
|
|
class LockInfo:
|
|
"""文件锁信息"""
|
|
file_path: str
|
|
agent_id: str
|
|
acquired_at: str
|
|
agent_name: str = ""
|
|
|
|
@property
|
|
def elapsed_seconds(self) -> int:
|
|
acquired_time = datetime.fromisoformat(self.acquired_at)
|
|
return int((datetime.now() - acquired_time).total_seconds())
|
|
|
|
@property
|
|
def elapsed_display(self) -> str:
|
|
seconds = self.elapsed_seconds
|
|
if seconds < 60:
|
|
return f"{seconds}s ago"
|
|
minutes = seconds // 60
|
|
secs = seconds % 60
|
|
return f"{minutes}m {secs:02d}s ago"
|
|
|
|
|
|
class FileLockService:
|
|
"""文件锁服务"""
|
|
|
|
LOCKS_FILE = "cache/file_locks.json"
|
|
LOCK_TIMEOUT = 300
|
|
|
|
def __init__(self):
|
|
self._storage = get_storage()
|
|
self._lock = asyncio.Lock()
|
|
|
|
async def _load_locks(self) -> Dict[str, Dict]:
|
|
return await self._storage.read_json(self.LOCKS_FILE)
|
|
|
|
async def _save_locks(self, locks: Dict[str, Dict]) -> None:
|
|
await self._storage.write_json(self.LOCKS_FILE, locks)
|
|
|
|
def _is_expired(self, lock_data: Dict) -> bool:
|
|
acquired_at = datetime.fromisoformat(lock_data["acquired_at"])
|
|
return (datetime.now() - acquired_at).total_seconds() >= self.LOCK_TIMEOUT
|
|
|
|
async def _cleanup_expired(self, locks: Dict[str, Dict]) -> Dict[str, Dict]:
|
|
return {k: v for k, v in locks.items() if not self._is_expired(v)}
|
|
|
|
async def acquire_lock(self, file_path: str, agent_id: str, agent_name: str = "") -> bool:
|
|
async with self._lock:
|
|
locks = await self._cleanup_expired(await self._load_locks())
|
|
|
|
if file_path in locks and locks[file_path]["agent_id"] != agent_id:
|
|
return False
|
|
|
|
locks[file_path] = asdict(LockInfo(
|
|
file_path=file_path,
|
|
agent_id=agent_id,
|
|
acquired_at=datetime.now().isoformat(),
|
|
agent_name=agent_name
|
|
))
|
|
await self._save_locks(locks)
|
|
return True
|
|
|
|
async def release_lock(self, file_path: str, agent_id: str) -> bool:
|
|
async with self._lock:
|
|
locks = await self._load_locks()
|
|
|
|
if file_path not in locks or locks[file_path]["agent_id"] != agent_id:
|
|
return False
|
|
|
|
del locks[file_path]
|
|
await self._save_locks(locks)
|
|
return True
|
|
|
|
async def get_locks(self) -> List[LockInfo]:
|
|
locks = await self._cleanup_expired(await self._load_locks())
|
|
return [LockInfo(**data) for data in locks.values()]
|
|
|
|
async def check_locked(self, file_path: str) -> Optional[str]:
|
|
locks = await self._cleanup_expired(await self._load_locks())
|
|
return locks.get(file_path, {}).get("agent_id")
|
|
|
|
async def get_agent_locks(self, agent_id: str) -> List[LockInfo]:
|
|
return [lock for lock in await self.get_locks() if lock.agent_id == agent_id]
|
|
|
|
async def release_all_agent_locks(self, agent_id: str) -> int:
|
|
async with self._lock:
|
|
locks = await self._load_locks()
|
|
to_remove = [k for k, v in locks.items() if v["agent_id"] == agent_id]
|
|
for k in to_remove:
|
|
del locks[k]
|
|
await self._save_locks(locks)
|
|
return len(to_remove)
|
|
|
|
|
|
# 简化单例实现
|
|
_file_lock_service: Optional[FileLockService] = None
|
|
|
|
|
|
def get_file_lock_service() -> FileLockService:
|
|
global _file_lock_service
|
|
if _file_lock_service is None:
|
|
_file_lock_service = FileLockService()
|
|
return _file_lock_service
|