feat(2.0): implement cockpit improvements, dynamic mcp path, v2 venv, and ko-residency

This commit is contained in:
Hitonabi
2026-06-25 21:50:16 +02:00
parent 807c2c6194
commit e1da5c797d
14 changed files with 87 additions and 50 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ DEFAULT_TTL = int(os.environ.get("MC_DEFAULT_TTL", "300"))
HF_DOWNLOAD_ENV = {"HF_HUB_DISABLE_XET": "1"}
# --- Routing-Gateway (LiteLLM, model: auto) ----------------------------------
GATEWAY_URL = os.environ.get("MC_GATEWAY_URL", "http://127.0.0.1:4000").rstrip("/")
GATEWAY_URL = os.environ.get("MC_GATEWAY_URL", f"http://127.0.0.1:{os.environ.get('MC_PORT', '9000')}").rstrip("/")
GATEWAY_CONFIG_PATH = Path(os.environ.get(
"MC_GATEWAY_CONFIG", str(Path(__file__).resolve().parent.parent / "gateway" / "config.yaml")))
+1
View File
@@ -4,3 +4,4 @@ httpx>=0.27
ruamel.yaml>=0.18
psutil>=5.9
huggingface_hub>=0.27
mcp>=1.2.0
+5 -2
View File
@@ -8,5 +8,8 @@ router = APIRouter(prefix="/api")
@router.get("/connect")
def connect(host: str = DEFAULT_HOST) -> dict:
return build_snippets(host=host)
def connect(host: str = DEFAULT_HOST, mcp_path: str | None = None) -> dict:
kwargs = {}
if mcp_path:
kwargs["mcp_script_path"] = mcp_path
return build_snippets(host=host, **kwargs)
+4 -3
View File
@@ -21,7 +21,7 @@ from services.system import system_status
router = APIRouter(prefix="/api")
# Nur diese User-Dienste dürfen neugestartet werden.
ALLOWED_SERVICES = {"mission-control-2", "litellm-gateway", "hermes-webui"}
ALLOWED_SERVICES = {"mission-control-2", "hermes-gateway", "hermes-webui"}
# 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"))
@@ -35,16 +35,17 @@ def status() -> dict:
def services() -> dict:
"""Aggregierte Erreichbarkeit aller Stack-Dienste (für die Health-Anzeige)."""
a = agent_status()
gw_url = f"{GATEWAY_URL}/v1"
return {
"services": [
{"name": "Engine (llama-swap)", "url": LLAMA_SWAP_URL, "ok": engine_reachable()},
{"name": "Gateway (LiteLLM)", "url": GATEWAY_URL, "ok": gateway_reachable()},
{"name": "Gateway (integriert)", "url": gw_url, "ok": gateway_reachable()},
{"name": "Hermes-Gateway", "url": HERMES_API_URL, "ok": a["gateway_reachable"]},
{"name": "Hermes-WebUI", "url": HERMES_WEBUI_URL, "ok": a["webui_reachable"]},
],
"links": {
"engine_ui": f"{LLAMA_SWAP_URL}/ui",
"gateway": GATEWAY_URL,
"gateway": gw_url,
"hermes_webui": HERMES_WEBUI_URL,
},
}
+13 -1
View File
@@ -6,7 +6,7 @@ Hermes' eigener Config verdrahtet (siehe docs/HERMES_SETUP.md).
import httpx
from config import HERMES_API_URL, HERMES_HOME, HERMES_WEBUI_URL
from config import HERMES_API_URL, HERMES_HOME, HERMES_WEBUI_URL, yaml
def _reach(url: str, path: str = "") -> bool:
@@ -20,12 +20,24 @@ def _reach(url: str, path: str = "") -> bool:
def agent_status() -> dict:
"""Erreichbarkeit von Gateway (:8642) + WebUI (:8787) + lokale Hinweise."""
home = HERMES_HOME
brain_model = "auto"
config_path = home / "config.yaml"
if config_path.exists():
try:
with config_path.open("r", encoding="utf-8") as f:
cfg = yaml.load(f) or {}
if isinstance(cfg, dict):
brain_model = cfg.get("model", {}).get("model", "auto")
except Exception:
pass
return {
"gateway_url": HERMES_API_URL,
"webui_url": HERMES_WEBUI_URL,
"gateway_reachable": _reach(HERMES_API_URL, "/v1/models"),
"webui_reachable": _reach(HERMES_WEBUI_URL),
"home_exists": home.exists(),
"brain_model": brain_model,
# Best-effort: welche Verdrahtung lokal sichtbar ist (auf der Box aussagekräftig).
"has_config": (home / "config.yaml").exists() or (home / "config.json").exists(),
"has_skills": (home / "skills").exists(),
+1 -1
View File
@@ -18,7 +18,7 @@ from services import discover, jobengine, llamaswap
# System-Dienste (root, via sudo -n NOPASSWD) vs. User-Dienste (systemctl --user).
SYSTEM_SERVICES = {"llama-swap"}
USER_SERVICES = {"mission-control-2", "hermes-gateway", "hermes-dashboard", "hermes-webui", "litellm-gateway"}
USER_SERVICES = {"mission-control-2", "hermes-gateway", "hermes-dashboard", "hermes-webui"}
ENGINE_UPDATE_CMD = os.environ.get("MC_ENGINE_UPDATE_CMD", "")
ENGINE_PATH = os.environ.get("MC_ENGINE_PATH", "/opt/llamacpp")
+3 -3
View File
@@ -69,9 +69,9 @@ delegation:
`agent.disabled_toolsets` (gilt für ALLE Plattformen).
- **Behalten:** terminal/shell, file, code_execution, skills, todo, session_search, clarify,
delegation, cronjob, web. `approvals: auto` (rein lokal).
- **MCP-Server** (`mcp_servers` in config) — laufen über die v1-venv (hat das `mcp`-Modul):
- geteiltes Gedächtnis: `/opt/mission-control/.venv/bin/python ~/mission-control-v2/mcp/mcp_memory.py` (Env `MC_URL=http://127.0.0.1:9001`)
- Stack-Management: ` ~/mission-control-v2/mcp/mcp_mc.py` (Env `MC_URL=http://127.0.0.1:9001`)
- **MCP-Server** (`mcp_servers` in config) — laufen über die v2-venv (hat das `mcp`-Modul nach pip install):
- geteiltes Gedächtnis: `~/mission-control-v2/backend/.venv/bin/python ~/mission-control-v2/mcp/mcp_memory.py` (Env `MC_URL=http://127.0.0.1:9001`)
- Stack-Management: `~/mission-control-v2/backend/.venv/bin/python ~/mission-control-v2/mcp/mcp_mc.py` (Env `MC_URL=http://127.0.0.1:9001`)
- **SSH→Windows-PC** (voller Zugriff): OpenSSH-Server auf Windows aktiv + Key
`~/.ssh/id_ed25519_hermes_agent` autorisiert; Hermes nutzt sein terminal-Tool für `ssh TobisPC@<win-ip>`.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -6,8 +6,8 @@
<meta name="theme-color" content="#0d1117" />
<link rel="manifest" href="/manifest.webmanifest" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-S7P9XrdI.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-IpcnHTkp.css">
<script type="module" crossorigin src="/assets/index-BcRd449w.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D7pMz4id.css">
</head>
<body>
<div id="root"></div>
+1
View File
@@ -143,6 +143,7 @@ export interface AgentStatus {
gateway_reachable: boolean
webui_reachable: boolean
home_exists: boolean
brain_model?: string
has_config: boolean
has_skills: boolean
has_memories: boolean
+1 -1
View File
@@ -64,7 +64,7 @@ export function AgentView() {
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
<Tile label="Gateway (:8642)" ok={s.gateway_reachable} detail={s.gateway_reachable ? "erreichbar" : "offline"} />
<Tile label="WebUI (:8787)" ok={s.webui_reachable} detail={s.webui_reachable ? "erreichbar" : "offline"} />
<Tile label="Brain" ok={s.gateway_reachable} detail="model: auto (über Gateway)" />
<Tile label="Brain" ok={s.gateway_reachable} detail={`model: ${s.brain_model || "auto"}`} />
<Tile label="Verdrahtung" ok={s.has_config} detail={`config ${s.has_config ? "✓" : "—"} · skills ${s.has_skills ? "✓" : "—"} · memories ${s.has_memories ? "✓" : "—"}`} />
</div>
+29 -10
View File
@@ -5,22 +5,31 @@ import { cn } from "@/lib/utils"
export function ConnectView() {
const [host, setHost] = useState(localStorage.getItem("mc_host") || "192.168.178.151")
const [mcpPath, setMcpPath] = useState(localStorage.getItem("mc_mcp_path") || "")
const [data, setData] = useState<ConnectResp | null>(null)
const [active, setActive] = useState("cline")
const [copied, setCopied] = useState(false)
const [error, setError] = useState("")
useEffect(() => {
api<ConnectResp>(`/api/connect?host=${encodeURIComponent(host)}`)
const params = new URLSearchParams()
params.set("host", host)
if (mcpPath) params.set("mcp_path", mcpPath)
api<ConnectResp>(`/api/connect?${params}`)
.then(setData)
.catch((e) => setError(String(e)))
}, [host])
}, [host, mcpPath])
function saveHost(v: string) {
setHost(v)
if (v) localStorage.setItem("mc_host", v)
}
function saveMcpPath(v: string) {
setMcpPath(v)
localStorage.setItem("mc_mcp_path", v)
}
const tool = data?.tools[active]
async function copy() {
@@ -40,14 +49,24 @@ export function ConnectView() {
</p>
</div>
<div className="flex items-center gap-2 text-sm">
<label className="text-muted-foreground">Box-LAN-IP:</label>
<input
value={host}
onChange={(e) => saveHost(e.target.value)}
className="rounded-md border border-border bg-card px-2 py-1 font-mono text-xs outline-none focus:ring-2 focus:ring-ring"
/>
{data && <span className="text-xs text-muted-foreground"> {data.gateway_url}</span>}
<div className="flex flex-wrap items-center gap-4 text-sm bg-card/40 p-3 rounded-lg border border-border">
<div className="flex items-center gap-2">
<label className="text-muted-foreground">Box-LAN-IP:</label>
<input
value={host}
onChange={(e) => saveHost(e.target.value)}
className="w-36 rounded-md border border-border bg-background px-2 py-1 font-mono text-xs outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div className="flex items-center gap-2 flex-1 min-w-[280px]">
<label className="text-muted-foreground whitespace-nowrap">Lokaler MCP-Pfad:</label>
<input
value={mcpPath}
onChange={(e) => saveMcpPath(e.target.value)}
placeholder="z.B. F:\Coding Stuff\mission-control-2\mcp\mcp_memory.py"
className="flex-1 rounded-md border border-border bg-background px-2 py-1 font-mono text-xs outline-none focus:ring-2 focus:ring-ring"
/>
</div>
</div>
{error && (