Feat: Hermes-Update Gehirn-Check + Drawer breiter & farbige Update-Icons

- Post-Update-Gehirn-Check: hermes_update_job haengt deploy/hermes-postcheck.sh an
  (Mem0-Sidecar erreichbar? /api/memory? memory.provider=mc2-memory aktiv? Plugin laedt?).
  Bricht der Check, wird der Job rot -> kaputtes Gehirn faellt sofort auf. Standalone verifiziert.
- SystemDrawer: Breite 500->640px (Log-Fenster endlich lesbar).
- Update-Zeilen: farbige Icons (OS cyan, Engine violet, Hermes amber, Modell emerald) wie im
  Mockup, statt einheitlich grau. Aktiv-Zustand (amber) live verifiziert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 22:16:19 +02:00
parent 58dda66f84
commit d3157d2535
8 changed files with 64 additions and 15 deletions
+4 -1
View File
@@ -354,9 +354,12 @@ def hermes_update_job() -> dict:
path = (git or {}).get("path") or os.path.expanduser("~/.hermes/hermes-agent") path = (git or {}).get("path") or os.path.expanduser("~/.hermes/hermes-agent")
py = os.path.join(path, "venv", "bin", "python") py = os.path.join(path, "venv", "bin", "python")
backup = os.path.join(_REPO_ROOT, "deploy", "backup.sh") backup = os.path.join(_REPO_ROOT, "deploy", "backup.sh")
postcheck = os.path.join(_REPO_ROOT, "deploy", "hermes-postcheck.sh")
# Backup → update → Gateway-Neustart → Gehirn-Check (Job wird rot, wenn Mem0/Plugin kaputt).
cmd = (f"bash {backup} || true; " cmd = (f"bash {backup} || true; "
f"cd {path} && {py} -m hermes_cli.main update --yes " f"cd {path} && {py} -m hermes_cli.main update --yes "
f"&& systemctl --user restart hermes-gateway") f"&& systemctl --user restart hermes-gateway "
f"&& sleep 4 && bash {postcheck}")
def on_done(): def on_done():
_comp_cache.update(ts=0.0, data=[]) # Update-Status neu berechnen lassen _comp_cache.update(ts=0.0, data=[]) # Update-Status neu berechnen lassen
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Post-Update-Check nach einem Hermes-Agent-Update: läuft unser geteiltes „Gehirn"
# (Mem0 + die mc2-memory-Integration) noch? Exit 0 = alles ok, sonst 1 → der Job wird
# im UI rot, damit ein kaputtes Gehirn sofort auffällt.
set -uo pipefail
MC_URL="${MC_URL:-http://127.0.0.1:9001}"
MEM0_URL="${MEM0_SERVICE_URL:-http://127.0.0.1:8765}"
HERMES="${HERMES_HOME:-$HOME/.hermes}"
fail=0
echo "=== Hermes Post-Update: Gehirn-Check ==="
if curl -sf -m 5 "$MEM0_URL/health" >/dev/null 2>&1; then
echo "PASS · Mem0-Sidecar erreichbar"
else
echo "FAIL · Mem0-Sidecar NICHT erreichbar"; fail=1
fi
if curl -sf -m 8 "$MC_URL/api/memory" >/dev/null 2>&1; then
echo "PASS · /api/memory antwortet"
else
echo "FAIL · /api/memory antwortet nicht"; fail=1
fi
if grep -qE "^[[:space:]]*provider:[[:space:]]*'?mc2-memory'?" "$HERMES/config.yaml" 2>/dev/null \
&& grep -qE "memory_enabled:[[:space:]]*true" "$HERMES/config.yaml" 2>/dev/null; then
echo "PASS · memory.provider=mc2-memory aktiv"
else
echo "FAIL · memory.provider nicht mehr gesetzt (Config vom Update überschrieben?)"; fail=1
fi
if ( cd "$HERMES/hermes-agent" && HERMES_HOME="$HERMES" ./venv/bin/python -c \
"import sys; sys.path.insert(0,'.'); from plugins.memory import load_memory_provider; p=load_memory_provider('mc2-memory'); assert p and p.name()=='mc2-memory'" \
>/dev/null 2>&1 ); then
echo "PASS · mc2-memory-Plugin lädt unter dem neuen Hermes"
else
echo "FAIL · mc2-memory-Plugin lädt nicht (MemoryProvider-ABC geändert?)"; fail=1
fi
if [ "$fail" -eq 0 ]; then
echo "=== GEHIRN OK ✓ ==="
else
echo "=== GEHIRN-CHECK FEHLGESCHLAGEN — bitte prüfen ==="
fi
exit "$fail"
@@ -1,4 +1,4 @@
import{r as K,a as Hv,g as Hr,R as Um,c as q2,j as Ne,l as Z2,S as K2,b as Vx,T as $2}from"./index-cq5kdO0r.js";/** import{r as K,a as Hv,g as Hr,R as Um,c as q2,j as Ne,l as Z2,S as K2,b as Vx,T as $2}from"./index-CazXw6yq.js";/**
* @license * @license
* Copyright 2010-2023 Three.js Authors * Copyright 2010-2023 Three.js Authors
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
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
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" /> <link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title> <title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-cq5kdO0r.js"></script> <script type="module" crossorigin src="/assets/index-CazXw6yq.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-3d2FUwyu.css"> <link rel="stylesheet" crossorigin href="/assets/index-SdhW3gX3.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+8 -8
View File
@@ -20,13 +20,13 @@ const SERVICES = [
{ id: "llama-swap", label: "Llama Swap", type: "system", reach: "llama-swap" }, { id: "llama-swap", label: "Llama Swap", type: "system", reach: "llama-swap" },
] ]
function UpdateRow({ icon: Icon, name, status, available, busy, actionLabel, onAction }: { function UpdateRow({ icon: Icon, iconClass, name, status, available, busy, actionLabel, onAction }: {
icon: any; name: string; status: string; available: boolean; busy?: boolean; actionLabel: string; onAction: () => void icon: any; iconClass: string; name: string; status: string; available: boolean; busy?: boolean; actionLabel: string; onAction: () => void
}) { }) {
return ( return (
<div className={cn("flex items-center gap-3 rounded-lg border px-3 py-2", <div className={cn("flex items-center gap-3 rounded-lg border px-3 py-2",
available ? "border-amber-500/30 bg-amber-500/5" : "border-border/50 bg-background/20")}> available ? "border-amber-500/30 bg-amber-500/5" : "border-border/50 bg-background/20")}>
<Icon className={cn("h-4 w-4 shrink-0", available ? "text-amber-400" : "text-muted-foreground")} /> <Icon className={cn("h-4 w-4 shrink-0", iconClass)} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-xs text-foreground truncate">{name}</div> <div className="text-xs text-foreground truncate">{name}</div>
<div className={cn("text-[10px] truncate", available ? "text-amber-400/90" : "text-muted-foreground")}>{status}</div> <div className={cn("text-[10px] truncate", available ? "text-amber-400/90" : "text-muted-foreground")}>{status}</div>
@@ -356,7 +356,7 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
{/* Drawer */} {/* Drawer */}
<div <div
className={cn( className={cn(
"fixed inset-y-0 right-0 w-full sm:w-[500px] bg-card/85 backdrop-blur-xl border-l border-border/60 shadow-2xl z-50 flex flex-col transform transition-transform duration-300 ease-in-out font-sans text-foreground", "fixed inset-y-0 right-0 w-full sm:w-[640px] bg-card/85 backdrop-blur-xl border-l border-border/60 shadow-2xl z-50 flex flex-col transform transition-transform duration-300 ease-in-out font-sans text-foreground",
open ? "translate-x-0" : "translate-x-full" open ? "translate-x-0" : "translate-x-full"
)} )}
> >
@@ -428,16 +428,16 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
<div className="text-[9px] text-muted-foreground -mt-1">Zuletzt gesucht: {formatLastCheck(updates.last_check)}</div> <div className="text-[9px] text-muted-foreground -mt-1">Zuletzt gesucht: {formatLastCheck(updates.last_check)}</div>
)} )}
<div className="space-y-1.5"> <div className="space-y-1.5">
<UpdateRow icon={Shield} name="OS-Pakete (apt)" available={!!updates?.os} status={updates?.os ? `${updates.os} verfügbar` : "aktuell"} actionLabel="Aktualisieren" onAction={triggerOsUpdate} /> <UpdateRow icon={Shield} iconClass="text-cyan-400" name="OS-Pakete (apt)" available={!!updates?.os} status={updates?.os ? `${updates.os} verfügbar` : "aktuell"} actionLabel="Aktualisieren" onAction={triggerOsUpdate} />
<UpdateRow icon={Server} name="Engine (llama.cpp)" available={!!updates?.engine} status={updates?.engine ? "Update verfügbar" : "aktuell"} actionLabel="Aktualisieren" onAction={triggerEngineUpdate} /> <UpdateRow icon={Server} iconClass="text-violet-400" name="Engine (llama.cpp)" available={!!updates?.engine} status={updates?.engine ? "Update verfügbar" : "aktuell"} actionLabel="Aktualisieren" onAction={triggerEngineUpdate} />
{(() => { {(() => {
const h = updates?.components?.find((c) => c.key === "hermes_agent") const h = updates?.components?.find((c) => c.key === "hermes_agent")
return ( return (
<UpdateRow icon={Bot} name="Hermes-Agent" available={h?.update === true} busy={hermesUpdating} status={h?.update === true ? `Update: ${h.latest}` : h?.reachable === false ? "offline" : "aktuell"} actionLabel="Aktualisieren" onAction={triggerHermesUpdate} /> <UpdateRow icon={Bot} iconClass="text-amber-400" name="Hermes-Agent" available={h?.update === true} busy={hermesUpdating} status={h?.update === true ? `Update: ${h.latest}` : h?.reachable === false ? "offline" : "aktuell"} actionLabel="Aktualisieren" onAction={triggerHermesUpdate} />
) )
})()} })()}
{updates?.model_list?.map((m) => ( {updates?.model_list?.map((m) => (
<UpdateRow key={m.role} icon={Box} name={`Modell · ${m.role}`} available={true} status={m.title} actionLabel="Upgrade" onAction={() => triggerModelUpgrade(m.repo, m.role)} /> <UpdateRow key={m.role} icon={Box} iconClass="text-emerald-400" name={`Modell · ${m.role}`} available={true} status={m.title} actionLabel="Upgrade" onAction={() => triggerModelUpgrade(m.repo, m.role)} />
))} ))}
</div> </div>
</div> </div>