Compare commits
2 Commits
4e5a7eed35
...
ce55752249
| Author | SHA1 | Date | |
|---|---|---|---|
| ce55752249 | |||
| d0688a7b64 |
@@ -0,0 +1,48 @@
|
|||||||
|
# AGENTS.md — Mission Control 2.0
|
||||||
|
|
||||||
|
Projekt-Geschmack für Coding-Agenten (Zed, Kilo Code, Claude Code lesen diese Datei).
|
||||||
|
Kurz gehalten, nur die Wahrheiten, die man sonst schmerzhaft lernt. Details: `README.md`, `docs/`.
|
||||||
|
|
||||||
|
## Was das ist
|
||||||
|
Lokaler Local-AI-Stack für die Box (Bosgame M5, Strix Halo, Vulkan/RADV). Schichten:
|
||||||
|
Engine (llama-swap) · Builtin-Routing-Gateway (`model: auto`) · MC2 (FastAPI + React/shadcn) ·
|
||||||
|
Hermes-Agent. Backend Python (Box läuft Python 3.14), Frontend Vite/React/shadcn/Tailwind.
|
||||||
|
|
||||||
|
## Sprache (nicht verhandelbar)
|
||||||
|
- **Alles User-Sichtbare ist Deutsch:** UI-Texte, Fehlermeldungen, Update-/Radar-Meldungen,
|
||||||
|
Skills, LLM-Summaries, Commit-Messages, Code-Kommentare.
|
||||||
|
- **Hermes' INTERNE System-Prompts bleiben Englisch** (fremde Software, wir forken sie nicht).
|
||||||
|
Antwort-Sprache ≠ Prompt-Sprache — Deutsch kommt aus SOUL.md, nicht aus den Tool-Prompts.
|
||||||
|
|
||||||
|
## Build & Deploy (die häufigste Falle)
|
||||||
|
- **`frontend/dist` WIRD committet.** Auf der Box läuft KEIN Node-Build; das Backend liefert die
|
||||||
|
gebauten Assets direkt aus. Nach jeder Frontend-Änderung: `cd frontend && npm run build`, dann
|
||||||
|
**das neue `frontend/dist` mit-committen**. Vergessen = Box zeigt alten Stand.
|
||||||
|
(Ausnahme: `frontend/dist/avatar.vrm` ist bewusst nicht in git — siehe `.gitignore`.)
|
||||||
|
- **Deploy macht `git reset --hard origin/main`** (`deploy/deploy.sh`). Heißt: **`main` muss vor
|
||||||
|
dem Deploy auf Gitea liegen**, und uncommittete Box-Änderungen gehen verloren (Absicht).
|
||||||
|
- **Nie direkt auf `main` arbeiten.** Immer Branch (`wartung/...`), Gate grün, dann Merge/Deploy.
|
||||||
|
|
||||||
|
## Zeit & Umgebung
|
||||||
|
- **Box = Ubuntu, läuft in UTC.** Dev-PC = Windows. Naive/lokale Zeiten immer über
|
||||||
|
`MC_LOCAL_TZ` (= `Europe/Berlin`) auflösen, nie `datetime.now()` ohne TZ annehmen
|
||||||
|
(siehe `backend/services/reminders.py`).
|
||||||
|
|
||||||
|
## Backend-Konventionen
|
||||||
|
- Router unter `backend/routers/` (`APIRouter(prefix="/api")`), Logik in `backend/services/`
|
||||||
|
(Single Source of Truth — Router bleiben dünn). Beispiel-Lehre: Restart-Allowlist lebt NUR in
|
||||||
|
`services.maintenance` (System-Dienste via `sudo -n`, User-Dienste via `systemctl --user`);
|
||||||
|
keine zweite Allowlist in einem Router duplizieren.
|
||||||
|
- **Gate vor Commit:** `python -m py_compile <geänderte .py>` muss durchlaufen.
|
||||||
|
- Wartung ist **sudo-frei** gedacht (systemctl --user). Wo doch sudo nötig ist (llama-swap =
|
||||||
|
System-Dienst), sauber über die NOPASSWD-Whitelist / `password_required`-Rückgabe, nie hart failen.
|
||||||
|
- Lokal (Windows) müssen Box-Shell-Befehle **harmlos fehlschlagen** statt zu crashen.
|
||||||
|
|
||||||
|
## Grenzen (Verdikt, eingehalten)
|
||||||
|
- **Hermes-Quellcode nie selbst patchen** (Fork verboten). Config/Deps ja, Code-Umbau nein.
|
||||||
|
- **Security-Config** (approvals, Tokens, ufw, command_allowlist) **nie ohne explizites User-Ja.**
|
||||||
|
- Alles reversibel halten: Branch + Backup + Pin.
|
||||||
|
|
||||||
|
## Gitea
|
||||||
|
Remote = `Hitonabi/mission-control-v2`. Auth ist flatterhaft → **Push mit Retry**, nur über
|
||||||
|
PowerShell/GCM. Neue Repos per API anlegen. Kein GitHub.
|
||||||
@@ -9,13 +9,14 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL, MEM0_SERVICE_URL, VOICE_SERVICE_URL
|
from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL, MEM0_SERVICE_URL, VOICE_SERVICE_URL
|
||||||
from services import backup as backup_svc
|
from services import backup as backup_svc
|
||||||
|
from services import maintenance
|
||||||
from services.agent import agent_status
|
from services.agent import agent_status
|
||||||
from services.gateway import gateway_reachable
|
from services.gateway import gateway_reachable
|
||||||
from services.llamaswap import engine_reachable, list_models
|
from services.llamaswap import engine_reachable, list_models
|
||||||
@@ -27,8 +28,6 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
router = APIRouter(prefix="/api")
|
router = APIRouter(prefix="/api")
|
||||||
|
|
||||||
# Nur diese User-Dienste dürfen neugestartet werden.
|
|
||||||
ALLOWED_SERVICES = {"mission-control-2", "hermes-gateway", "hermes-webui", "mem0-service", "voice-service"}
|
|
||||||
# Quelle für Self-Update (auf der Box ~/mission-control-v2).
|
# Quelle für Self-Update (auf der Box ~/mission-control-v2).
|
||||||
SOURCE_DIR = os.path.expanduser(os.environ.get("MC2_SOURCE_DIR", "~/mission-control-v2"))
|
SOURCE_DIR = os.path.expanduser(os.environ.get("MC2_SOURCE_DIR", "~/mission-control-v2"))
|
||||||
|
|
||||||
@@ -99,9 +98,12 @@ class RestartReq(BaseModel):
|
|||||||
|
|
||||||
@router.post("/system/restart")
|
@router.post("/system/restart")
|
||||||
def restart(req: RestartReq) -> dict:
|
def restart(req: RestartReq) -> dict:
|
||||||
if req.service not in ALLOWED_SERVICES:
|
"""Dienst neustarten. Delegiert an den Wartungs-Service (EINE Allowlist-Wahrheit):
|
||||||
raise HTTPException(400, f"Dienst '{req.service}' nicht erlaubt.")
|
der kennt System-Dienste (llama-swap, via sudo -n) UND User-Dienste und wird auch von
|
||||||
return _run(["systemctl", "--user", "restart", req.service])
|
der UI (/api/maintenance/restart) genutzt. Vorher lag hier eine zweite, veraltete Liste
|
||||||
|
(ohne llama-swap/hermes-terminal, mit Geist-Eintrag hermes-webui) → Engine-Neustart per
|
||||||
|
Sprache/MCP schlug fehl."""
|
||||||
|
return maintenance.restart_service(req.service)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/system/self-update")
|
@router.post("/system/self-update")
|
||||||
|
|||||||
+3
-2
@@ -226,8 +226,9 @@ def list_services() -> str:
|
|||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def restart_service(service: str) -> str:
|
def restart_service(service: str) -> str:
|
||||||
"""Startet einen erlaubten Dienst neu
|
"""Startet einen erlaubten Dienst neu. Erlaubt: llama-swap (Engine) | mission-control-2 |
|
||||||
(mission-control-2 | hermes-gateway | hermes-webui | mem0-service | voice-service)."""
|
hermes-gateway | hermes-terminal | mem0-service | voice-service. Der Engine-Neustart
|
||||||
|
(llama-swap = System-Dienst) kann das Box-Passwort verlangen, wenn kein NOPASSWD gesetzt ist."""
|
||||||
res = _post("/api/system/restart", {"service": service})
|
res = _post("/api/system/restart", {"service": service})
|
||||||
return f"Restart {service}: {'ok' if res.get('ok') else 'Fehler — ' + res.get('err', '')}"
|
return f"Restart {service}: {'ok' if res.get('ok') else 'Fehler — ' + res.get('err', '')}"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user