feat(2.0): Phase 4 — Hermes-Schicht (MC-Seite + Runbook)
MC-Seite: services/agent.py + routers/agent.py (GET /api/agent/status: Gateway/WebUI-Reachability + Verdrahtungs-Hinweise), AgentView (Status- Tiles, Hermes-oeffnen-Button, Offline-Hinweis). deploy/hermes-webui.service (systemd-USER, :8787). Box-Runbook docs/HERMES_SETUP.md (hermes-webui installieren, Brain=model:auto via Gateway, Tools/MCP verdrahten inkl. mcp_mc+mcp_memory, SSH-Windows, LiteLLM-Caveat #26489). Lokal verifiziert: agent/status + Frontend-Build + Browser (Hermes-View). Box-Ausfuehrung der Verdrahtung steht aus (Runbook). Docs aktualisiert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ siehe Architektur-Plan (`docs/` bzw. der genehmigte Plan).
|
|||||||
Mission Control 2.0 (FastAPI + React/shadcn) · Hermes Agent + hermes-webui ·
|
Mission Control 2.0 (FastAPI + React/shadcn) · Hermes Agent + hermes-webui ·
|
||||||
Shared Memory (SQLite via MCP). Jede Schicht hinter stabilem Vertrag austauschbar.
|
Shared Memory (SQLite via MCP). Jede Schicht hinter stabilem Vertrag austauschbar.
|
||||||
|
|
||||||
## Status: Phase 3 (Memory + MCP) ✅
|
## Status: Phase 4 (Hermes-Schicht — MC-Seite) ✅ · Box-Ausführung offen
|
||||||
|
|
||||||
Fortschritt & Resume-Guide: siehe [`docs/STATUS.md`](docs/STATUS.md).
|
Fortschritt & Resume-Guide: siehe [`docs/STATUS.md`](docs/STATUS.md).
|
||||||
|
|
||||||
@@ -21,8 +21,12 @@ Fortschritt & Resume-Guide: siehe [`docs/STATUS.md`](docs/STATUS.md).
|
|||||||
- **Phase 3** — Geteiltes **Gedächtnis** (SQLite/WAL, 5 Kategorien, Dedupe-Kurator) + **MCP-Server**
|
- **Phase 3** — Geteiltes **Gedächtnis** (SQLite/WAL, 5 Kategorien, Dedupe-Kurator) + **MCP-Server**
|
||||||
(`mcp/mcp_memory.py` shared, `mcp/mcp_mc.py` Stack-Management für Hermes), MemoryView.
|
(`mcp/mcp_memory.py` shared, `mcp/mcp_mc.py` Stack-Management für Hermes), MemoryView.
|
||||||
|
|
||||||
|
- **Phase 4** — Hermes-**Agent-Status** (`/api/agent/status`, AgentView mit Tiles + „Hermes öffnen"),
|
||||||
|
`deploy/hermes-webui.service`, **Box-Runbook** [`docs/HERMES_SETUP.md`](docs/HERMES_SETUP.md)
|
||||||
|
(hermes-webui, Brain=`auto`, Tools/MCP-Verdrahtung). Box-Ausführung steht noch aus.
|
||||||
|
|
||||||
API: `health · models · discover · fit · models/register · groups · routing · system/* · connect ·
|
API: `health · models · discover · fit · models/register · groups · routing · system/* · connect ·
|
||||||
memory/*` (Details in `docs/STATUS.md`). MCP: `mcp/` (siehe `mcp/requirements.txt`).
|
memory/* · agent/status` (Details in `docs/STATUS.md`). MCP: `mcp/` (siehe `mcp/requirements.txt`).
|
||||||
|
|
||||||
## Entwickeln
|
## Entwickeln
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -13,7 +13,7 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
|
|
||||||
from config import FRONTEND_DIST, VERSION
|
from config import FRONTEND_DIST, VERSION
|
||||||
from routers import connect, health, memory, models, routing, system
|
from routers import agent, connect, health, memory, models, routing, system
|
||||||
|
|
||||||
app = FastAPI(title="Mission Control 2.0", version=VERSION)
|
app = FastAPI(title="Mission Control 2.0", version=VERSION)
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ app.include_router(routing.router)
|
|||||||
app.include_router(system.router)
|
app.include_router(system.router)
|
||||||
app.include_router(connect.router)
|
app.include_router(connect.router)
|
||||||
app.include_router(memory.router)
|
app.include_router(memory.router)
|
||||||
|
app.include_router(agent.router)
|
||||||
|
|
||||||
|
|
||||||
# Prod: gebautes Frontend ausliefern (falls vorhanden). SPA-Fallback auf index.html.
|
# Prod: gebautes Frontend ausliefern (falls vorhanden). SPA-Fallback auf index.html.
|
||||||
|
|||||||
+7
-1
@@ -38,6 +38,12 @@ GATEWAY_URL = os.environ.get("MC_GATEWAY_URL", "http://127.0.0.1:4000").rstrip("
|
|||||||
GATEWAY_CONFIG_PATH = Path(os.environ.get(
|
GATEWAY_CONFIG_PATH = Path(os.environ.get(
|
||||||
"MC_GATEWAY_CONFIG", str(Path(__file__).resolve().parent.parent / "gateway" / "config.yaml")))
|
"MC_GATEWAY_CONFIG", str(Path(__file__).resolve().parent.parent / "gateway" / "config.yaml")))
|
||||||
|
|
||||||
|
# --- Hermes Agent (eigener Dienst auf der Box) -------------------------------
|
||||||
|
# Gateway (OpenAI-API des Agenten) + standalone Web-UI (nesquena/hermes-webui).
|
||||||
|
HERMES_API_URL = os.environ.get("HERMES_API_URL", "http://127.0.0.1:8642").rstrip("/")
|
||||||
|
HERMES_WEBUI_URL = os.environ.get("HERMES_WEBUI_URL", "http://127.0.0.1:8787").rstrip("/")
|
||||||
|
HERMES_HOME = Path(os.path.expanduser(os.environ.get("HERMES_HOME", "~/.hermes")))
|
||||||
|
|
||||||
# --- Server ------------------------------------------------------------------
|
# --- Server ------------------------------------------------------------------
|
||||||
HOST = os.environ.get("MC_HOST", "0.0.0.0")
|
HOST = os.environ.get("MC_HOST", "0.0.0.0")
|
||||||
PORT = int(os.environ.get("MC_PORT", "9000"))
|
PORT = int(os.environ.get("MC_PORT", "9000"))
|
||||||
@@ -46,7 +52,7 @@ PORT = int(os.environ.get("MC_PORT", "9000"))
|
|||||||
FRONTEND_DIST = Path(os.environ.get("MC_FRONTEND_DIST", str(Path(__file__).resolve().parent.parent / "frontend" / "dist")))
|
FRONTEND_DIST = Path(os.environ.get("MC_FRONTEND_DIST", str(Path(__file__).resolve().parent.parent / "frontend" / "dist")))
|
||||||
|
|
||||||
# Version (Phase 0 — Greenfield-Skeleton).
|
# Version (Phase 0 — Greenfield-Skeleton).
|
||||||
VERSION = "2.0.0-phase3"
|
VERSION = "2.0.0-phase4"
|
||||||
|
|
||||||
# Gemeinsame YAML-Instanz (preserve_quotes hält Kommentare/Quotes in config.yaml).
|
# Gemeinsame YAML-Instanz (preserve_quotes hält Kommentare/Quotes in config.yaml).
|
||||||
yaml = YAML()
|
yaml = YAML()
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
"""Agent-Endpoint: Hermes-Status + WebUI-Link (MC verlinkt nur, betreibt nicht)."""
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from services.agent import agent_status
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/agent/status")
|
||||||
|
def status() -> dict:
|
||||||
|
return agent_status()
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
Hermes-Agent-Status (Control-Plane-Read). MC betreibt Hermes NICHT — es zeigt nur
|
||||||
|
Status + verlinkt das standalone hermes-webui. Voller Zugriff + Tools/MCP werden in
|
||||||
|
Hermes' eigener Config verdrahtet (siehe docs/HERMES_SETUP.md).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from config import HERMES_API_URL, HERMES_HOME, HERMES_WEBUI_URL
|
||||||
|
|
||||||
|
|
||||||
|
def _reach(url: str, path: str = "") -> bool:
|
||||||
|
try:
|
||||||
|
with httpx.Client(timeout=3.0) as c:
|
||||||
|
return c.get(f"{url}{path}").status_code < 500
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def agent_status() -> dict:
|
||||||
|
"""Erreichbarkeit von Gateway (:8642) + WebUI (:8787) + lokale Hinweise."""
|
||||||
|
home = HERMES_HOME
|
||||||
|
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(),
|
||||||
|
# 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(),
|
||||||
|
"has_memories": (home / "memories").exists(),
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# systemd-USER-Unit für das standalone Hermes-WebUI (nesquena/hermes-webui).
|
||||||
|
# Eigene Oberfläche des Agenten (Chat/Sessions/Workspace/Cron/Skills/Memory).
|
||||||
|
# An 0.0.0.0:8787 gebunden, damit der Windows-PC im LAN direkt zugreifen kann
|
||||||
|
# → Passwort-Schutz aktivieren (HERMES_WEBUI_PASSWORD)!
|
||||||
|
# Ablage: ~/.config/systemd/user/hermes-webui.service ; dann:
|
||||||
|
# systemctl --user enable --now hermes-webui
|
||||||
|
# loginctl enable-linger hitonabi # läuft auch ohne aktive Session
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Hermes WebUI (standalone)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=%h/hermes-webui
|
||||||
|
# Start über das mitgelieferte Control-Skript (siehe docs/HERMES_SETUP.md).
|
||||||
|
ExecStart=%h/hermes-webui/ctl.sh start --foreground
|
||||||
|
Environment=HERMES_WEBUI_HOST=0.0.0.0
|
||||||
|
Environment=HERMES_WEBUI_PORT=8787
|
||||||
|
Environment=HERMES_HOME=%h/.hermes
|
||||||
|
# Environment=HERMES_WEBUI_PASSWORD=... # in ~/.config/environment.d/ setzen, NICHT hier committen
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Hermes-Schicht — Box-Runbook (Phase 4)
|
||||||
|
|
||||||
|
> Diese Schritte laufen **auf der Bosgame** (`192.168.178.151`, User `hitonabi`).
|
||||||
|
> MC betreibt Hermes nicht — es zeigt nur Status + verlinkt das WebUI. Hier wird die
|
||||||
|
> eigentliche **volle Verdrahtung** gemacht (das war in v1 der „Hermes ist dumm"-Grund).
|
||||||
|
|
||||||
|
## Reihenfolge der Dienste
|
||||||
|
`llama-swap (:8080)` → `LiteLLM-Gateway (:4000)` → `hermes-gateway (:8642)` → `hermes-webui (:8787)`
|
||||||
|
|
||||||
|
## 1. LiteLLM-Gateway starten + Auto-Routing prüfen
|
||||||
|
```bash
|
||||||
|
uv pip install "litellm[proxy]" # oder pipx
|
||||||
|
litellm --config /opt/mission-control-2/gateway/config.yaml --port 4000
|
||||||
|
curl -s http://127.0.0.1:4000/v1/models # fast/heavy/vision/coder/auto?
|
||||||
|
# 'auto' testen: einfacher Prompt → fast; harter/zu langer → Fallback heavy.
|
||||||
|
```
|
||||||
|
⚠️ **Complexity-Auto-Router** (model:auto wählt nach Schwierigkeit) ist versionsabhängig — gegen die
|
||||||
|
installierte LiteLLM-Version verifizieren (docs.litellm.ai/docs/proxy/auto_routing). Baseline =
|
||||||
|
fast + Fallback heavy ist bereits in `gateway/config.yaml`.
|
||||||
|
|
||||||
|
## 2. Engine: Hirne + Ko-Residenz (llama-swap groups)
|
||||||
|
In MC (Modelle & Routing) bzw. direkt: Qwen3.6-35B-A3B (Alias `fast`, `--jinja`!) und
|
||||||
|
Qwen3.5-122B-A10B (Alias `heavy`) eintragen, Gruppe `brains` mit `swap:false` (ko-resident).
|
||||||
|
```bash
|
||||||
|
curl -s http://127.0.0.1:9001/api/groups # MC2 (Phase 0/1 API)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. hermes-webui installieren (standalone)
|
||||||
|
```bash
|
||||||
|
cd ~ && git clone https://github.com/nesquena/hermes-webui && cd hermes-webui
|
||||||
|
python3 bootstrap.py # erkennt hermes-agent, installiert Abhängigkeiten
|
||||||
|
# Dienst: deploy/hermes-webui.service → ~/.config/systemd/user/, Passwort setzen:
|
||||||
|
mkdir -p ~/.config/environment.d
|
||||||
|
echo 'HERMES_WEBUI_PASSWORD=<dein-passwort>' > ~/.config/environment.d/hermes-webui.conf
|
||||||
|
systemctl --user enable --now hermes-webui
|
||||||
|
loginctl enable-linger hitonabi
|
||||||
|
```
|
||||||
|
Zugriff vom Windows-PC: `http://192.168.178.151:8787` (mit Passwort).
|
||||||
|
|
||||||
|
## 4. Hermes-Brain = `model: auto` über den Gateway
|
||||||
|
In `~/.hermes/config.yaml` (bzw. via `hermes`-CLI/WebUI-Settings) das Modell auf den **Gateway** zeigen:
|
||||||
|
```yaml
|
||||||
|
model:
|
||||||
|
provider: openai # NICHT 'custom' → Hänger-Bug #26489
|
||||||
|
base_url: http://127.0.0.1:4000/v1
|
||||||
|
api_key: local
|
||||||
|
name: auto # schnell im Alltag, eskaliert auf heavy
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Tools/MCP verdrahten (der eigentliche Fix)
|
||||||
|
Hermes' Tools sind by-default AUS. Aktivieren:
|
||||||
|
- **Built-in:** terminal/shell, read_file/search_files, file-ops. `approvals: auto` (rein lokal).
|
||||||
|
- **MCP-Server** (`hermes mcp` oder config `mcp_servers`):
|
||||||
|
- geteiltes Gedächtnis: `python /opt/mission-control-2/mcp/mcp_memory.py` (Env `MC_URL=http://127.0.0.1:9001`)
|
||||||
|
- Stack-Management: `python /opt/mission-control-2/mcp/mcp_mc.py` (Env `MC_URL=http://127.0.0.1:9001`)
|
||||||
|
- **SSH→Windows-PC** (voller Zugriff auf deinen PC): OpenSSH-Server auf Windows aktiv + Key
|
||||||
|
`~/.ssh/id_ed25519_hermes_agent` autorisiert; Hermes nutzt sein Shell-Tool für `ssh TobisPC@<win-ip> …`.
|
||||||
|
- **100 % lokal:** Hermes' eingebauter web_search/browser laufen über Nous Portal (Cloud). Für rein
|
||||||
|
lokal stattdessen einen **lokalen Browser-/Such-MCP** binden (optional, später).
|
||||||
|
|
||||||
|
## 6. Verifikation
|
||||||
|
- WebUI öffnet vom Windows-PC, Chat antwortet (Brain via Gateway).
|
||||||
|
- Hermes kann via `mcp_mc` ein Modell listen/Routing ändern (`list_models`, `set_route`).
|
||||||
|
- Hermes erreicht den Windows-PC (SSH-Kommando) und das Netz.
|
||||||
|
- 14B-Loop weg (Guard-Beschreibungen in mcp_memory + tool-fähiges Brain).
|
||||||
+8
-6
@@ -21,7 +21,9 @@
|
|||||||
(CRUD/export/dedupe), `mcp/mcp_memory.py` (Guard-Beschreibungen gegen Loop) + `mcp/mcp_mc.py` NEU
|
(CRUD/export/dedupe), `mcp/mcp_memory.py` (Guard-Beschreibungen gegen Loop) + `mcp/mcp_mc.py` NEU
|
||||||
(Stack-Management-Tools für Hermes: list/discover/register/route/restart/status). Frontend
|
(Stack-Management-Tools für Hermes: list/discover/register/route/restart/status). Frontend
|
||||||
MemoryView (Add/Filter/Suche/Delete/Aufräumen). Lokal verifiziert (CRUD+Dedupe; MCP syntax-OK).
|
MemoryView (Add/Filter/Suche/Delete/Aufräumen). Lokal verifiziert (CRUD+Dedupe; MCP syntax-OK).
|
||||||
- [ ] **Phase 4 — Hermes-Schicht** (hermes-webui Dienst, Brain=auto, Tools/MCP verdrahten). **Braucht Box.**
|
- [~] **Phase 4 — Hermes-Schicht:** MC-Seite ✅ (services/agent.py + /api/agent/status, AgentView mit
|
||||||
|
Status-Tiles + „Hermes öffnen"), `deploy/hermes-webui.service`, **Runbook `docs/HERMES_SETUP.md`**.
|
||||||
|
**Box-Ausführung offen** (hermes-webui installieren, Brain=auto, Tools/MCP verdrahten — Runbook).
|
||||||
- [ ] **Phase 5 — Betrieb/Observability/Politur** (Backup, LiteLLM-Traces, Health).
|
- [ ] **Phase 5 — Betrieb/Observability/Politur** (Backup, LiteLLM-Traces, Health).
|
||||||
- [ ] **Phase 6 — Cutover** (/opt → v2, v1 aus). **Braucht Box.**
|
- [ ] **Phase 6 — Cutover** (/opt → v2, v1 aus). **Braucht Box.**
|
||||||
- Offen aus Phase 0/1: **Box-Deploy** (Gitea-Repo da; Box-Setup als systemd-USER-Dienst noch offen),
|
- Offen aus Phase 0/1: **Box-Deploy** (Gitea-Repo da; Box-Setup als systemd-USER-Dienst noch offen),
|
||||||
@@ -50,10 +52,10 @@ cd frontend && npm run build
|
|||||||
- `GET /api/connect?host=` — IDE-/Agent-Snippets (Gateway + Memory-MCP)
|
- `GET /api/connect?host=` — IDE-/Agent-Snippets (Gateway + Memory-MCP)
|
||||||
- `GET/POST/PUT/DELETE /api/memory[...]` + `/api/memory/export` + `/api/memory/dedupe` — geteiltes Gedächtnis
|
- `GET/POST/PUT/DELETE /api/memory[...]` + `/api/memory/export` + `/api/memory/dedupe` — geteiltes Gedächtnis
|
||||||
- `mcp/mcp_memory.py` (geteiltes Memory) + `mcp/mcp_mc.py` (Stack-Management für Hermes) — stdio-MCP
|
- `mcp/mcp_memory.py` (geteiltes Memory) + `mcp/mcp_mc.py` (Stack-Management für Hermes) — stdio-MCP
|
||||||
|
- `GET /api/agent/status` — Hermes Gateway/WebUI-Erreichbarkeit + Verdrahtungs-Hinweise
|
||||||
|
|
||||||
## Nächster sinnvoller Schritt
|
## Nächster sinnvoller Schritt
|
||||||
**Phase 4 (Hermes-Schicht) braucht die Box** — hermes-webui als Dienst, Brain=model:auto, Tools/MCP
|
Alle **lokal baubaren** Teile sind fertig (Phase 0–4 MC-Seite + Phase 5-Vorbereitung folgt). Was jetzt
|
||||||
verdrahten (FS/Shell/SSH→Win/Netz + mcp_mc + mcp_memory), LiteLLM-Caveat verifizieren. Vorher sinnvoll:
|
die **Box** braucht: **Box-Deploy** (systemd-USER-Dienst, sudo-frei) → **LiteLLM-Gateway** starten +
|
||||||
**Box-Deploy** einrichten (systemd-USER-Dienst, sudo-frei; `deploy/deploy.sh` auf User-Dienst umstellen,
|
`model:auto` verifizieren → **Hermes-Schicht** nach `docs/HERMES_SETUP.md` einrichten → **Cutover** (Phase 6).
|
||||||
kein /opt/sudo) + **LiteLLM-Gateway** auf der Box starten und `model:auto`/Complexity-Router verifizieren.
|
Lokal noch offen/sinnvoll: Phase 5 (Backup-Skript, Observability/Health-Anzeige im System-Tab).
|
||||||
Lokal noch machbar ohne Box: Phase-4-Frontend (Agent-Status/Verdrahtungs-Anzeige + „Hermes öffnen").
|
|
||||||
|
|||||||
-160
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+165
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
<meta name="theme-color" content="#0d1117" />
|
<meta name="theme-color" content="#0d1117" />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<title>Mission Control 2.0</title>
|
<title>Mission Control 2.0</title>
|
||||||
<script type="module" crossorigin src="/assets/index-ByXSUESS.js"></script>
|
<script type="module" crossorigin src="/assets/index-umMcKqw6.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CE_qxW4X.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-nVsgoG2p.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { RoutingView } from "@/views/RoutingView"
|
|||||||
import { SystemView } from "@/views/SystemView"
|
import { SystemView } from "@/views/SystemView"
|
||||||
import { ConnectView } from "@/views/ConnectView"
|
import { ConnectView } from "@/views/ConnectView"
|
||||||
import { MemoryView } from "@/views/MemoryView"
|
import { MemoryView } from "@/views/MemoryView"
|
||||||
|
import { AgentView } from "@/views/AgentView"
|
||||||
import { Placeholder } from "@/views/Placeholder"
|
import { Placeholder } from "@/views/Placeholder"
|
||||||
import { api, type Health } from "@/lib/api"
|
import { api, type Health } from "@/lib/api"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@@ -96,7 +97,8 @@ export default function App() {
|
|||||||
{view === "system" && <SystemView />}
|
{view === "system" && <SystemView />}
|
||||||
{view === "connect" && <ConnectView />}
|
{view === "connect" && <ConnectView />}
|
||||||
{view === "memory" && <MemoryView />}
|
{view === "memory" && <MemoryView />}
|
||||||
{!["models", "routing", "system", "connect", "memory"].includes(view) && (
|
{view === "agent" && <AgentView />}
|
||||||
|
{!["models", "routing", "system", "connect", "memory", "agent"].includes(view) && (
|
||||||
<Placeholder title={active.label} hint={active.hint} />
|
<Placeholder title={active.label} hint={active.hint} />
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -116,6 +116,17 @@ export interface DedupeResult {
|
|||||||
applied: boolean
|
applied: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AgentStatus {
|
||||||
|
gateway_url: string
|
||||||
|
webui_url: string
|
||||||
|
gateway_reachable: boolean
|
||||||
|
webui_reachable: boolean
|
||||||
|
home_exists: boolean
|
||||||
|
has_config: boolean
|
||||||
|
has_skills: boolean
|
||||||
|
has_memories: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface Health {
|
export interface Health {
|
||||||
status: string
|
status: string
|
||||||
version: string
|
version: string
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { ExternalLink } from "lucide-react"
|
||||||
|
import { api, type AgentStatus } from "@/lib/api"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Dot({ ok }: { ok: boolean }) {
|
||||||
|
return <span className={cn("h-2 w-2 rounded-full", ok ? "bg-emerald-500" : "bg-amber-500")} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function Tile({ label, ok, detail }: { label: string; ok: boolean; detail?: string }) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-lg border border-border bg-card p-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Dot ok={ok} />
|
||||||
|
<span className="text-sm font-medium">{label}</span>
|
||||||
|
</div>
|
||||||
|
{detail && <div className="mt-1 text-xs text-muted-foreground">{detail}</div>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AgentView() {
|
||||||
|
const [s, setS] = useState<AgentStatus | null>(null)
|
||||||
|
const [error, setError] = useState("")
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const load = () => api<AgentStatus>("/api/agent/status").then(setS).catch((e) => setError(String(e)))
|
||||||
|
load()
|
||||||
|
const t = setInterval(load, 8000)
|
||||||
|
return () => clearInterval(t)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-semibold">Hermes</h1>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Der autonome Agent läuft eigenständig (Gateway :8642) und hat seine eigene Oberfläche
|
||||||
|
(hermes-webui :8787). Mission Control verlinkt nur — Chat & Steuerung leben dort.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{s?.webui_url && (
|
||||||
|
<a
|
||||||
|
href={s.webui_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium",
|
||||||
|
s.webui_reachable
|
||||||
|
? "bg-primary text-primary-foreground hover:opacity-90"
|
||||||
|
: "border border-border text-muted-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ExternalLink className="h-4 w-4" /> Hermes öffnen
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <div className="text-sm text-muted-foreground">Status nicht lesbar ({error}).</div>}
|
||||||
|
|
||||||
|
{s && (
|
||||||
|
<>
|
||||||
|
<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="Verdrahtung" ok={s.has_config} detail={`config ${s.has_config ? "✓" : "—"} · skills ${s.has_skills ? "✓" : "—"} · memories ${s.has_memories ? "✓" : "—"}`} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!s.gateway_reachable && (
|
||||||
|
<div className="rounded-xl border border-dashed border-border bg-card/50 p-5 text-sm">
|
||||||
|
<div className="mb-2 font-medium">Hermes ist (hier) offline</div>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Der Agent + hermes-webui laufen auf der Box. Einrichtung & volle Verdrahtung
|
||||||
|
(Brain = <code>model: auto</code>, Tools/MCP inkl. <code>mcp_mc</code> + <code>mcp_memory</code>,
|
||||||
|
SSH→Windows, lokale Browser-/Such-MCP) sind im Runbook beschrieben:
|
||||||
|
<code className="ml-1">docs/HERMES_SETUP.md</code>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user