Autonomie 0d+0g: Lucy-Alarm-Weg, taegliche Selbst-Smokes, Selbstreparatur nach rotem Update
- /api/alarm: Lucy-unabhaengiger Telegram-Weg (fuer den PC-Lucy-Watchdog, wenn die App haengt). - self-smoke.sh + mc2-selfsmoke.timer (taeglich 07:15): Gateway/Tools/Voice aktiv durchspielen, Meldung nur bei Rot. Ergaenzt den passiven Health-Waechter um echte Funktion. - self-repair.sh + autoupdate.sh: bei rotem Hermes-Update zieht die Box Config-Brueche selbst (nur eindeutige, nicht-sicherheitsrelevante Keys; Backup -> YAML-Check -> Gehirn-Check, sonst zurueck). Greift es nicht, haengt eine LLM-Diagnose an die Rollback-Meldung. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,31 @@ class AnnounceIn(BaseModel):
|
|||||||
priority: str = "normal" # 'silent' = nur im Verlauf zeigen, nicht sprechen
|
priority: str = "normal" # 'silent' = nur im Verlauf zeigen, nicht sprechen
|
||||||
|
|
||||||
|
|
||||||
|
class AlarmIn(BaseModel):
|
||||||
|
text: str # die Alarm-Meldung
|
||||||
|
subject: str = "[Alarm]" # Betreff (Telegram-Präfix)
|
||||||
|
source: str = "alarm" # Absender fürs Log/Panel (z.B. "lucy-watchdog")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/alarm")
|
||||||
|
def alarm(body: AlarmIn) -> dict:
|
||||||
|
"""Lucy-UNABHÄNGIGER Alarm-Weg: schickt direkt auf Telegram (und legt die Meldung in den
|
||||||
|
Briefkasten). Für Absender, die NICHT auf die sprechende Lucy zählen können — allen voran
|
||||||
|
der PC-seitige Lucy-Watchdog, wenn die Desktop-App selbst hängt (dann nützt der Briefkasten
|
||||||
|
nichts, weil niemand ihn vorliest → Telegram ist der einzige verlässliche Kanal). LAN-only
|
||||||
|
wie alle MC2-Endpoints."""
|
||||||
|
text = (body.text or "").strip()
|
||||||
|
if not text:
|
||||||
|
raise HTTPException(400, "Leere Meldung.")
|
||||||
|
subject = (body.subject or "[Alarm]").strip()
|
||||||
|
try:
|
||||||
|
item = announce.add(text, subject, body.source or "alarm", "normal")
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(400, str(exc))
|
||||||
|
announce.notify_telegram(subject, text) # best-effort Telegram (posix/bash; Windows = No-op)
|
||||||
|
return {"ok": True, "item": item}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/voice/announce")
|
@router.post("/voice/announce")
|
||||||
def voice_announce(body: AnnounceIn) -> dict:
|
def voice_announce(body: AnnounceIn) -> dict:
|
||||||
"""Meldung in den Briefkasten legen (Lucy-Proaktivität). Absender: Health-Wächter,
|
"""Meldung in den Briefkasten legen (Lucy-Proaktivität). Absender: Health-Wächter,
|
||||||
|
|||||||
+17
-3
@@ -126,8 +126,20 @@ check_hermes(){
|
|||||||
notify "Hermes-Agent aktualisiert ($behind Commits). Gehirn-Check (Mem0, Tools, Voice) grün — alles läuft."
|
notify "Hermes-Agent aktualisiert ($behind Commits). Gehirn-Check (Mem0, Tools, Voice) grün — alles läuft."
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
# Rot/Timeout ⇒ Rollback: Code auf alten Stand, Config aus dem frischen Backup, Neustart, Gehirn-Check.
|
# Rot/Timeout: ERST Selbstreparatur versuchen (Config-Bruch selbst ziehen, neue Version behalten) —
|
||||||
say "$name: Job endete '$state' — ROLLBACK auf $old_head…"
|
# Autonomie 0g. Nur Config, reversibel, hart gegatet; scheitert es, fällt es sauber in den Rollback.
|
||||||
|
say "$name: Job endete '$state' — versuche Selbstreparatur (Config) vor dem Rollback…"
|
||||||
|
local sr_out sr_rc sugg=""
|
||||||
|
sr_out="$(bash "$SRC_DIR/self-repair.sh" 2>&1)"; sr_rc=$?
|
||||||
|
echo "$sr_out"
|
||||||
|
if [ "$sr_rc" -eq 0 ]; then
|
||||||
|
SUMMARY+=("$name: Config-Bruch nach Update SELBST repariert — neue Version läuft (kein Rollback).")
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
sugg="$(echo "$sr_out" | sed -n 's/^SELFREPAIR_SUGGESTION=//p' | tail -1)"
|
||||||
|
|
||||||
|
# Rollback: Code auf alten Stand, Config aus dem frischen Backup, Neustart, Gehirn-Check.
|
||||||
|
say "$name: Selbstreparatur griff nicht — ROLLBACK auf $old_head…"
|
||||||
if [ -n "$old_head" ]; then
|
if [ -n "$old_head" ]; then
|
||||||
git -C "$HERMES_DIR" reset --hard "$old_head" >/dev/null 2>&1
|
git -C "$HERMES_DIR" reset --hard "$old_head" >/dev/null 2>&1
|
||||||
local bak stage
|
local bak stage
|
||||||
@@ -143,7 +155,9 @@ check_hermes(){
|
|||||||
if bash "$SRC_DIR/hermes-postcheck.sh" >/dev/null 2>&1; then
|
if bash "$SRC_DIR/hermes-postcheck.sh" >/dev/null 2>&1; then
|
||||||
set_pin hermes "$old_head" "Update ($behind Commits) riss den Gehirn-Check; Rollback grün"
|
set_pin hermes "$old_head" "Update ($behind Commits) riss den Gehirn-Check; Rollback grün"
|
||||||
SUMMARY+=("$name: Update FEHLGESCHLAGEN → zurückgerollt + GEPINNT auf ${old_head:0:9}.")
|
SUMMARY+=("$name: Update FEHLGESCHLAGEN → zurückgerollt + GEPINNT auf ${old_head:0:9}.")
|
||||||
notify "Hermes-Update fehlgeschlagen (Gehirn-Check rot). Automatisch zurückgerollt auf ${old_head:0:9} — läuft wieder. Hermes ist jetzt GEPINNT."
|
notify "Hermes-Update fehlgeschlagen (Gehirn-Check rot). Selbstreparatur der Config griff nicht, deshalb automatisch zurückgerollt auf ${old_head:0:9} — läuft wieder. Hermes ist jetzt GEPINNT.${sugg:+
|
||||||
|
|
||||||
|
Wahrscheinliche Ursache/Fix (Box-Diagnose): $sugg}"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ cp "$SRC/deploy/mc2-backup.timer" "$HOME/.config/systemd/user/mc2-backup.timer"
|
|||||||
# Wöchentliches Auto-Update (Router/Engine/Hermes, Rollback+Pin+Telegram) — Autonomie E2.
|
# Wöchentliches Auto-Update (Router/Engine/Hermes, Rollback+Pin+Telegram) — Autonomie E2.
|
||||||
cp "$SRC/deploy/mc2-autoupdate.service" "$HOME/.config/systemd/user/mc2-autoupdate.service"
|
cp "$SRC/deploy/mc2-autoupdate.service" "$HOME/.config/systemd/user/mc2-autoupdate.service"
|
||||||
cp "$SRC/deploy/mc2-autoupdate.timer" "$HOME/.config/systemd/user/mc2-autoupdate.timer"
|
cp "$SRC/deploy/mc2-autoupdate.timer" "$HOME/.config/systemd/user/mc2-autoupdate.timer"
|
||||||
|
# Tägliche Selbst-Smoke-Tests (Gateway/Tools/Voice aktiv, Alarm bei Rot) — Autonomie 0d.
|
||||||
|
cp "$SRC/deploy/mc2-selfsmoke.service" "$HOME/.config/systemd/user/mc2-selfsmoke.service"
|
||||||
|
cp "$SRC/deploy/mc2-selfsmoke.timer" "$HOME/.config/systemd/user/mc2-selfsmoke.timer"
|
||||||
# Evolution-Radar-Feed (Autonomie E4): Hermes-cron speist ihn als Prompt ein.
|
# Evolution-Radar-Feed (Autonomie E4): Hermes-cron speist ihn als Prompt ein.
|
||||||
# Cron-Job selbst wird EINMALIG registriert (hermes cron create, siehe docs/RUNBOOK.md).
|
# Cron-Job selbst wird EINMALIG registriert (hermes cron create, siehe docs/RUNBOOK.md).
|
||||||
mkdir -p "$HOME/.hermes/scripts"
|
mkdir -p "$HOME/.hermes/scripts"
|
||||||
@@ -78,6 +81,7 @@ systemctl --user enable mem0-service >/dev/null 2>&1 || true
|
|||||||
systemctl --user enable voice-service >/dev/null 2>&1 || true
|
systemctl --user enable voice-service >/dev/null 2>&1 || true
|
||||||
systemctl --user enable --now mc2-backup.timer >/dev/null 2>&1 || true
|
systemctl --user enable --now mc2-backup.timer >/dev/null 2>&1 || true
|
||||||
systemctl --user enable --now mc2-autoupdate.timer >/dev/null 2>&1 || true
|
systemctl --user enable --now mc2-autoupdate.timer >/dev/null 2>&1 || true
|
||||||
|
systemctl --user enable --now mc2-selfsmoke.timer >/dev/null 2>&1 || true
|
||||||
loginctl enable-linger "$USER" >/dev/null 2>&1 || true
|
loginctl enable-linger "$USER" >/dev/null 2>&1 || true
|
||||||
# Mem0-Sidecar VOR dem Backend (re)starten, damit /api/memory sofort bedient wird.
|
# Mem0-Sidecar VOR dem Backend (re)starten, damit /api/memory sofort bedient wird.
|
||||||
[ -x "$HOME/.mem0/venv/bin/python" ] && systemctl --user restart mem0-service 2>/dev/null || true
|
[ -x "$HOME/.mem0/venv/bin/python" ] && systemctl --user restart mem0-service 2>/dev/null || true
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=MC2 Selbst-Smoke-Tests (Gateway/Tools/Voice aktiv durchspielen, Alarm bei Rot)
|
||||||
|
Documentation=file:%h/mission-control-v2/docs/AUTONOMIE_PLAN.md
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/bin/bash %h/mission-control-v2/deploy/self-smoke.sh
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Täglicher MC2-Selbst-Smoke-Test (07:15, unabhängig von Updates)
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* 07:15:00
|
||||||
|
Persistent=true
|
||||||
|
RandomizedDelaySec=300
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Selbstreparatur-Modus (Autonomie 0g, Kür): Macht ein Hermes-Update den Gehirn-Check rot,
|
||||||
|
# WEIL sich Config-Schlüssel geändert haben (v0.18-Klasse: „Unknown key … → stiller Default →
|
||||||
|
# alle Tools hingen"), versucht die Box den Config-Bruch SELBST zu ziehen — bevor zurückgerollt
|
||||||
|
# wird, um die neue Version zu behalten. NUR Config, NIE Code, NIE Security-Schlüssel.
|
||||||
|
#
|
||||||
|
# Sicher durch Kapselung — jeder Schritt ist reversibel und hart gegatet:
|
||||||
|
# Backup → nur EINDEUTIGE (genau 1× vorkommende), nicht-sicherheitsrelevante Keys auskommentieren
|
||||||
|
# → YAML muss gültig bleiben → Gehirn-Check (hermes-postcheck.sh) muss GRÜN werden → sonst zurück.
|
||||||
|
# Schlimmster Fall bei Fehlgriff = exakt der heutige: Rollback. Der Aufrufer (autoupdate.sh) ruft
|
||||||
|
# dies VOR seinem Rollback; bei Rückgabe 2 rollt er wie gehabt zurück + pinnt.
|
||||||
|
#
|
||||||
|
# Rückgabe: 0 = repariert (Gehirn-Check danach grün) · 2 = nichts sicher reparierbar.
|
||||||
|
# Gibt bei Misserfolg eine Zeile „SELFREPAIR_SUGGESTION=<LLM-Diagnose>" auf stdout aus.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
SRC_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
HERMES="${HERMES_HOME:-$HOME/.hermes}"
|
||||||
|
CFG="$HERMES/config.yaml"
|
||||||
|
WARN_FILE="${HERMES_CONFIG_WARNINGS:-/tmp/hermes-config-warnings.txt}"
|
||||||
|
LLAMA="${LLAMA_SWAP_URL:-http://127.0.0.1:8080}"
|
||||||
|
VENV_PY="$HERMES/hermes-agent/venv/bin/python"
|
||||||
|
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||||
|
|
||||||
|
say(){ echo "[self-repair] $*"; }
|
||||||
|
|
||||||
|
# ── 1) Evidenz: Postcheck-Warnungen + frisches Gateway-Journal ────────────────
|
||||||
|
EVID="$(cat "$WARN_FILE" 2>/dev/null)"
|
||||||
|
JOURN="$(journalctl --user -u hermes-gateway --since '-6 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 -10)"
|
||||||
|
[ -z "$EVID" ] && EVID="$JOURN"
|
||||||
|
if [ -z "$EVID" ]; then
|
||||||
|
say "Keine Config-Bruch-Hinweise gefunden — hier ist (sicher) nichts zu reparieren."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
say "Config-Hinweise:"; echo "$EVID"
|
||||||
|
|
||||||
|
# LLM-Diagnose/Empfehlung (best-effort) — für die Meldung, egal ob wir reparieren.
|
||||||
|
SUGG="$(curl -sf -m 60 "$LLAMA/v1/chat/completions" -H 'Content-Type: application/json' \
|
||||||
|
-d "$(python3 - "$EVID" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
p = ("Ein Hermes-Agent-Update hat den Selbsttest rot gemacht. Log-Hinweise:\n" + sys.argv[1] +
|
||||||
|
"\n\nNenne auf Deutsch in 1-2 kurzen Sätzen die wahrscheinliche Ursache und die konkrete "
|
||||||
|
"Config-Korrektur in ~/.hermes/config.yaml (Schlüssel → neuer Wert). Keine Einleitung.")
|
||||||
|
print(json.dumps({"model": "coder-lite", "max_tokens": 200, "temperature": 0.1,
|
||||||
|
"messages": [{"role": "user", "content": p}]}))
|
||||||
|
PY
|
||||||
|
)" 2>/dev/null | python3 -c 'import sys,json; print((json.load(sys.stdin).get("choices") or [{}])[0].get("message",{}).get("content","").strip())' 2>/dev/null)"
|
||||||
|
|
||||||
|
# ── 2) Offending Leaf-Keys ziehen ────────────────────────────────────────────
|
||||||
|
KEYS="$(echo "$EVID" | grep -oE "Unknown [a-zA-Z0-9_.]+|'[a-zA-Z0-9_.]+'" | sed -E "s/^Unknown //; s/'//g" | sort -u)"
|
||||||
|
LEAVES="$(for k in $KEYS; do echo "${k##*.}"; done | sort -u | grep -vE '^(true|false|null|)$')"
|
||||||
|
|
||||||
|
SEC_RX='token|secret|password|approval|api_server_key|ufw|sudo|allowlist|credential'
|
||||||
|
BAK="$CFG.selfrepair-bak-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
cp -a "$CFG" "$BAK"
|
||||||
|
|
||||||
|
CHANGED=""; SKIPPED=""
|
||||||
|
for leaf in $LEAVES; do
|
||||||
|
if echo "$leaf" | grep -qiE "$SEC_RX"; then SKIPPED="$SKIPPED $leaf(security-tabu)"; continue; fi
|
||||||
|
# Nur EINDEUTIGE Schlüssel-Zeilen (genau 1×) anfassen — Mehrdeutige sind zu riskant.
|
||||||
|
n="$(grep -cE "^[[:space:]]*${leaf}:" "$CFG" 2>/dev/null || echo 0)"
|
||||||
|
if [ "$n" = "1" ]; then
|
||||||
|
sed -i -E "s|^([[:space:]]*)(${leaf}:.*)$|\1# [selbstrepariert $(date +%F)] \2|" "$CFG"
|
||||||
|
CHANGED="$CHANGED $leaf"; say "auskommentiert: $leaf (unbekannt/veraltet)"
|
||||||
|
else
|
||||||
|
SKIPPED="$SKIPPED $leaf(${n}x)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ -n "$SKIPPED" ] && say "nicht automatisch angefasst:$SKIPPED"
|
||||||
|
|
||||||
|
if [ -z "$CHANGED" ]; then
|
||||||
|
say "Nichts sicher automatisch reparierbar."
|
||||||
|
cp -a "$BAK" "$CFG"
|
||||||
|
echo "SELFREPAIR_SUGGESTION=$SUGG"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 3) YAML noch gültig? ──────────────────────────────────────────────────────
|
||||||
|
if [ -x "$VENV_PY" ] && ! "$VENV_PY" -c "import yaml; yaml.safe_load(open('$CFG'))" 2>/dev/null; then
|
||||||
|
say "YAML nach Fix ungültig → zurück zum Backup."
|
||||||
|
cp -a "$BAK" "$CFG"; echo "SELFREPAIR_SUGGESTION=$SUGG"; exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 4) Neustart + Gehirn-Check ────────────────────────────────────────────────
|
||||||
|
systemctl --user restart hermes-gateway; sleep 6
|
||||||
|
if bash "$SRC_DIR/hermes-postcheck.sh" >/dev/null 2>&1; then
|
||||||
|
say "Gehirn-Check nach Selbstreparatur GRÜN."
|
||||||
|
bash "$SRC_DIR/notify.sh" -s "[Box-Update]" \
|
||||||
|
"Commander, nach dem Hermes-Update hakte der Selbsttest wegen geänderter Einstellungen — ich habe das selbst gerichtet (angepasst:${CHANGED// /, }). Der Test ist wieder grün, die neue Version läuft. Ein Rollback war nicht nötig." || true
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
say "Auch nach Selbstreparatur rot → zurück zum Backup (der Aufrufer rollt zurück)."
|
||||||
|
cp -a "$BAK" "$CFG"
|
||||||
|
systemctl --user restart hermes-gateway; sleep 6
|
||||||
|
echo "SELFREPAIR_SUGGESTION=$SUGG"
|
||||||
|
exit 2
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Tägliche Selbst-Smoke-Tests der Box (Autonomie 0d): spielt die drei Kern-Pfade AKTIV durch —
|
||||||
|
# auch OHNE Update — und meldet NUR bei Rot (kein tägliches Grün-Rauschen). Ergänzt den passiven
|
||||||
|
# Health-Wächter (sentry.py prüft nur Erreichbarkeit) um echte Funktion:
|
||||||
|
# 1) Gateway/Hirn: der Agent liefert überhaupt eine Antwort.
|
||||||
|
# 2) Tools: 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).
|
||||||
|
# 3) Voice: Lucys Sprech-Pfad (/api/voice/chat) streamt.
|
||||||
|
# Bei Fehlschlag → notify.sh (Telegram + Briefkasten) in Lucys Stimme. Läuft als User, kein sudo.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
SRC_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
MC_URL="${MC_URL:-http://127.0.0.1:9001}"
|
||||||
|
HERMES_URL="${HERMES_API_URL:-http://127.0.0.1:8642}"
|
||||||
|
HERMES="${HERMES_HOME:-$HOME/.hermes}"
|
||||||
|
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||||
|
|
||||||
|
say(){ echo "[self-smoke] $*"; }
|
||||||
|
FAILED=()
|
||||||
|
|
||||||
|
API_KEY="$(grep -E '^API_SERVER_KEY=' "$HERMES/.env" 2>/dev/null | cut -d= -f2- | tr -d '"' | tr -d "'")"
|
||||||
|
|
||||||
|
# ── 1) Gateway/Hirn: Agent antwortet ────────────────────────────────────────
|
||||||
|
if [ -n "$API_KEY" ]; then
|
||||||
|
RESP=$(curl -sf -m 90 -X POST "$HERMES_URL/v1/chat/completions" \
|
||||||
|
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
|
||||||
|
-H "X-Hermes-Session-Id: selfsmoke-gateway" \
|
||||||
|
-d '{"model":"hermes","stream":false,"chat_template_kwargs":{"enable_thinking":false},"messages":[{"role":"user","content":"Antworte NUR mit dem einen Wort: bereit"}]}' 2>/dev/null)
|
||||||
|
if echo "$RESP" | grep -qi "bereit"; then
|
||||||
|
say "PASS · Gateway/Hirn antwortet"
|
||||||
|
else
|
||||||
|
say "FAIL · Gateway/Hirn ohne verwertbare Antwort: $(echo "$RESP" | head -c 160)"
|
||||||
|
FAILED+=("Gehirn/Gateway (keine Antwort)")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
say "SKIP · Gateway (kein API_SERVER_KEY in $HERMES/.env)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 2) Tools: terminal via Agent ─────────────────────────────────────────────
|
||||||
|
if [ -n "$API_KEY" ]; then
|
||||||
|
TOOL=$(curl -sf -m 90 -X POST "$HERMES_URL/v1/chat/completions" \
|
||||||
|
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
|
||||||
|
-H "X-Hermes-Session-Id: selfsmoke-tool" \
|
||||||
|
-d '{"model":"hermes","stream":false,"messages":[{"role":"user","content":"Führe im Terminal exakt den Befehl echo selfsmoke-ok aus und gib mir nur dessen Ausgabe zurück."}]}' 2>/dev/null)
|
||||||
|
if echo "$TOOL" | grep -qi "selfsmoke-ok"; then
|
||||||
|
say "PASS · Tools (terminal via Agent)"
|
||||||
|
elif echo "$TOOL" | grep -qi "pending_approval"; then
|
||||||
|
say "FAIL · Tools hängen in pending_approval (approvals.mode prüfen!)"
|
||||||
|
FAILED+=("Tools (hängen in Freigabe)")
|
||||||
|
else
|
||||||
|
say "FAIL · Tool-Smoke ohne Ergebnis: $(echo "$TOOL" | head -c 160)"
|
||||||
|
FAILED+=("Tools (kein Ergebnis)")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 3) Voice: Lucys Sprech-Stream ────────────────────────────────────────────
|
||||||
|
# Retry: der erste Turn nach Idle zahlt evtl. den Session-Kaltstart (Prefill).
|
||||||
|
# Subshell OHNE pipefail — head -c schließt die Pipe früh (curl endet mit 23/SIGPIPE),
|
||||||
|
# das würde einen bestandenen Check sonst als FAIL werten (Lehre aus hermes-postcheck.sh).
|
||||||
|
voice_ok=0
|
||||||
|
for try in 1 2 3; do
|
||||||
|
if ( set +o pipefail; curl -sf -m 45 -N -X POST "$MC_URL/api/voice/chat" -H "Content-Type: application/json" \
|
||||||
|
-d '{"text":"Sag nur ok.","session_id":"selfsmoke-voice"}' 2>/dev/null | head -c 50 | grep -q "data:" ); then
|
||||||
|
voice_ok=1; break
|
||||||
|
fi
|
||||||
|
sleep 8
|
||||||
|
done
|
||||||
|
if [ "$voice_ok" -eq 1 ]; then
|
||||||
|
say "PASS · Voice (/api/voice/chat streamt, Versuch $try)"
|
||||||
|
else
|
||||||
|
say "FAIL · Voice: /api/voice/chat streamt nicht (3 Versuche)"
|
||||||
|
FAILED+=("Voice (Lucys Sprech-Pfad)")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Meldung nur bei Rot ──────────────────────────────────────────────────────
|
||||||
|
if [ "${#FAILED[@]}" -eq 0 ]; then
|
||||||
|
say "Alle Selbst-Tests grün ($(date '+%F %H:%M'))."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
MSG="Commander, mein täglicher Selbst-Test hat etwas gefunden — das läuft gerade NICHT rund:
|
||||||
|
$(printf '• %s\n' "${FAILED[@]}")
|
||||||
|
Der Rest der Box kann trotzdem stehen; magst du kurz draufschauen?"
|
||||||
|
bash "$SRC_DIR/notify.sh" -s "[Selbst-Test]" "$MSG" || true
|
||||||
|
say "FEHLGESCHLAGEN: ${FAILED[*]}"
|
||||||
|
exit 1
|
||||||
Reference in New Issue
Block a user