#!/usr/bin/env bash # news-melden.sh — schickt den fertigen Nachrichtenbericht als TEXT und als # SPRACHNACHRICHT an den Commander (21.08.2026, überarbeitet 22.08.). # # Der Agent ruft genau EINEN Befehl auf. Alles danach ist deterministisch — # Zustellung darf nicht davon abhaengen, ob ein Modell die Reihenfolge einhaelt. # # ‼️ WARUM DAS SKRIPT SOFORT ZURUECKKEHRT (Fund vom 22.08., erster echter Lauf): # Hermes' terminal-Werkzeug bricht nach 30 s ab. Die Sprachsynthese dauert # laenger. Der Agent sah den Timeout, hielt den Aufruf fuer gescheitert und # rief das Skript ein zweites Mal auf — der Text ging DOPPELT raus # (07:01:42 und 07:02:14). # Deshalb: Text senden, Stimme abgekoppelt im Hintergrund, sofort raus. # Dazu eine Doppelversand-Sperre, die auch kuenftige Wiederholungen abfaengt. # # Erwartet zwei Dateien, die der Agent vorher geschrieben hat: # $1 Textfassung (mit Emojis, Links, Formatierung — geht nach Telegram) # $2 Sprechfassung (kurz, ohne Links/Emojis — wird vorgelesen) # # ‼️ STIMME: :8021, nicht :8650. # :8021 = lucy-stimme.service — Kyutai pocket-tts, Modell `german_24l`, # Stimme geklont aus ref.mp3. DAS ist Lucy. Lokal, auf der CPU. # :8650 = MC2s voice-service. Dort sind nur `elevenlabs` und `edge` geladen — # beides CLOUD, und die Vorgabe ist die ElevenLabs-Stimme # "Artoria/Saber", also HERMES' Stimme, nicht Lucys. set -uo pipefail NOTIFY="${NOTIFY:-$HOME/mission-control-v2/deploy/notify.sh}" VOICE="${VOICE_URL:-http://127.0.0.1:8021}" HERMES="${HERMES_BIN:-$HOME/.local/bin/hermes}" TON="/tmp/news-stimme.wav" TON_OGG="/tmp/news-stimme.ogg" # ---------------------------------------------- Zweiter Durchgang: Stimme --- # Wird vom ersten Durchgang abgekoppelt neu gestartet. Laeuft beliebig lange, # ohne dass irgendein Werkzeug-Timeout hineinredet. if [ "${1:-}" = "--nur-stimme" ]; then SPRECHDATEI="${2:-/tmp/news-sprich.txt}" [ -s "$SPRECHDATEI" ] || exit 0 CODE=$(curl -s -m 600 -X POST "$VOICE/tts" \ -H 'Content-Type: application/json' \ --data "$(python3 -c 'import json,sys; print(json.dumps({"text": open(sys.argv[1], encoding="utf-8").read()[:1500]}))' "$SPRECHDATEI")" \ -o "$TON" -w '%{http_code}' 2>/dev/null) [ "$CODE" = "200" ] && [ -s "$TON" ] || exit 0 # Telegram zeigt eine ECHTE Sprachnachricht (runde Blase, Wellenform) nur bei # OGG/Opus. Eine WAV kaeme als Dateianhang an. Mono/48k ist das erwartete Format. if ! ffmpeg -y -loglevel error -i "$TON" -c:a libopus -b:a 32k -ar 48000 -ac 1 "$TON_OGG" 2>/dev/null; then TON_OGG="$TON" fi "$HERMES" send --to telegram "MEDIA:$TON_OGG" >/dev/null 2>&1 exit 0 fi # ------------------------------------------- Erster Durchgang: Text + Start --- TEXTDATEI="${1:-/tmp/news-text.md}" SPRECHDATEI="${2:-/tmp/news-sprich.txt}" if [ ! -s "$TEXTDATEI" ]; then bash "$NOTIFY" -s "[News]" "Der Nachrichtenbericht ist leer geblieben ($TEXTDATEI). Da lief etwas schief." exit 1 fi # Doppelversand-Sperre. Greift, egal WARUM ein zweiter Aufruf kommt — # Timeout, Modell-Wiederholung, versehentlicher Handstart. MARKE="/tmp/.news-gesendet-$(sha256sum "$TEXTDATEI" | cut -c1-16)" if [ -f "$MARKE" ]; then ALTER=$(( $(date +%s) - $(stat -c %Y "$MARKE" 2>/dev/null || echo 0) )) if [ "$ALTER" -lt 7200 ]; then echo "gemeldet (dieser Bericht ging vor ${ALTER}s bereits raus — nicht doppelt geschickt)" exit 0 fi fi : > "$MARKE" find /tmp -maxdepth 1 -name '.news-gesendet-*' -mtime +2 -delete 2>/dev/null bash "$NOTIFY" -s "" "$(cat "$TEXTDATEI")" # Stimme abkoppeln: eigene Sitzung, kein Elternprozess, keine offenen Deskriptoren. # Damit ueberlebt sie das Ende dieses Skripts und blockiert es nicht. if [ -s "$SPRECHDATEI" ]; then setsid nohup bash "$0" --nur-stimme "$SPRECHDATEI" >/dev/null 2>&1 < /dev/null & disown 2>/dev/null || true fi echo "gemeldet" exit 0