diff --git a/server/__init__.py b/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/api/__init__.py b/server/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/api/agents.py b/server/api/agents.py deleted file mode 100644 index ea4487e..0000000 --- a/server/api/agents.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Health-check и статус worker-профилей""" -from __future__ import annotations -import subprocess, json -from fastapi import APIRouter - -router = APIRouter(prefix="/api/v1/agents", tags=["agents"]) - -PROFILES = ["worker-code", "worker-fast", "worker-research", "worker-review"] -API_KEY = "sk-a345af809e8a26f0693b9405344edc8adc5b5a96" -HEALTH_URL = "http://localhost:20127/v1/chat/completions" - -MODEL_MAP = { - "worker-code": "opencode-go/kimi-k2.7-code", - "worker-fast": "opencode-go/kimi-k2.7-code", - "worker-research": "opencode-go/kimi-k2.7-code", - "worker-review": "opencode-go/kimi-k2.7-code", -} - -def _check_provider(model: str) -> str: - try: - import urllib.request, urllib.error - body = json.dumps({"model": model, "messages": [{"role": "user", "content": "Hi"}], "max_tokens": 1, "stream": False}).encode() - req = urllib.request.Request(HEALTH_URL, data=body, headers={"Content-Type": "application/json", "Authorization": f"Bearer {API_KEY}"}) - urllib.request.urlopen(req, timeout=10) - return "ok" - except urllib.error.HTTPError as e: - return f"http_{e.code}" - except Exception: - return "timeout" - -@router.get("/health") -def agents_health(): - result = {} - for profile in PROFILES: - model = MODEL_MAP[profile] - status = _check_provider(model) - result[profile] = {"model": model, "provider_status": status, "healthy": status == "ok"} - return result - -@router.get("/{agent_id}") -def agent_info(agent_id: str): - if agent_id not in PROFILES: - return {"error": "unknown agent"} - return {"agent_id": agent_id, "model": MODEL_MAP[agent_id], "status": _check_provider(MODEL_MAP[agent_id])} diff --git a/server/api/connectors.py b/server/api/connectors.py deleted file mode 100644 index 09ecc2d..0000000 --- a/server/api/connectors.py +++ /dev/null @@ -1,97 +0,0 @@ -"""OAuth коннекторы — GitHub, Gmail, Google Drive, Yandex Disk, Google Calendar""" -from __future__ import annotations -import secrets, json -from fastapi import APIRouter -from pydantic import BaseModel - -router = APIRouter(prefix="/api/v1/connectors", tags=["connectors"]) - -# In-memory OAuth state store (заменить на БД в production) -_pending: dict[str, dict] = {} -_connections: dict[str, list[dict]] = {} - -OAUTH_CONFIG = { - "github": { - "name": "GitHub", - "auth_url": "https://github.com/login/oauth/authorize", - "scope": "repo,user", - }, - "gmail": { - "name": "Gmail", - "auth_url": "https://accounts.google.com/o/oauth2/v2/auth", - "scope": "https://www.googleapis.com/auth/gmail.readonly", - }, - "gdrive": { - "name": "Google Drive", - "auth_url": "https://accounts.google.com/o/oauth2/v2/auth", - "scope": "https://www.googleapis.com/auth/drive.readonly", - }, - "gcal": { - "name": "Google Calendar", - "auth_url": "https://accounts.google.com/o/oauth2/v2/auth", - "scope": "https://www.googleapis.com/auth/calendar.readonly", - }, - "ydisk": { - "name": "Yandex Disk", - "auth_url": "https://oauth.yandex.ru/authorize", - "scope": "cloud_api:disk.read", - }, -} - -class OAuthInitRequest(BaseModel): - provider: str - project_id: str - -@router.post("/oauth/init") -def oauth_init(req: OAuthInitRequest): - if req.provider not in OAUTH_CONFIG: - return {"error": "unknown_provider"} - - cfg = OAUTH_CONFIG[req.provider] - state = secrets.token_urlsafe(16) - _pending[state] = {"provider": req.provider, "project_id": req.project_id} - - # В production здесь формируется полный URL с client_id и redirect_uri - client_id = "ACC_CLIENT_ID_PLACEHOLDER" - redirect_uri = "http://localhost:8100/api/v1/connectors/oauth/callback" - auth_url = f"{cfg['auth_url']}?client_id={client_id}&redirect_uri={redirect_uri}&scope={cfg['scope']}&state={state}&response_type=code" - - return {"auth_url": auth_url, "state": state, "provider": req.provider} - -@router.get("/oauth/callback") -def oauth_callback(code: str, state: str): - """OAuth callback — обработка кода авторизации""" - if state not in _pending: - return {"error": "invalid_state"} - - pending = _pending.pop(state) - provider = pending["provider"] - - # В production: обменять code на access_token через POST к token endpoint - connection = { - "provider": provider, - "account": f"{provider}_user", - "label": OAUTH_CONFIG[provider]["name"], - "token_valid": True, - "state": state, - } - - if pending["project_id"] not in _connections: - _connections[pending["project_id"]] = [] - _connections[pending["project_id"]].append(connection) - - return {"connected": True, "provider": provider, **connection} - -@router.get("/oauth/check") -def oauth_check(state: str): - """Проверка статуса OAuth-авторизации (polling)""" - if state in _pending: - return {"connected": False, "status": "pending"} - - # Найти connection по state - for conns in _connections.values(): - for c in conns: - if c.get("state") == state: - return {"connected": True, **c} - - return {"connected": False, "status": "expired"} diff --git a/server/api/memory.py b/server/api/memory.py deleted file mode 100644 index 2577f21..0000000 --- a/server/api/memory.py +++ /dev/null @@ -1,43 +0,0 @@ -"""API для работы с памятью (MEMORY.md)""" -from __future__ import annotations -import os -from pathlib import Path -from fastapi import APIRouter, HTTPException -from pydantic import BaseModel, Field - -router = APIRouter(prefix="/api/v1/memory", tags=["memory"]) - -HERMES_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes")) - -class MemoryEntry(BaseModel): - key: str - value: str - -@router.get("/{key}") -def get_memory(key: str): - path = HERMES_HOME / "memories" / "MEMORY.md" - if not path.exists(): - return {"key": key, "value": None, "found": False} - content = path.read_text(encoding="utf-8") - # Поиск секции ключа - for line in content.split("\n"): - if key.lower() in line.lower(): - return {"key": key, "value": line.strip(), "found": True, "source": str(path)} - return {"key": key, "value": None, "found": False} - -@router.put("/{key}") -def set_memory(key: str, entry: MemoryEntry): - path = HERMES_HOME / "memories" / "MEMORY.md" - path.parent.mkdir(parents=True, exist_ok=True) - content = path.read_text(encoding="utf-8") if path.exists() else "" - content += f"\n{entry.value}" - path.write_text(content, encoding="utf-8") - return {"status": "saved"} - -@router.get("") -def list_memory(limit: int = 20): - path = HERMES_HOME / "memories" / "MEMORY.md" - if not path.exists(): - return {"entries": []} - lines = [l.strip() for l in path.read_text(encoding="utf-8").split("\n") if l.strip() and not l.startswith("#")] - return {"entries": [{"line": l} for l in lines[:limit]], "total": len(lines)} diff --git a/server/api/runs.py b/server/api/runs.py deleted file mode 100644 index 5412a3c..0000000 --- a/server/api/runs.py +++ /dev/null @@ -1,67 +0,0 @@ -"""API для запуска задач на worker-профилях""" -from __future__ import annotations -import subprocess, uuid, json, os -from datetime import datetime, timezone -from fastapi import APIRouter, HTTPException -from pydantic import BaseModel, Field - -router = APIRouter(prefix="/api/v1/runs", tags=["runs"]) - -# In-memory storage (до БД в следующих частях) -_runs: dict[str, dict] = {} - -HERMES = os.environ.get("HERMES_BIN", "hermes") - -class RunRequest(BaseModel): - agent: str = Field(description="worker-code | worker-fast | worker-research | worker-review") - goal: str = Field(min_length=3, max_length=2000) - model: str | None = None - -class RunResponse(BaseModel): - run_id: str - agent: str - goal: str - status: str - created_at: str - -@router.post("", response_model=RunResponse) -def create_run(req: RunRequest): - if req.agent not in ("worker-code", "worker-fast", "worker-research", "worker-review"): - raise HTTPException(400, f"Неизвестный агент: {req.agent}") - - run_id = uuid.uuid4().hex[:12] - created = datetime.now(timezone.utc).isoformat() - cmd = [HERMES, "-p", req.agent, "chat", "-q", req.goal, "-Q"] - if req.model: - cmd.extend(["-m", req.model]) - - # Запуск в фоне - process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - - _runs[run_id] = { - "run_id": run_id, "agent": req.agent, "goal": req.goal, - "status": "running", "created_at": created, "pid": process.pid, - } - return RunResponse(run_id=run_id, agent=req.agent, goal=req.goal, status="running", created_at=created) - -@router.get("") -def list_runs(agent: str | None = None, limit: int = 20): - result = list(_runs.values()) - if agent: - result = [r for r in result if r["agent"] == agent] - # Check status for running processes - for r in result: - if r["status"] == "running": - try: - pid = r.get("pid") - if pid: - os.kill(pid, 0) # check if alive - except (OSError, ProcessLookupError): - r["status"] = "completed" - return {"runs": sorted(result, key=lambda r: r["created_at"], reverse=True)[:limit]} - -@router.get("/{run_id}") -def get_run(run_id: str): - if run_id not in _runs: - raise HTTPException(404, "Задача не найдена") - return _runs[run_id] diff --git a/server/api/skills.py b/server/api/skills.py deleted file mode 100644 index 69db8eb..0000000 --- a/server/api/skills.py +++ /dev/null @@ -1,29 +0,0 @@ -"""API для скиллов""" -from __future__ import annotations -import os, json -from pathlib import Path -from fastapi import APIRouter - -router = APIRouter(prefix="/api/v1/skills", tags=["skills"]) - -HERMES_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes")) - -@router.get("") -def list_skills(): - skills = [] - skills_dir = HERMES_HOME / "skills" - if not skills_dir.exists(): - return {"skills": []} - for d in sorted(skills_dir.iterdir()): - if d.is_dir(): - md = d / "SKILL.md" - if md.exists(): - content = md.read_text(encoding="utf-8", errors="replace") - name = d.name - category = "" - for line in content.split("\n")[:10]: - if line.startswith("category:"): - category = line.split(":", 1)[1].strip() - break - skills.append({"name": name, "category": category, "path": str(d)}) - return {"skills": skills} diff --git a/server/main.py b/server/main.py deleted file mode 100644 index 5aa6820..0000000 --- a/server/main.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Agent Control Center — Control Plane API -Часть 1: управление агентами, задачами, памятью -Часть 2: Web UI -""" -from __future__ import annotations -from pathlib import Path -from fastapi import FastAPI, HTTPException -from fastapi.middleware.cors import CORSMiddleware -from fastapi.staticfiles import StaticFiles -from app.api import runs, agents, memory, skills, connectors - -app = FastAPI( - title="Agent Control Center", - version="0.1.0", - description="Control Plane для управления Hermes-агентами", -) -app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) -app.include_router(runs.router) -app.include_router(agents.router) -app.include_router(memory.router) -app.include_router(skills.router) -app.include_router(connectors.router) - -# Web UI (доступен на /ui) -static = Path(__file__).parent / "static" -static.mkdir(exist_ok=True) -app.mount("/ui", StaticFiles(directory=str(static), html=True), name="static") - -@app.get("/health") -def health(): - return {"status": "ok", "version": app.version} diff --git a/server/static/index.html b/server/static/index.html deleted file mode 100644 index 0b341dc..0000000 --- a/server/static/index.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - -Agent Control Center - - - - -
- -

Agent Control Center

-
- - - - -
-
- -
- -
-

🤖 Агенты

0
-
-

📁 Проекты

-
-
- - - - - -
-
-
💬 Чат
-
-
-
Выберите агента и проект слева, затем напишите задачу
-
- - - - -
- - -
-
-
- - - - - -