From c31ef827fcfd4e3cbfaf0a2604facb20866027af Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Fri, 25 Sep 2026 13:07:42 +0200 Subject: [PATCH] =?UTF-8?q?meldungen:=20Lucy=20spricht=20=E2=80=93=20jede?= =?UTF-8?q?=20Telegram-Meldung=20in=20ihrem=20Ton?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User 25.09.: „Hermes' Persona ist Lucy – das muss sich in all ihren Meldungen widerspiegeln“; das angehängte „(Diese Meldung kam auch an Lucy.)“ tat so, als wäre sie jemand anderes als der Absender. - notify.sh: Kopfzeile aus dem Betreff in Lucys Ton (SOUL.md: „Commander“, knapp, sachlich), bei Dringendem „Commander, das ist dringend.“; keine zweite Anrede, wenn der Text schon mit ihr beginnt. Morgenmeldung mit kurzen Marken statt Betreffen in Klammern; die des Homelabs sagt, woher sie kommt. - Wächter: Zusatz „kam auch an Lucy“ entfernt (der Briefkasten bekommt die Meldung weiter). - Melde-Log und Auswertungen unverändert. Co-Authored-By: Claude Opus 5.5 --- backend/services/waechter.py | 4 +- backend/tests/test_einstellungen.py | 7 +++- backend/tests/test_nachtmeldung.py | 45 +++++++++++++++++++--- deploy/morgenmeldung.sh | 7 +++- deploy/notify.sh | 59 ++++++++++++++++++++++++++--- docs/BETRIEB.md | 10 +++++ 6 files changed, 118 insertions(+), 14 deletions(-) diff --git a/backend/services/waechter.py b/backend/services/waechter.py index 5d6177b..0e80e20 100644 --- a/backend/services/waechter.py +++ b/backend/services/waechter.py @@ -647,7 +647,9 @@ def _telegram(betreff: str, text: str) -> None: log.info("waechter (trocken): würde melden %s %s", betreff, text) return announce.add(text, subject=betreff, source="waechter") - announce.notify_telegram(betreff, text + " (Diese Meldung kam auch an Lucy.)") + # Bis 25.09.2026 hing hier „(Diese Meldung kam auch an Lucy.)“ an — als wäre Lucy jemand anderes als der Absender. + # Sie IST der Absender (ihr Bot, ihre Stimme, siehe notify.sh); im Briefkasten oben liegt die Meldung trotzdem. + announce.notify_telegram(betreff, text) def _auto_erlaubt(schluessel: str, jetzt: float) -> bool: diff --git a/backend/tests/test_einstellungen.py b/backend/tests/test_einstellungen.py index efa218e..f7f34b1 100644 --- a/backend/tests/test_einstellungen.py +++ b/backend/tests/test_einstellungen.py @@ -420,7 +420,9 @@ def test_telegram_test_ueber_hermes_auch_nachts(melden): ergebnis = einstellungen.telegram_test() assert ergebnis["ok"] and ergebnis["gesendet"] and ergebnis["weg"] == "hermes", ergebnis gesendet = protokoll.read_text(encoding="utf-8") - assert "send --to telegram --subject [Test]" in gesendet and f"Kennung {ergebnis['kennung']}" in gesendet + # Lucys Kopfzeile statt „[Test]“ (seit 25.09.2026, notify.sh). + assert "send --to telegram --subject Commander, ein Test des Meldewegs." in gesendet + assert f"Kennung {ergebnis['kennung']}" in gesendet assert not (tmp / "night-queue.txt").exists() assert "OK telegram: Testmeldung aus den Einstellungen" in (tmp / "notify.log").read_text(encoding="utf-8") @@ -436,7 +438,8 @@ def test_telegram_test_ueber_den_zweitweg(melden, bot_api, monkeypatch): monkeypatch.setenv("STUB_EXIT", "1") # wie im Homelab-Teil: kein Hermes ergebnis = einstellungen.telegram_test() assert ergebnis["gesendet"] and ergebnis["weg"] == "direkt", ergebnis - assert len(empfangen) == 1 and empfangen[0]["text"].startswith("[Test]\nTestmeldung aus den Einstellungen") + assert len(empfangen) == 1 + assert empfangen[0]["text"].startswith("Commander, ein Test des Meldewegs.\nTestmeldung aus den Einstellungen") @braucht_bash diff --git a/backend/tests/test_nachtmeldung.py b/backend/tests/test_nachtmeldung.py index d111681..d8eec9f 100644 --- a/backend/tests/test_nachtmeldung.py +++ b/backend/tests/test_nachtmeldung.py @@ -61,7 +61,8 @@ def test_nachts_wird_gesammelt(umgebung): tmp, env, protokoll = umgebung assert _notify(env, "-s", "[Box-Update]", "Engine aktualisiert").returncode == 0 assert _gesendet(protokoll) == "" - assert (tmp / "night-queue.txt").read_text(encoding="utf-8") == "- [Box-Update]: Engine aktualisiert\n" + # Die Zeile der Morgenmeldung trägt Lucys kurze Marke statt des Betreffs in Klammern (25.09.2026). + assert (tmp / "night-queue.txt").read_text(encoding="utf-8") == "- Box, Wartung: Engine aktualisiert\n" assert "QUEUED für Morgen-Digest: Engine aktualisiert" in (tmp / "notify.log").read_text(encoding="utf-8") @@ -97,8 +98,9 @@ def test_morgenmeldung_schickt_alles_als_eine_nachricht(umgebung): assert _morgenmeldung(env).returncode == 0 gesendet = _gesendet(protokoll) assert gesendet.count("send --to telegram") == 1 - assert "[Morgenmeldung]" in gesendet and "2 Meldungen" in gesendet - assert "Router aktualisiert" in gesendet and "Kandidat bestanden" in gesendet + assert "Guten Morgen, Commander. Heute Nacht gab es 2 Meldungen:" in gesendet + assert "--subject" not in gesendet # die Anrede steht schon im Text, keine zweite Kopfzeile + assert "Box, Wartung: Router aktualisiert" in gesendet and "Modell-Radar: Kandidat bestanden" in gesendet assert not (tmp / "night-queue.txt").exists() assert (tmp / "night-queue.txt.zuletzt-gesendet").exists() @@ -170,7 +172,7 @@ def test_zweitweg_springt_ein_wenn_hermes_scheitert(umgebung, bot_api): lauf = _notify(env, "-s", "[Alarm]", "Hermes steht", stunde="12") assert lauf.returncode == 0 assert empfangen == [{"pfad": "/bot123:abc/sendMessage", "chat_id": "4711", - "text": "[Alarm]\nHermes steht"}] + "text": "Commander, das ist dringend.\nHermes steht"}] assert "OK telegram direkt: Hermes steht" in (tmp / "notify.log").read_text(encoding="utf-8") @@ -180,7 +182,8 @@ def test_zweitweg_nimmt_den_thread_mit(umgebung, bot_api): env = {**env, "STUB_EXIT": "1", "MC_TELEGRAM_API": url, "MC_TELEGRAM_ENV": _env_datei( tmp, "TELEGRAM_BOT_TOKEN='9:x'\r\nTELEGRAM_HOME_CHANNEL=-100\r\nTELEGRAM_HOME_CHANNEL_THREAD_ID=7\r\n")} assert _notify(env, "Ohne Betreff", stunde="12").returncode == 0 - assert empfangen == [{"pfad": "/bot9:x/sendMessage", "chat_id": "-100", "text": "Ohne Betreff", + assert empfangen == [{"pfad": "/bot9:x/sendMessage", "chat_id": "-100", + "text": "Commander, kurz gesagt:\nOhne Betreff", "message_thread_id": "7"}] @@ -208,3 +211,35 @@ def test_letzter_ausweg_wenn_auch_der_zweitweg_scheitert(umgebung, bot_api, inha lauf = _notify(env, "-s", "[Test]", "Geht nirgends hin", stunde="12") assert lauf.returncode == 1 assert "FALLBACK (telegram fehlgeschlagen" in (tmp / "notify.log").read_text(encoding="utf-8") + + + +@pytest.mark.parametrize("args, kopf", [ + (("-s", "[Homelab-Problem]", "Platte von AdGuard Home zu 94 % voll."), "Commander, im Homelab stimmt etwas nicht."), + (("-s", "[Homelab wieder ok]", "Erledigt: Gitea antwortet nicht."), "Commander, Entwarnung im Homelab."), + (("-s", "[Homelab-Update]", "Gitea: eingespielt, Prüfung grün."), "Commander, Bericht aus dem Homelab."), + (("-s", "[Box-Problem]", "llama-swap steht."), "Commander, auf der Box stimmt etwas nicht."), + (("-s", "[Alarm] Homelab-Update", "Gitea: Update gescheitert."), "Commander, das ist dringend."), + (("-d", "-s", "[Test]", "Der Meldeweg geht."), "Commander, ein Test des Meldewegs."), + (("-s", "[Modell-Radar]", "Kandidat bestanden."), "Commander, Bericht vom Modell-Radar."), +]) +def test_lucy_spricht(umgebung, args, kopf): + """25.09.2026: Hermes' Persona ist Lucy — jede Meldung kommt von ihrem Bot, also in ihrem Ton (SOUL.md).""" + _, env, protokoll = umgebung + assert _notify(env, *args, stunde="12").returncode == 0 + assert f"--subject {kopf} -- {args[-1]}" in _gesendet(protokoll) + + +def test_lucy_formuliert_selbst_keine_zweite_anrede(umgebung): + _, env, protokoll = umgebung + assert _notify(env, "-s", "[Box-Update]", "Commander, die Wochenpflege der Box ist durch.", stunde="12").returncode == 0 + assert _gesendet(protokoll) == "send --to telegram -- Commander, die Wochenpflege der Box ist durch.\n" + + +def test_morgenmeldung_des_homelabs_sagt_woher(umgebung): + _, env, protokoll = umgebung + _notify(env, "-s", "[Homelab-Problem]", "NetBird antwortet nicht.") + assert _morgenmeldung(env, MC_MORGENMELDUNG_BETREFF="[Morgenmeldung Homelab]").returncode == 0 + gesendet = _gesendet(protokoll) + assert "Guten Morgen, Commander. Aus dem Homelab kamen heute Nacht 1 Meldung:" in gesendet + assert "Homelab, Störung: NetBird antwortet nicht." in gesendet diff --git a/deploy/morgenmeldung.sh b/deploy/morgenmeldung.sh index bb0186f..1c36209 100644 --- a/deploy/morgenmeldung.sh +++ b/deploy/morgenmeldung.sh @@ -39,7 +39,12 @@ if [ "$(wc -c < "$ARBEIT")" -gt "$MAX_ZEICHEN" ]; then TEXT="$TEXT … gekürzt. Den vollen Stapel hat die Box unter ~/.hermes/night-queue.txt.zuletzt-gesendet." fi -NACHRICHT="Guten Morgen, Commander. Heute Nacht gab es $ANZAHL $WORT: +# Lucys Ton (SOUL.md): kurz, sachlich. Kommen zwei Morgenmeldungen (KI-Box und Homelab), sagt die zweite, woher. +case "$BETREFF" in + *Homelab*) NACHRICHT="Guten Morgen, Commander. Aus dem Homelab kamen heute Nacht $ANZAHL $WORT:" ;; + *) NACHRICHT="Guten Morgen, Commander. Heute Nacht gab es $ANZAHL $WORT:" ;; +esac +NACHRICHT="$NACHRICHT $TEXT" for ((i = 1; i <= VERSUCHE; i++)); do diff --git a/deploy/notify.sh b/deploy/notify.sh index 9822c96..ba51e5e 100644 --- a/deploy/notify.sh +++ b/deploy/notify.sh @@ -69,20 +69,69 @@ case "$MSG" in KRITISCH*) DRINGEND=1 ;; esac +# Lucys Stimme (User-Wunsch 25.09.2026: „Hermes' Persona ist Lucy — das muss sich in all ihren Meldungen +# widerspiegeln“). Was über Telegram rausgeht, kommt von ihrem Bot, also spricht sie: Anrede „Commander“, knapp, +# sachlich, ohne Emojis und Ausrufe (~/.hermes/SOUL.md). Die Kopfzeile folgt aus dem Betreff; beginnt die Nachricht +# schon mit ihrer Anrede (Morgenmeldung, von Lucy selbst formulierte Berichte), bleibt sie weg. Melde-Log und +# Auswertungen (update_verlauf.py, protokoll.py) behalten Betreff und Wortlaut wie bisher. +lucy_kopf() { + case "$MSG" in + Commander*|"Guten Morgen, Commander"*) return 0 ;; + esac + case "${SUBJECT,,}" in + *test*) echo "Commander, ein Test des Meldewegs." ; return 0 ;; + *morgenmeldung*) echo "Guten Morgen, Commander." ; return 0 ;; + esac + if [ "$DRINGEND" = "1" ]; then + echo "Commander, das ist dringend." + return 0 + fi + case "${SUBJECT,,}" in + *homelab-problem*) echo "Commander, im Homelab stimmt etwas nicht." ;; + *"homelab wieder ok"*) echo "Commander, Entwarnung im Homelab." ;; + *homelab-update*) echo "Commander, Bericht aus dem Homelab." ;; + *box-problem*) echo "Commander, auf der Box stimmt etwas nicht." ;; + *"box wieder ok"*) echo "Commander, Entwarnung auf der Box." ;; + *update*) echo "Commander, Bericht von der Wartung." ;; + *deploy*) echo "Commander, Bericht vom Deploy." ;; + *erinnerung*) echo "Commander, eine Erinnerung." ;; + *radar*) echo "Commander, Bericht vom Modell-Radar." ;; + "") echo "Commander, kurz gesagt:" ;; + *) echo "Commander, $(printf '%s' "$SUBJECT" | tr -d '[]')." ;; + esac +} + +# Dasselbe als kurze Marke für eine Zeile der Morgenmeldung („Homelab, Störung: …“ statt „[Homelab-Problem]: …“). +lucy_marke() { + case "${SUBJECT,,}" in + "") return 0 ;; + *alarm*|*notfall*) echo "Dringend" ;; + *homelab-problem*) echo "Homelab, Störung" ;; + *"homelab wieder ok"*) echo "Homelab, Entwarnung" ;; + *homelab-update*) echo "Homelab, Update" ;; + *box-problem*) echo "Box, Störung" ;; + *"box wieder ok"*) echo "Box, Entwarnung" ;; + *box-update*) echo "Box, Wartung" ;; + *) printf '%s' "$SUBJECT" | tr -d '[]' ;; + esac +} +KOPF="$(lucy_kopf)" + # Nachtruhe: 00:00–06:59 sammeln, außer es ist dringend. 10#… verhindert, dass „08“/„09“ als # Oktalzahl gelesen werden. HOUR="${MC_NOTIFY_STUNDE:-$(date +%H)}" if [ "$DRINGEND" != "1" ] && [ "$((10#$HOUR))" -lt 7 ]; then mkdir -p "$(dirname "$NIGHT_QUEUE")" - printf -- '- %s%s\n' "${SUBJECT:+$SUBJECT: }" "$MSG" >> "$NIGHT_QUEUE" + MARKE="$(lucy_marke)" + printf -- '- %s%s\n' "${MARKE:+$MARKE: }" "$MSG" >> "$NIGHT_QUEUE" # Wortlaut „QUEUED für Morgen-Digest“ nicht ändern: services/update_verlauf.py liest ihn. echo "$TS QUEUED für Morgen-Digest: $MSG" >> "$LOG" exit 0 fi -# Primärweg: Telegram via hermes send (Login-Shell-PATH, falls aus Timer/cron aufgerufen). -if [ -n "$SUBJECT" ]; then - SEND_OUT="$(bash -lc '"$1" send --to telegram --subject "$2" -- "$3"' _ "$HERMES_BIN" "$SUBJECT" "$MSG" 2>&1)" +# Primärweg: Telegram via hermes send (Login-Shell-PATH, falls aus Timer/cron aufgerufen). Die Kopfzeile ist Lucys. +if [ -n "$KOPF" ]; then + SEND_OUT="$(bash -lc '"$1" send --to telegram --subject "$2" -- "$3"' _ "$HERMES_BIN" "$KOPF" "$MSG" 2>&1)" else SEND_OUT="$(bash -lc '"$1" send --to telegram -- "$2"' _ "$HERMES_BIN" "$MSG" 2>&1)" fi @@ -119,7 +168,7 @@ telegram_direkt() { return 1 fi text="$MSG" - [ -n "$SUBJECT" ] && text="$SUBJECT + [ -n "$KOPF" ] && text="$KOPF $MSG" printf 'url = "%s/bot%s/sendMessage"\n' "$TG_API" "$token" \ | curl -sf -m 15 -o /dev/null --config - \ diff --git a/docs/BETRIEB.md b/docs/BETRIEB.md index f61ae3f..a7d702c 100644 --- a/docs/BETRIEB.md +++ b/docs/BETRIEB.md @@ -52,6 +52,16 @@ sudo journalctl -u llama-swap -n 100 # Motor (System-Dienst) `bash ~/mission-control-v2/deploy/notify.sh -s "[Test]" "Testmeldung"`; nachts landet sie in der Warteschlange, mit `-d` kommt sie sofort. +**Lucys Stimme (seit 25.09.2026, User-Wunsch: „Hermes' Persona ist Lucy“).** Was über Telegram rausgeht, kommt von +Lucys Bot und spricht in ihrem Ton (`~/.hermes/SOUL.md`: Anrede „Commander“, knapp, sachlich, keine Emojis). +`notify.sh` macht aus dem Betreff ihre Kopfzeile (`lucy_kopf`): „Commander, im Homelab stimmt etwas nicht.“, +„Commander, Entwarnung auf der Box.“, „Commander, Bericht aus dem Homelab.“, bei Dringendem „Commander, das ist +dringend.“, ohne Betreff „Commander, kurz gesagt:“. Beginnt der Text schon mit ihrer Anrede (Morgenmeldung, von Lucy +selbst formulierte Berichte), kommt keine zweite dazu. In der Morgenmeldung stehen kurze Marken („Homelab, Störung: +…“) statt der Betreffe in Klammern. Melde-Log, Update-Verlauf und Protokoll behalten Betreff und Wortlaut wie in der +Tabelle — die Betreffe unten sind also die internen Kennungen. Das frühere „(Diese Meldung kam auch an Lucy.)“ am +Ende der Wächter-Meldungen ist weg; in ihrem Briefkasten liegt jede Meldung trotzdem. + | Betreff | Absender | Inhalt | |---|---|---| | `[Box-Update]` | `autoupdate.sh`, `jobs/sonntags-update.sh` | Ergebnis je Baustein, Wochenbericht, Neustart-Ankündigung; beginnt der Text mit „KRITISCH", ist es dringend |