Hermes-Update-Playbook: doctor im Job, Config-Drift-Scan + Tool-/Voice-Smoke im Postcheck, LLM-Release-Notes im Modal
- Update-Job: nach 'hermes update' laeuft 'hermes doctor' (Job rot bei Fehlern) - hermes-postcheck.sh: Journal-Scan auf Unknown/deprecated/defaulting (Lehre aus v0.18: approvals.mode 'auto' wurde still ungueltig -> alle Tools in pending_approval), Tool-Smoke (echo via Agent, erkennt pending_approval), Voice-Smoke (/api/voice/chat) - Update-Modal: die Box fasst anstehende Hermes-Commits selbst zusammen (fast-Modell, no-think, gecacht auf neuesten Hash) — Breaking Changes zuerst Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -359,8 +359,44 @@ def swap_update_details() -> dict:
|
|||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
# LLM-Zusammenfassung der anstehenden Hermes-Commits (die Box liest ihre Release-Notes selbst).
|
||||||
|
# Gecacht auf den neuesten Commit-Hash — das Modal darf beliebig oft geöffnet werden.
|
||||||
|
_relnotes_cache: dict = {"key": "", "summary": ""}
|
||||||
|
|
||||||
|
|
||||||
|
def _summarize_hermes_commits(commits: list[dict]) -> str:
|
||||||
|
key = commits[0]["hash"] if commits else ""
|
||||||
|
if not key:
|
||||||
|
return ""
|
||||||
|
if _relnotes_cache["key"] == key and _relnotes_cache["summary"]:
|
||||||
|
return _relnotes_cache["summary"]
|
||||||
|
from config import LLAMA_SWAP_URL
|
||||||
|
subjects = "\n".join(f"- {c['subject']}" for c in commits[:100])
|
||||||
|
prompt = (
|
||||||
|
"Du bist der Update-Berater einer lokalen AI-Box (Hermes-Agent auf Strix Halo; genutzt werden: "
|
||||||
|
"api_server/Gateway, memory-provider-Plugin 'mc2-memory', terminal-/web-Tools, approvals, cron). "
|
||||||
|
"Hier die Commit-Titel des anstehenden Hermes-Updates:\n\n" + subjects + "\n\n"
|
||||||
|
"Fasse auf DEUTSCH in maximal 6 Stichpunkten zusammen: (1) Breaking Changes oder geänderte/"
|
||||||
|
"entfernte Config-Schlüssel (WICHTIGSTES zuerst, explizit warnen), (2) was unser Setup betrifft, "
|
||||||
|
"(3) welche neuen Features sich für uns lohnen. Keine Einleitung, nur die Punkte."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
r = httpx.post(f"{LLAMA_SWAP_URL}/v1/chat/completions", timeout=90.0, json={
|
||||||
|
"model": "fast", "max_tokens": 380, "temperature": 0.2,
|
||||||
|
"chat_template_kwargs": {"enable_thinking": False},
|
||||||
|
"messages": [{"role": "user", "content": prompt}],
|
||||||
|
})
|
||||||
|
r.raise_for_status()
|
||||||
|
summary = ((r.json().get("choices") or [{}])[0].get("message", {}).get("content") or "").strip()
|
||||||
|
if summary:
|
||||||
|
_relnotes_cache.update(key=key, summary=summary)
|
||||||
|
return summary
|
||||||
|
except Exception as exc: # noqa: BLE001 — Zusammenfassung ist Komfort, nie Blocker
|
||||||
|
return f"(Zusammenfassung nicht verfügbar: {exc})"
|
||||||
|
|
||||||
|
|
||||||
def hermes_update_details() -> dict:
|
def hermes_update_details() -> dict:
|
||||||
"""Commits, die ein Hermes-Update einspielen würde (HEAD..origin/<branch>)."""
|
"""Commits, die ein Hermes-Update einspielen würde (HEAD..origin/<branch>) + LLM-Zusammenfassung."""
|
||||||
info: dict = {"kind": "hermes", "branch": None, "behind": 0, "commits": []}
|
info: dict = {"kind": "hermes", "branch": None, "behind": 0, "commits": []}
|
||||||
git = system.find_hermes_agent_git()
|
git = system.find_hermes_agent_git()
|
||||||
if not git or not git.get("path"):
|
if not git or not git.get("path"):
|
||||||
@@ -382,6 +418,8 @@ def hermes_update_details() -> dict:
|
|||||||
commits.append({"hash": parts[0], "subject": parts[1], "when": parts[2]})
|
commits.append({"hash": parts[0], "subject": parts[1], "when": parts[2]})
|
||||||
info["commits"] = commits
|
info["commits"] = commits
|
||||||
info["behind"] = len(commits)
|
info["behind"] = len(commits)
|
||||||
|
if commits:
|
||||||
|
info["summary"] = _summarize_hermes_commits(commits)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
info["error"] = str(exc)
|
info["error"] = str(exc)
|
||||||
return info
|
return info
|
||||||
@@ -549,10 +587,14 @@ def hermes_update_job() -> dict:
|
|||||||
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")
|
postcheck = os.path.join(_REPO_ROOT, "deploy", "hermes-postcheck.sh")
|
||||||
# Backup → update → Gateway-Neustart → Gehirn-Check (Job wird rot, wenn Mem0/Plugin kaputt).
|
# Backup → update → Gateway-Neustart → Gehirn-Check (Job wird rot, wenn Mem0/Plugin kaputt).
|
||||||
|
# Backup → update → doctor (Config/Deps-Diagnose der NEUEN Version) → Gateway-Neustart →
|
||||||
|
# Gehirn-Check (erweitert um Config-Drift-Scan, Tool- und Voice-Smoke — Lehre aus v0.18:
|
||||||
|
# 'approvals.mode auto' wurde still ungültig und legte alle Tools in pending_approval).
|
||||||
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"&& {py} -m hermes_cli.main doctor "
|
||||||
f"&& systemctl --user restart hermes-gateway "
|
f"&& systemctl --user restart hermes-gateway "
|
||||||
f"&& sleep 4 && bash {postcheck}")
|
f"&& sleep 6 && 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
|
||||||
|
|||||||
@@ -38,6 +38,44 @@ else
|
|||||||
echo "FAIL · mc2-memory-Plugin lädt nicht (MemoryProvider-ABC geändert?)"; fail=1
|
echo "FAIL · mc2-memory-Plugin lädt nicht (MemoryProvider-ABC geändert?)"; fail=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- Config-Drift-Wächter (Lehre aus v0.18, 02.07.2026): Hermes fällt bei unbekannten ---
|
||||||
|
# --- Config-Werten STILL auf Defaults zurück (approvals.mode 'auto' -> manual -> alle ---
|
||||||
|
# --- Tools hingen in pending_approval). Solche Warnungen müssen den Job ROT machen. ---
|
||||||
|
if journalctl --user -u hermes-gateway --since "-3 minutes" --no-pager -o cat 2>/dev/null \
|
||||||
|
| grep -iE "Unknown [a-z._]+ '|deprecated .*(setting|option|config)|defaulting to|invalid config" \
|
||||||
|
| grep -v "check_fn" | head -5 | tee /tmp/hermes-config-warnings.txt | grep -q .; then
|
||||||
|
echo "FAIL · Config-Warnungen nach Update (siehe oben) — Config an neue Version anpassen!"; fail=1
|
||||||
|
else
|
||||||
|
echo "PASS · keine Config-Drift-Warnungen im Gateway-Log"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Tool-Smoke: ein harmloser terminal-Befehl durch den echten Agenten. Fängt kaputte ---
|
||||||
|
# --- Approval-/Tool-Ketten (Antwort muss kommen und darf nicht in pending_approval hängen). ---
|
||||||
|
API_KEY="$(grep -E '^API_SERVER_KEY=' "$HERMES/.env" 2>/dev/null | cut -d= -f2- | tr -d '"' | tr -d "'")"
|
||||||
|
if [ -n "$API_KEY" ]; then
|
||||||
|
TOOL_RESP=$(curl -sf -m 90 -X POST "http://127.0.0.1:8642/v1/chat/completions" \
|
||||||
|
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
|
||||||
|
-H "X-Hermes-Session-Id: postcheck-tool-smoke" \
|
||||||
|
-d '{"model":"hermes","stream":false,"messages":[{"role":"user","content":"Führe im Terminal exakt den Befehl echo postcheck-ok aus und gib mir nur dessen Ausgabe zurück."}]}' 2>/dev/null)
|
||||||
|
if echo "$TOOL_RESP" | grep -qi "postcheck-ok"; then
|
||||||
|
echo "PASS · Tool-Smoke (terminal via Agent) liefert Ergebnis"
|
||||||
|
elif echo "$TOOL_RESP" | grep -qi "pending_approval"; then
|
||||||
|
echo "FAIL · Tool-Smoke hängt in pending_approval (approvals.mode prüfen!)"; fail=1
|
||||||
|
else
|
||||||
|
echo "FAIL · Tool-Smoke ohne verwertbares Ergebnis: $(echo "$TOOL_RESP" | head -c 200)"; fail=1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "SKIP · Tool-Smoke (kein API_SERVER_KEY in $HERMES/.env gefunden)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Voice-Smoke: Lucys Sprech-Pfad antwortet zügig (first byte des SSE-Streams). ---
|
||||||
|
if curl -sf -m 30 -N -X POST "$MC_URL/api/voice/chat" -H "Content-Type: application/json" \
|
||||||
|
-d '{"text":"Sag nur ok.","session_id":"postcheck-voice-smoke"}' 2>/dev/null | head -c 50 | grep -q "data:"; then
|
||||||
|
echo "PASS · Voice-Smoke (Lucys /api/voice/chat streamt)"
|
||||||
|
else
|
||||||
|
echo "FAIL · Voice-Smoke: /api/voice/chat streamt nicht"; fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$fail" -eq 0 ]; then
|
if [ "$fail" -eq 0 ]; then
|
||||||
echo "=== GEHIRN OK ✓ ==="
|
echo "=== GEHIRN OK ✓ ==="
|
||||||
else
|
else
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<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-B-6TT-6s.js"></script>
|
<script type="module" crossorigin src="/assets/index-DvUiHM4g.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-_Rap01H_.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-_Rap01H_.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -92,6 +92,12 @@ export function UpdateDetailModal({ detail, maintenanceJob, onClose, onApply }:
|
|||||||
<div className="text-muted-foreground">Keine neuen Commits — Hermes-Agent ist bereits aktuell.</div>
|
<div className="text-muted-foreground">Keine neuen Commits — Hermes-Agent ist bereits aktuell.</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
{d?.summary && (
|
||||||
|
<div className="rounded-lg border border-primary/25 bg-primary/5 p-3 space-y-1">
|
||||||
|
<div className="text-[10px] font-bold uppercase tracking-wider text-primary">Was dieses Update bedeutet (Zusammenfassung der Box)</div>
|
||||||
|
<pre className="whitespace-pre-wrap text-[11px] leading-relaxed text-foreground/90 font-sans">{d.summary}</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="text-muted-foreground">{d!.behind} neue Commit(s) auf <span className="font-mono text-foreground">origin/{d!.branch}</span>:</div>
|
<div className="text-muted-foreground">{d!.behind} neue Commit(s) auf <span className="font-mono text-foreground">origin/{d!.branch}</span>:</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{d!.commits!.map((c) => (
|
{d!.commits!.map((c) => (
|
||||||
|
|||||||
@@ -306,6 +306,8 @@ export interface UpdatesResp {
|
|||||||
export interface UpdateDetails {
|
export interface UpdateDetails {
|
||||||
kind: "os" | "engine" | "swap" | "hermes"
|
kind: "os" | "engine" | "swap" | "hermes"
|
||||||
error?: string
|
error?: string
|
||||||
|
// hermes: LLM-Zusammenfassung der anstehenden Commits (Breaking Changes zuerst)
|
||||||
|
summary?: string
|
||||||
// os
|
// os
|
||||||
count?: number
|
count?: number
|
||||||
packages?: { name: string; current: string; candidate: string }[]
|
packages?: { name: string; current: string; candidate: string }[]
|
||||||
|
|||||||
Reference in New Issue
Block a user