phase1a: Box-Diaet - schlafende Dienste, Sonntags-Update als Timer, tote Skills raus, Warm-Set nur Hirn
Ampel / ampel (push) Successful in 26s
Ampel / ampel (push) Successful in 26s
- Waechter und Dienste-Liste kennen "schlaeft": abgeschaltete Units (disable --now) sind kein Befund mehr; die Oberflaeche zeigt sie grau mit Knopf "Wecken" (fuer die Android-App spaeter). - Updates am Sonntag laufen wieder ueber mc2-autoupdate.timer (jobs/sonntags-update.sh) statt als Hermes-Cron, der sich beim Hermes-Update selbst neu startete (20.09.: 180 s, Fehlschlag). Flugplan und Waechter kennen den Timer. - 10 abgeloeste Skills (Werkstatt, Kanban, Orchestrator, Trend-Radar ...) aus dem Repo; deploy.sh verschiebt sie in ~/.hermes/skills-archiv statt sie weiter zu Lucy zu kopieren. - Embedding und Reranker schlafen: raus aus brains und Warm-Set, ttl 300, warmup.sh waermt sie nicht mehr vor. deploy.sh spielt Timer und Warm-Set-Drop-in selbst aus. - Dienste-Namen: "Box-Wart (MC2)", "Hermes-Dashboard", "Spracherkennung (Desktop-Lucy)". - Frontend neu gebaut (npm ci, lokale Pakete waren vom 28.08.). Tests: 87 gruen (neu: schlafende Dienste), Frontend Lint/Tests/Build gruen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
648be33d21
commit
45b5bf275c
@@ -14,7 +14,7 @@ from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL, V1_UPSTREAM, VOI
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
from services import backup as backup_svc
|
||||
from services import maintenance
|
||||
from services import maintenance, waechter
|
||||
from services.agent import agent_status
|
||||
from services.gateway import gateway_reachable
|
||||
from services.llamaswap import engine_reachable, list_models
|
||||
@@ -80,7 +80,7 @@ def services() -> dict:
|
||||
"ok": gateway_reachable(), "scope": "user"})
|
||||
# MC2 selbst antwortet gerade auf diesen Request — ok=True ist hier ehrlich;
|
||||
# die Zeile existiert für Restart/Logs, nicht als Erreichbarkeits-Orakel.
|
||||
rows.append({"name": "Steuerpult (MC2)", "unit": "mission-control-2", "url": gw_url,
|
||||
rows.append({"name": "Box-Wart (MC2)", "unit": "mission-control-2", "url": gw_url,
|
||||
"ok": True, "scope": "user"})
|
||||
else:
|
||||
rows.append({"name": "Gateway (integriert)", "unit": "mission-control-2", "url": gw_url,
|
||||
@@ -90,15 +90,20 @@ def services() -> dict:
|
||||
"ok": _user_unit_active("mc2-steward"), "scope": "user"},
|
||||
{"name": "Hermes-Gateway (Agent & Gedächtnis)", "unit": "hermes-gateway", "url": HERMES_API_URL,
|
||||
"ok": a["gateway_reachable"], "scope": "user"},
|
||||
{"name": "Hermes-GUI (Desktop-Gateway)", "unit": "hermes-builtin-ui", "url": a["hermes_ui_url"],
|
||||
{"name": "Hermes-Dashboard", "unit": "hermes-builtin-ui", "url": a["hermes_ui_url"],
|
||||
"ok": a["hermes_ui_reachable"], "scope": "user"},
|
||||
{"name": "Voice (STT/TTS)", "unit": "voice-service", "url": VOICE_SERVICE_URL,
|
||||
{"name": "Spracherkennung (Desktop-Lucy)", "unit": "voice-service", "url": VOICE_SERVICE_URL,
|
||||
"ok": _voice_reachable(), "scope": "user"},
|
||||
# Lucys Stimme (pocket-tts) spricht Telegram und Lucy-Desktop — bis 09/2026 fehlte
|
||||
# sie hier; die Box-Konsole ist mit dem Box-Wart-Umbau aus MC2 raus.
|
||||
{"name": "Lucys Stimme", "unit": "lucy-stimme", "url": "",
|
||||
"ok": _user_unit_active("lucy-stimme"), "scope": "user"},
|
||||
]
|
||||
# Bewusst abgeschaltete Dienste (24.09.2026: Spracherkennung bis zur Android-App) sind kein
|
||||
# Fehler — die Oberfläche zeigt sie als „schläft“ mit Knopf zum Wecken statt rot.
|
||||
for row in rows:
|
||||
row["schlaeft"] = (not row["ok"] and row["scope"] == "user"
|
||||
and waechter.schlaeft(waechter.dienst_zustand(row["unit"])))
|
||||
return {
|
||||
"services": rows,
|
||||
"links": {
|
||||
|
||||
@@ -74,7 +74,7 @@ DIENSTE: dict[str, tuple[bool, bool, str]] = {
|
||||
"hermes-gateway": (False, True, "Hermes"),
|
||||
"mc2-gateway": (False, True, "Modell-Gateway"),
|
||||
"mission-control-2": (False, True, "MC2"),
|
||||
"voice-service": (False, False, "Hör-Dienst"),
|
||||
"voice-service": (False, False, "Spracherkennung"),
|
||||
"lucy-stimme": (False, False, "Lucys Stimme"),
|
||||
"hermes-builtin-ui": (False, False, "Hermes-Dashboard"),
|
||||
}
|
||||
@@ -86,6 +86,8 @@ TIMER_DIENSTE: dict[str, tuple[str, bool]] = {
|
||||
"mc2-radar": ("Modell-Radar", False),
|
||||
# Rot: scheitert sie, erfährst du nichts von dem, was nachts passiert ist.
|
||||
"mc2-morgenmeldung": ("Morgenmeldung", True),
|
||||
# Seit 24.09.2026 ein eigener Timer statt Hermes-Cron (Hermes startet sich beim Update selbst neu).
|
||||
"mc2-autoupdate": ("Updates am Sonntag", True),
|
||||
}
|
||||
|
||||
HERMES_JOBS_PATH = HERMES_HOME / "cron" / "jobs.json"
|
||||
@@ -119,7 +121,7 @@ def _systemctl_show(name: str, system: bool) -> dict[str, str]:
|
||||
"""Zustand einer Unit. Auf Windows (Dev) oder ohne systemd: leeres Dict (harmlos)."""
|
||||
cmd = ["systemctl"] if system else ["systemctl", "--user"]
|
||||
cmd += ["show", name, "-p",
|
||||
"LoadState,ActiveState,SubState,Result,ExecMainStatus,InactiveExitTimestamp"]
|
||||
"LoadState,ActiveState,SubState,Result,ExecMainStatus,InactiveExitTimestamp,UnitFileState"]
|
||||
try:
|
||||
out = subprocess.run(cmd, capture_output=True, text=True, timeout=10).stdout
|
||||
except Exception:
|
||||
@@ -178,6 +180,17 @@ def _update_laeuft() -> bool:
|
||||
|
||||
# --- Prüfungen ------------------------------------------------------------------
|
||||
|
||||
def dienst_zustand(name: str, system: bool = False) -> dict[str, str]:
|
||||
"""systemd-Zustand einer Unit für andere Module (Dienste-Liste der Oberfläche)."""
|
||||
return _systemctl_show(name, system)
|
||||
|
||||
|
||||
def schlaeft(zustand: dict[str, str]) -> bool:
|
||||
"""Abgeschaltet und nicht mehr für den Autostart eingetragen = bewusst schlafen gelegt
|
||||
(24.09.2026: Konsole, Spracherkennung). Das ist kein Fehler; geweckt wird per Knopf."""
|
||||
return zustand.get("ActiveState") == "inactive" and zustand.get("UnitFileState") == "disabled"
|
||||
|
||||
|
||||
def pruefe_dienste() -> list[Befund]:
|
||||
befunde: list[Befund] = []
|
||||
for name, (system, wichtig, anzeige) in DIENSTE.items():
|
||||
@@ -187,6 +200,8 @@ def pruefe_dienste() -> list[Befund]:
|
||||
zustand = z.get("ActiveState", "")
|
||||
if zustand in ("active", "activating", "reloading", "deactivating"):
|
||||
continue
|
||||
if schlaeft(z):
|
||||
continue # bewusst schlafen gelegt (disable --now), z. B. die Spracherkennung bis zur App
|
||||
stufe = "rot" if wichtig else "gelb"
|
||||
aktionen = [_aktion("neustart", "Neu starten", dienst=name), _aktion("protokoll", "Protokoll", dienst=name)]
|
||||
if zustand == "failed":
|
||||
|
||||
@@ -15,7 +15,7 @@ from zoneinfo import ZoneInfo
|
||||
from services import modell_nutzung, waechter
|
||||
|
||||
TIMER = {"mc2-backup.timer": "Sicherung", "mc2-radar.timer": "Modell-Radar",
|
||||
"mc2-morgenmeldung.timer": "Morgenmeldung"}
|
||||
"mc2-morgenmeldung.timer": "Morgenmeldung", "mc2-autoupdate.timer": "Updates am Sonntag"}
|
||||
LOCAL_TZ = ZoneInfo(os.environ.get("MC_LOCAL_TZ", "Europe/Berlin"))
|
||||
|
||||
|
||||
|
||||
@@ -104,3 +104,19 @@ def test_befunde_werden_erst_nach_fail_after_takten_zum_hinweis(monkeypatch, tmp
|
||||
|
||||
stand = waechter.lese_stand()
|
||||
assert stand["aktiv"] is True and stand["hinweise"] == []
|
||||
|
||||
|
||||
def test_schlafende_dienste_sind_kein_befund(monkeypatch):
|
||||
"""24.09.2026: Spracherkennung und Konsole sind bewusst abgeschaltet (disable --now) — kein Alarm.
|
||||
Ein gestoppter, aber noch eingetragener Dienst bleibt dagegen ein Hinweis."""
|
||||
zustaende = {
|
||||
"voice-service": {"LoadState": "loaded", "ActiveState": "inactive", "UnitFileState": "disabled"},
|
||||
"lucy-stimme": {"LoadState": "loaded", "ActiveState": "inactive", "UnitFileState": "enabled"},
|
||||
}
|
||||
monkeypatch.setattr(waechter, "_systemctl_show",
|
||||
lambda name, system: zustaende.get(name, {"LoadState": "loaded", "ActiveState": "active"}))
|
||||
ids = {b.id for b in waechter.pruefe_dienste()}
|
||||
assert "dienst:voice-service" not in ids
|
||||
assert "dienst:lucy-stimme" in ids
|
||||
assert waechter.schlaeft(zustaende["voice-service"]) is True
|
||||
assert waechter.schlaeft({"ActiveState": "failed", "UnitFileState": "disabled"}) is False
|
||||
|
||||
Reference in New Issue
Block a user