1028 Commits
Author SHA1 Message Date
duffyduck 75fa574a85 release: bump version to 0.2.4.8 v0.2.4.8 2026-08-16 22:01:07 +02:00
duffyduckandClaude Opus 4.8 a3d7933949 fix(voxtral): Speech-Endpoint gegen laute Umgebungsmusik
Problem (Stefan): laeuft Musik, endet eine Aufnahme nie von selbst — die Energie
bleibt oben, der RMS-Stille-Endpoint feuert nicht (last_voice_at immer frisch).
Erst Stop druecken oder Musik leiser (dann echte Stille) beendet.

Fix: waehrend lauter Phasen (rms ueber Schwelle) periodisch (~700ms, gedrosselt)
mit Silero pruefen, ob im letzten endpoint_ms-Fenster ueberhaupt Sprache ist.
Nur Musik/Stille → Turn beenden (finalize endpoint). Real gesprochene Turns
haben Sprache im Fenster → laufen normal weiter; eine kurze Denk-Pause auch,
solange innerhalb der Toleranz noch Sprache im Fenster liegt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 21:57:06 +02:00
duffyduck 626281dead release: bump version to 0.2.4.7 v0.2.4.7 2026-08-16 21:39:35 +02:00
duffyduckandClaude Opus 4.8 49ea172617 feat(wake): Voxtral-Bestätigungsstufe gegen Musik-Fehltrigger
openWakeWord triggert oft auf Musik (Pet Shop Boys "West End Girls" & Co.). Neu:
nach dem Trigger prüft Voxtral den Vor-Trigger-Audio ("war das wirklich das
Wake-Wort oder nur Musik?") — erst bei Bestätigung Gong + Mikro, sonst still
verworfen + re-arm. Kostet ~0,5-1s vor dem Gong.

- Native (OpenWakeWordModule.kt): 2s Roh-PCM-Ringpuffer; bei Erkennung wird der
  1,5s-Vor-Trigger-Schnipsel base64 (s16le 16kHz) ans WakeWordDetected-Event
  gehängt (preTriggerPcm).
- Bridge (voxtral): neuer One-Shot-Handler stt_transcribe_blob → Silero + Voxtral
  → stt_transcribe_result. Musik/Rauschen → leerer Text.
- App: audio.transcribeBlob() schickt den Schnipsel, wakeword.confirmWake() prüft
  ob das distinktive Keyword (>=4 Zeichen) im Transkript steht. Gate sitzt in
  onWakeDetected VOR Gong/Dialog. Setting "Wake-Wort per Voxtral bestätigen"
  (default aus, opt-in).

FAIL-OPEN durchgängig: kein preTriggerPcm (altes APK) / Timeout / Fehler / VAD
weg → Wake läuft normal durch. Nie schlechter als heute. NATIVER TEIL UNGETESTET
(kein Gerät hier) — braucht Build + Test auf dem Handy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 21:37:15 +02:00
duffyduck a3650bb0e4 release: bump version to 0.2.4.6 v0.2.4.6 2026-08-16 21:17:07 +02:00
duffyduckandClaude Opus 4.8 514ba44e51 fix(app): Umgebungsmusik killt Weiterreden-Fenster nicht mehr (re-listen)
Wechselwirkung entdeckt: im Passiv-Lausch-Fenster (Weiterreden nach ARIAs
Antwort) nimmt das Mikro Umgebungsmusik auf → Silero verwirft sie korrekt als
vad_no_speech → leerer Endpoint. Die App wertete JEDEN leeren Endpoint als
"nichts gesagt" → sofort zurueck aufs Wake-Word. Bei laufender Musik in einer
Sprechpause endete so das Gespraech, obwohl die Stille-Toleranz noch lief.

Fix: bei leerem Endpoint im Passiv-Fenster wird — solange die Stille-Toleranz ab
Fenster-Oeffnung noch laeuft (Budget) und < 15 Runden — nochmal gelauscht statt
zu beenden. Musik wird verworfen, das Fenster bleibt bis zur Toleranz offen, du
kannst innerhalb reden. Erst wenn die Toleranz wirklich um ist → aufs Wake-Word.
Sprichst du (Text-Endpoint), geht es sofort normal weiter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 21:14:37 +02:00
duffyduckandClaude Opus 4.8 617dddf3d8 fix(app): Konversationsmodus auch im Hintergrund, wenn Background-Wake an
Diagnose aus dem App-State-Trail (Bridge-Log): nach jeder gesprochenen Antwort
rief die App endConversation(bg || !converse) — der bg-Term (AppState != active)
erzwang "zurueck aufs Wake-Word", sobald die App im Hintergrund war. Das war vor
dem Background-Wake-Feature Absicht (kein Multi-Turn im Hintergrund). Jetzt, mit
aktivem Background-Wake, killt genau das den Konversationsmodus im Hintergrund —
im Vordergrund lief er weiter (daher "kommt beim Vorholen wieder"). converse kam
korrekt als true vom Brain (keine Marker, Default true); die App warf es im
Hintergrund weg.

Fix: bg erzwingt "armed" nur noch, wenn Background-Wake AUS ist
(isBgWakeEnabled()). Bei aktivem Background-Wake bleibt der Konversationsmodus
auch im Hintergrund offen — der User hat das Zuhoeren ja bewusst eingeschaltet.
Vordergrund-Verhalten unveraendert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 21:06:40 +02:00
duffyduck 88faf57253 release: bump version to 0.2.4.5 v0.2.4.5 2026-08-16 20:40:47 +02:00
duffyduckandClaude Opus 4.8 41714f7cf1 feat(voice): Wake-Word per Befehl wieder anschalten (Gegenstück zu wake_off)
"Wake-Word an" / "mach das Ohr an" / "hör wieder zu" / "wach auf" → App startet
den Listener wieder. Geht per Text-Nachricht ODER manuellem Aufnahme-Button —
beide laufen unabhängig vom Wake-Word-Listener, also funktioniert das Wieder-An
auch wenn das Ohr gerade taub ist (nur nicht per "Computer", das hört ja nicht).

Symmetrisch zu wake_off: Detektor _user_wants_wake_on → wake_on durch ChatOut →
Bridge → App löst wakeWordService.start() + setWakeWordActive(true) aus. An/Aus
kollidieren nicht (12 Fälle getestet). Damit: Ohr per Befehl ein UND aus, plus
weiterhin der Ohr-Button.

Fix nebenbei: gerades " in den Reply-Strings (hätte den Brain-Start gecrasht) →
einfache Anführungszeichen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 20:33:16 +02:00
duffyduckandClaude Opus 4.8 93401d42d1 feat(voice): Wake-Word per Sprachbefehl ausschalten
"Computer, Wake-Word aus" / "mach das Ohr aus" / "hör auf zuzuhören" / "geh
schlafen" → die App stoppt den Wake-Word-Listener KOMPLETT (Mikro frei, echte
Ruhe). Wieder-An nur über den Ohr-Button (bewusst, weil dann taub — Voice-
Wieder-An ist physikalisch unmöglich, wenn nichts mehr hört).

Deterministischer Detektor _user_wants_wake_off im Brain (kein LLM-Call nötig,
still). Signal fließt als wake_off durch ChatOut → Bridge → App-Payload; die App
löst denselben Pfad wie toggleWakeWord-off aus (cancelRecording +
wakeWordService.stop() + setWakeWordActive(false)). Detektor gegen 13 Positiv-
+ 9 Negativ-Fällen verifiziert (fängt nicht 'Licht aus' / 'Konversation Ende').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 20:27:48 +02:00
duffyduckandClaude Opus 4.8 c4e658446d feat(voxtral): Silero VAD — echte Sprach-Erkennung gegen generische Phantome + Musik
Der Muster-Filter fängt nur spezifische Artefakte; generische Silence-Phantome
("Ich bin ein guter Mann", "Die Serie wurde in 13 Episoden gedreht") kann ein
Text-Regex prinzipiell nicht vorhersehen. Lösung an der Wurzel: Silero VAD
(neuronal, ~2MB) entscheidet am AUDIO, ob überhaupt echte Sprache drin ist.

Im _finalize vor dem Transkribieren: kein Speech-Segment → no-speech, gar nicht
erst transkribieren → kein Phantom. Trennt zuverlässig Sprache von Stille /
Rauschen / MUSIK (löst damit auch den Musik-False-Positive). Bei Speech wird auf
die Sprach-Spanne getrimmt (Stille-Ränder weg → weniger Edge-Halluzination).

FAIL-OPEN: Import/Load/Inferenz-Fehler → normal transkribieren (die STT darf nie
komplett sterben — Speaker-ID-Lektion). Alles env-tunebar: SILERO_VAD_ENABLED,
_THRESHOLD (0.5), _MIN_SPEECH_MS (150), _PAD_MS (200). Modell im pip-Paket
gebündelt, kein Runtime-Download.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 20:18:06 +02:00
duffyduck 3693157210 release: bump version to 0.2.4.4 v0.2.4.4 2026-08-16 19:57:14 +02:00
duffyduckandClaude Opus 4.8 64670cdd12 fix(voxtral): Repetition-Loop bremsen ('vergiss das'-Schleife)
Stefans Repro: eine echte Nachricht endete mit "...vergiss das, das ist nur..."
und Voxtral hat den Satz ~15x geloopt, bis zur Brain durch. Klassische
Repetition-Halluzination bei Stille/Rauschen am Ende.

Zwei Ebenen: (1) Generation bekommt no_repeat_ngram_size=4 + repetition_penalty
=1.15 → erste echte Nennung bleibt, exakte 4-Gramm-Wiederholung verboten, Loop
bricht an der Quelle ab. (2) Post-Detektor _collapse_repetitions kassiert einen
durchgerutschten Loop auf eine Kopie ein. Gegen den echten Loop + legitime
Doppelungen ('ja ja ja', 'sehr sehr') verifiziert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 19:42:22 +02:00
duffyduckandClaude Opus 4.8 c82616ebbd fix(voxtral): No-Speech-Timeout — Stille-Fenster schließt selbst
Stefans Repro: "die Stille-Ende wird nie erreicht, stop ich selbst ist es weg,
und geht automatisch auf lausche Computer". Ursache: der Endpoint feuert nur
wenn schon Stimme da war (last_voice_at>0). Bei totaler Stille bleibt
last_voice_at==0 → Endpoint feuert NIE → Fenster offen bis Hardcap/manuellem
Stop (→ stream_end → Phantom).

Fix: No-Speech-Timeout im _tick — wenn nach endpoint_ms (Stille-Toleranz) ab
Start noch KEINE Stimme kam, schließt der Bridge das Fenster selbst als
no-speech (leer, lautlos, zurück aufs Wake-Word). voiced_frames==0 →
_finalize verwirft ohne Transkript, also kein Phantom. Logik gegen totale
Stille / Sprache-dann-still / Dauer-Sprache verifiziert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 19:38:38 +02:00
duffyduck b226e1da11 release: bump version to 0.2.4.3 v0.2.4.3 2026-08-16 11:33:45 +02:00
duffyduckandClaude Opus 4.8 0b7ed241b4 fix(voxtral): Phantom-Filter greift auch bei stream_end
Aus dem ai-box-Log gelernt: die realen Silence-Phantome ('Die Stadt hat eine
Fläche von 1,5 km²') kommen ALLE mit reason=stream_end — Passiv-/Wake-Fenster
enden auch per stream_end, wenn sie auf Stille zumachen. stream_end ist also
NICHT gleich 'manueller Stop mit bewusster Sprache'. Meine vorige Fassung nahm
stream_end aus → Phantome liefen durch.

Jetzt: (1) Pre-Guard greift auch bei stream_end, aber mit Schwelle voiced==0
(kurze bewusste Wörter am Button gehen durch, echte Stille nicht). (2) Phrase-
Filter gilt für ALLE reasons; das borderline-Band (wenig voiced_frames) schützt
echte, klar gesprochene Geo-Fragen. Gegen alle 3 Log-Fälle + reale Eingaben
verifiziert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 11:30:20 +02:00
duffyduck a2b7e3a48d release: bump version to 0.2.4.2 v0.2.4.2 2026-08-16 11:26:49 +02:00
duffyduckandClaude Opus 4.8 7b72149671 fix(voxtral): Halluzinations-Filter gegen Phantom-Text aus Stille
Punkt 3: 2. Netz nach der Transkription. Im borderline-Band (wenig echte
Stimme) werden leere/Artefakt-Transkripte verworfen statt als Phantom ans
Brain zu gehen ('Die Stadt hat eine Fläche von 1,5 km²' aus Fast-Stille).
Bekannte Voxtral-Silence-Artefakte (Untertitel-Credits, Geo-/Städte-Fakten)
per Regex, gegated auf voiced_frames — echte Geo-FRAGEN (normale Energie)
gehen durch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 11:19:06 +02:00
duffyduckandClaude Opus 4.8 e9439dbccb feat(app): Hintergrund-Wake als Schalter (default aus)
Punkt 2: das Wake-Wort triggerte nur im Vordergrund — eine bewusste JS-Zeile
verwarf jede Hintergrund-Erkennung (native Erkennung + Foreground-Service
liefen längst durch). Jetzt hinter Einstellung 'Auch bei gesperrtem Bildschirm
zuhören' (default aus, mehr Fehltrigger möglich). Greift live via
setBgWakeEnabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 11:19:06 +02:00
duffyduckandClaude Opus 4.8 219de091d2 feat(brain): Titel-Index fürs kalte Gedächtnis + 'beendet' erkennen
Punkt 4B: jeder System-Prompt bekommt einen kompakten Titel-Index der bewusst
gespeicherten Nachschlage-Memories (Zugangsdaten, Infra, Projekte) — ARIA
sieht WAS sie hat und holt es via memory_search, statt Stefan nach etwas zu
fragen, das schon da ist (Git-Credentials-Vorfall). Auto-distillierte Fakten
+ Conversation-Logs sind ausgefiltert → billig.

Punkt 1: _CONV_END_VERB erkennt jetzt auch 'beendet' (beend\w*) und 'stoppe'
(stop\w*) — 'Konversation beendet' schloss vorher das Mikro nicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 11:19:06 +02:00
duffyduckandClaude Opus 4.8 5992a7e441 feat(brain): Gedächtnis nach scope trennen (system/personal)
Neues Feld scope=system|personal auf jedem Memory-Punkt. Bootstrap-Export
getrennt: System-Regeln (generisch, teilbar) vs. Persönliches (Name,
Zugangsdaten, Projekte). Import ist scope-sicher — ein System-Import löscht
NICHT die persönlichen pinned Memories. seed_rules + AGENT.md/TOOLING.md →
system, USER.md-Präferenzen → personal. Backfill für Bestand (57 system /
617 personal). Diagnostic: zwei Export-Buttons, scope-Badge (SYS/PRIV) +
Umschalter pro Memory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 11:19:06 +02:00
duffyduck 07ccf05429 release: bump version to 0.2.4.1 v0.2.4.1 2026-08-16 02:02:10 +02:00
duffyduckandClaude Opus 4.8 4ab0e68245 fix(voice): Passiv-Backstop lang genug — schneidet lange Antworten nicht mehr ab
Log-Analyse (alter Build): eine lange gesprochene Antwort wurde vom 30s-Passiv-
Timer mitten im Wort gekappt (exit reason=timeout, dann stt_endpoint reason=
stream_end mit abgeschnittenem Text). Genau das Fenster ist raus — ABER mein
Ersatz-Backstop (15s) hätte sogar früher abgeschnitten.

Der Backstop ist eine reine HANG-Notbremse und darf aktives Reden NIE kappen →
jetzt 10min (länger als der ~5min-Hardcap der Aufnahme). Das echte Ende regelt
immer die Aufnahme selbst (Stille-Toleranz / No-Speech / Hardcap). Lange
zusammenhängende Antworten laufen jetzt durch, bis du ≥ Stille-Toleranz pausierst.

Hinweis: der geloggte Fehler ist der ALTE Build — die Fixes sind noch nicht
ausgerollt. Dieser Commit korrigiert den noch-nicht-deployten Stand.

Deploy: neue APK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 01:59:45 +02:00
duffyduckandClaude Opus 4.8 9636a702d3 refactor(voice): tote Fenster-Settings ausgeräumt (Konv-Fenster/Passiv-Lauschen)
Nachdem die Stille-Toleranz jetzt überall der einzige Wert ist, sind die alten
Settings obsolet — raus damit:

- audio.ts: CONV_WINDOW_* Konstanten + loadConvWindowMs() entfernt.
- wakeword.ts: PASSIVE_LISTEN_* + load/savePassiveListenMs() entfernt; der Passiv-
  Master-Timer nutzt jetzt einen festen internen Backstop (PASSIVE_BACKSTOP_MS,
  15s) statt eines Nutzer-Werts — das echte Ende regelt die Stille-Toleranz.
- SettingsScreen: "Konversations-Fenster"- und "Weiterreden-Fenster"-Slider raus;
  Letzterer durch eine kurze Erklärung ersetzt (verweist auf Stille-Toleranz).
- ChatScreen: tote Imports weg.

Kein Verhaltensänderung ggü. ebe0e80 — nur Aufräumen. tsc grün.

Deploy: neue APK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 01:53:55 +02:00
duffyduck 5f09e2bca3 release: bump version to 0.2.4.0 v0.2.4.0 2026-08-16 01:48:43 +02:00
duffyduckandClaude Opus 4.8 ebe0e8065f feat(voice): 30s-Passiv-Fenster raus — Stille-Toleranz regiert alles
Stefans Modell: es braucht keinen separaten 30s-Wert. Nach ARIAs Antwort geht das
Mikro auf; fängst du nicht innerhalb der Stille-Toleranz (z.B. 5s) an zu reden, ist
Schluss → zurück aufs Wake-Word. Derselbe Wert wie die Pause-Toleranz beim Reden.
Ein Befehl ohne "fortführen" = Ende; nach einer normalen Antwort = ein Stille-
Fenster zum Weiterreden.

- Alle Aufnahme-Pfade (wake/barge/passiv): noSpeechTimeoutMs = endpointMs =
  loadSttEndpointMs() statt loadConvWindowMs()/loadPassiveListenMs(). Ein Wert.
- Passiv-Hardcap: loadMaxRecordingMs() statt fix 35s (schnitt langes Reden ab).
- Leeres Endpoint im listening-State: KEIN Re-Arm mehr → exitPassiveListening
  (ein 5s-Fenster, dann Ende). Der 30s-Master-Timer in wakeword.ts wird dadurch
  nie mehr scharf (harmloser Backstop).

Redest du weiter → Antwort → wieder ein Stille-Fenster (Multi-Turn bleibt, nur ohne
30s-Leerlauf). Die Settings "Konversations-Fenster"/"Passiv-Lauschen" sind damit
obsolet (UI-Cleanup später).

Deploy: neue APK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 01:46:26 +02:00
duffyduckandClaude Opus 4.8 9d2c07d8d1 feat(voice): "Konversation fortführen" hält offen — auch bei Fast-Path-Befehl
Stefans Repro: "spiel Spotify auf Smartphone ab ABER Konversation fortführen" →
die Kette endete trotzdem. Grund: "spiel spotify" trifft den Fast-Path (Regex),
der den Satz-Rest nicht versteht → converse=false aus dem Skill-Manifest; ARIAs
[[WEITER]]-Marker lebt in Claude, der wurde uebersprungen.

Fix: _user_wants_conversation_continue() — Gegenstueck zu _user_wants_conversation_
end(). Erkennt "Konversation/Gespraech fortfuehren/weiterfuehren/offen halten/nicht
beenden", "weiter reden/sprechen" etc. und erzwingt converse=true an ALLEN Returns
(fast-path/local/claude), auch wenn das Manifest false sagt. [[ENDE]]/_wants_end hat
Vorrang bei Widerspruch. Getestet inkl. Negativfaelle (sofort/spotify ab).

Deploy: Brain-Neustart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 01:35:49 +02:00
duffyduck 9aae5af6a9 release: bump version to 0.2.3.9 v0.2.3.9 2026-08-16 01:06:18 +02:00
duffyduckandClaude Opus 4.8 a8ff73f93d feat(speaker-id): Gating als bewusster Schalter — Default AUS (fail-open)
Stefans Repro "nichts geht mehr" kam NICHT von den Marker/Guard-Aenderungen,
sondern von der Speaker-ID: die Bridge-Logs zeigten fast durchgehend
"stt_endpoint mit leerem Text — ignoriert (reason=speaker_mismatch)" — ein aus
einem kaputten Enroll (AAC-als-PCM) entstandener Muell-Fingerprint hat Stefans
EIGENE Stimme abgelehnt und damit die komplette STT lahmgelegt.

Fix: Speaker-ID-Gating ist jetzt ein expliziter Schalter, Default AUS. Bei aus
laeuft die Pruefung GAR NICHT (fail-open, alle Stimmen durch) — ein schlechter
Enroll kann nie wieder alles abwuergen. Damit ist Stefans Problem schon durch den
Voxtral-Rebuild geloest (kein Loeschen noetig, der Check greift einfach nicht).

- voxtral + whisper bridge: SPEAKER_ID_ENABLED (Default False, ENV VOICE_ID_ENABLED),
  _check_speaker faellt bei aus sofort fail-open zurueck; config-Broadcast
  voiceIdEnabled setzt es zur Laufzeit.
- diagnostic: Schalter "Nur meine Stimme" in der Voice-ID-Sektion (Default aus,
  Hinweis: erst an wenn enrollt), broadcastet + persistiert voiceIdEnabled.

Reihenfolge lt. Stefan: erst Konversation sauber, dann Multi-Person/nur-ich.

Deploy: docker compose up -d --build voxtral-bridge; aria-diagnostic neu starten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 01:00:58 +02:00
duffyduckandClaude Opus 4.8 0e9adeee5c fix(speaker-id): Enrollment akzeptiert Android-MP4/AAC — kein "zu kurz" mehr
Voice-ID-Enrollment scheiterte immer mit "zu kurz". Ursache: die App nimmt die
Samples mit dem Legacy-Recorder als AAC im MP4-Container auf (16kHz mono), die
Bridge dekodierte das base64 aber als ROHES int16-PCM. 4s AAC sind stark
komprimiert (< 32KB = MIN_SAMPLE_BYTES) → faelschlich als "zu kurz" verworfen,
und selbst darueber waere das Embedding Muell.

Fix (bridge-seitig, kein APK): _normalize_audio_bytes erkennt jetzt den MP4/M4A/
AAC-Container ('ftyp' bei Offset 4) und dekodiert ihn via ffmpeg (im Container) auf
16kHz mono int16 PCM — zusaetzlich zu rohem PCM und WAV. enroll_from_samples
dekodiert erst, prueft DANN die Laenge aufs dekodierte PCM (nicht die komprimierten
Bytes). Input via Temp-Datei, da Androids moov-Atom am Ende seekbaren Input braucht.
Gleich in voxtral + whisper (identische Kopien).

Deploy: docker compose up -d --build voxtral-bridge; danach in Einstellungen →
Voice-ID neu einlernen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 14:43:14 +02:00
duffyduckandClaude Opus 4.8 6addb2f8fe fix(voxtral): Halluzinations-Guard — kein Phantom-Text aus Stille/Blip
Stefans Repro: nach dem Senden ging sofort ein Passiv-Fenster auf (waehrend ARIA
noch arbeitete), und obwohl er nichts sagte, kam eine "Nachricht von ihm" — Voxtral
halluzinierte aus Fast-Nichts einen Fuellsatz. Der ging als Phantom-Turn ans Brain
und liess das Gespraech entgleisen (Antwort drauf, dann Konversation-Ende).

Ursache war eine Nebenwirkung des "Sprache erkannt"-Pings (9e78d75): der feuerte
beim ERSTEN Voice-Frame — also auch bei einem kurzen Geraeusch-Blip. Damit war der
App-No-Speech-Watchdog geloescht, der akustische Endpoint feuerte, und Voxtral
transkribierte den Blip → Halluzination. (Vorher haette der Watchdog den Blip
verworfen.)

Fix: voiced_frames-Zaehler (Endpointer-Frames mit echter Stimme). Erst ab
STREAM_MIN_VOICED_FRAMES (=2, ~400ms, env-tunebar) gilt es als Sprache:
- Der Speech-Ping feuert erst ab dieser Schwelle (ein Blip loescht den Watchdog
  also nicht mehr).
- _finalize transkribiert unter der Schwelle NICHT, sondern schickt ein leeres
  Endpoint (no-speech) → App re-armt still, kein Phantom-Turn. Manueller Stop
  (stream_end) ist ausgenommen (bewusst gesprochen, kurze Woerter ok).

Deploy: docker compose up -d --build voxtral-bridge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 14:35:21 +02:00
duffyduckandClaude Opus 4.8 9bdfb7193e feat(voice): "Konversation Ende" deterministisch — nicht nur ARIAs [[ENDE]]-Marker
Beobachtung Stefan: ARIA haelt die Konversation offen (Default, korrekt), aber
auf "Konversation Ende" hin schloss sie NICHT — sie hat den [[ENDE]]-Marker nicht
gesetzt. Das aufs LLM allein zu verlassen ist zu unzuverlaessig fuer einen festen
Trigger.

Fix: _user_wants_conversation_end() erkennt explizite Beenden-Phrasen im User-Text
(konversation/gespraech/befehlskette + ende/beenden/aus/stop/schluss, beide
Reihenfolgen, ein Satzteil) und erzwingt converse=false an ALLEN drei Returns
(fast-path/local/claude). ARIA beantwortet eine evtl. enthaltene Frage noch normal
(speak bleibt), danach zurueck aufs Wake-Word. "befehls?kette" statt bare "kette",
damit "Lieferkette" u.ae. nicht faelschlich matchen (per Test abgesichert).

Deploy: Brain-Neustart (nicht waehrend ARIA arbeitet).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 14:25:09 +02:00
duffyduckandClaude Opus 4.8 9e78d75149 fix(voxtral): "Sprache erkannt"-Ping — No-Speech-Watchdog schneidet nicht mehr ab
Im Ohr-Modus (Wake/Passiv/Barge) beendete die App die Aufnahme am Konversations-
fenster (~conv window), obwohl Stefan noch redete — nur dort, nicht beim normalen
Button. Ursache: der App-No-Speech-Watchdog cancelt, solange KEIN stt_partial kam
(streamGotPartial=false). Voxtral schickt aber seit dem Cutoff-Fix keine Live-
Partials mehr → streamGotPartial blieb immer false → Timer feuerte mitten im Satz.
Der normale Button nutzt noSpeechTimeoutMs=0 (Watchdog aus) → dort nie aufgefallen.

Fix: Voxtral-Bridge feuert EIN leeres stt_partial beim ersten Voice-Frame (nach
der Speaker-ID-Pruefung, fremde Stimmen signalisieren also nicht). Die App setzt
darauf streamGotPartial=true und loescht den Watchdog — bestehender Handler, keine
App-Aenderung. Der akustische Endpoint (Stille >= endpointMs) bleibt der einzige
Turn-Ende-Ausloeser.

Deploy: docker compose up -d --build voxtral-bridge (keine neue APK noetig).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 14:15:21 +02:00
duffyduck 517c993ac8 release: bump version to 0.2.3.8 v0.2.3.8 2026-08-15 14:06:34 +02:00
duffyduckandClaude Opus 4.8 c1bd13687d feat(voice): ARIA erkennt Gespraechs-Phase selbst (Befehl/Frage, Kette/Ende)
Voice-First: ARIA deklariert die Phase aus dem Text per Inline-Marker — dasselbe
Muster wie [[AWAIT]], die Marker werden vor Anzeige/TTS/History entfernt. Loest:
- Claude-Pfad las Steuerbefehle vor, obwohl das Skill lief (Manifest-speak-Flag
  kennt den Unterschied Befehl/Frage nicht — dasselbe Skill ist mal Aktion, mal
  Auskunft). Jetzt entscheidet ARIA aus dem Kontext.
- Befehlsketten (VNC oeffnen, Menue klicken, ...): Mikro soll offen bleiben und
  stumm gearbeitet werden, bis Stefan die Kette beendet.

Marker (ARIA haengt sie ans Antwort-Ende):
  [[STUMM]]  -> Steuerbefehl, nicht vorlesen. Allein = Einzelbefehl -> danach Stop.
  [[WEITER]] -> Konversation/Kette laeuft weiter -> Mikro offen halten.
  [[ENDE]]   -> Konversation/Kette beenden -> zurueck aufs Wake-Word.

Brain:
- _extract_flow_markers() zieht [[STUMM]]/[[WEITER]]/[[ENDE]] aus dem Claude-Reply
  und ueberschreibt speak/converse (Marker ist autoritativ ueber Skill-Flag).
  [[STUMM]] allein impliziert converse=false; [[ENDE]] schlaegt [[WEITER]].
- prompts.py: neue Sektion build_voice_flow_section() bringt ARIA Marker +
  Befehl/Frage-Klassifikation + Ketten-/Ende-Erkennung bei.

App (ChatScreen): stiller Antwort-Zweig verzweigt jetzt auf converse —
  converse=true (Kette) -> endConversation(false), Mikro bleibt offen fuer den
  naechsten Kettenbefehl; converse=false -> ariaStopRecording (Stop wie gehabt).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 14:00:51 +02:00
duffyduckandClaude Opus 4.8 d9bb7239c6 fix(voice): ARIA schliesst bei Steuerbefehl die Aufnahme selbst (stiller Stop)
Steuerbefehle laufen stumm (speak=false in Fast-Path/Local/Claude-Pfad) — ohne
TTS feuert aber onPlaybackFinished nie, und daran hing bisher das Aufraeumen der
Aufnahme. Folge: nach "spotify play" blieb das Mikro-/Konversations-Fenster offen
(30s Leerlauf), obwohl der Befehl laengst ausgefuehrt war.

Neu: ariaStopRecording() — das programmatische Gegenstueck zum Stop-Button. Nach
einer stillen Antwort (speak=false) schliesst ARIA jede offene Aufnahme selbst und
geht zurueck aufs Wake-Word, egal in welchem Zustand: passives Lauschen sauber
beenden, offene Streaming-Aufnahme (aktiv/Barge) verwerfen, sonst endConversation.
Vorher nur der conversing-Fall abgedeckt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 13:49:18 +02:00
duffyduck 0265aabb5e release: bump version to 0.2.3.7 v0.2.3.7 2026-08-15 13:27:32 +02:00
duffyduckandClaude Opus 4.8 17bc50b847 fix(voice): manueller Stop unterdrueckt Passiv-Fenster nach der Antwort
Stop finalisierte die Aufnahme, aber die danach kommende onPlaybackFinished oeffnete via converseRef erneut das 30s-Passiv-Fenster. Jetzt setzt handleVoiceButtonStop converse=false → nach manuellem Stop kein Passiv-Lauschen mehr.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 13:16:46 +02:00
duffyduckandClaude Opus 4.8 1f2be4299d fix(voice): Passiv-Lauschen nur bei converse:true (kein 30s-Linger nach Befehl)
converse defaultete true → jeder Befehl mit gesprochener Bestaetigung ('Spiele Spotify') ging ins 30s-Passiv-Fenster. Jetzt nur bei explizitem converse:true vom Brain; einzelne Befehle enden sofort → zurueck aufs Wake-Word, Spotify resumed gleich.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 13:15:38 +02:00
duffyduck 0ca8a82013 release: bump version to 0.2.3.6 v0.2.3.6 2026-08-15 13:05:46 +02:00
duffyduckandClaude Opus 4.8 7bc3f827d0 feat(voxtral): Speaker-ID portiert (nur Stefans Stimme) — E3a
Voxtral hatte 0 Speaker-Filter (mit Voxtral reagierte ARIA auf JEDE Stimme). Jetzt portiert aus der whisper-Bridge: speaker_id.py (ECAPA/speechbrain) kopiert, Einmal-Check auf die ersten 1.5s (fremde Stimme → leeres stt_endpoint reason=speaker_mismatch, kein Transcribe/Brain), voice_id_enroll/status/delete-RVS-Handler + voiceIdThreshold-config. voice-id-Volume gemountet, speechbrain in requirements. Ohne Enrollment fail-open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 13:00:32 +02:00
duffyduckandClaude Opus 4.8 e7da9cf9c4 feat(voice): Halb-Duplex-Default + Barge-in-Schalter + echter Cancel (E2)
Behebt den Konversations-Mischmasch: (1) Barge-in-Einstellung (Default AUS = Halb-Duplex) gated das Mikro-Lauschen waehrend TTS -> ARIA spricht ungestoert zu Ende. (2) interruptAriaIfBusy bricht die Brain-Antwort WIRKLICH ab (cancel_request) statt nur TTS zu muten -> sie antwortet nicht mehr weiter waehrend man redet. (3) Stop-Button stoppt vorhersehbar alles (TTS + Brain-Cancel). Musik: nativer Focus ist schon GAIN_TRANSIENT (pausiert) -> der saubere Flow behebt das Focus-Geflacker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:57:24 +02:00
duffyduckandClaude Opus 4.8 353fd98d3f feat(wake): schnellere/empfindlichere Wake-Word-Defaults (E1)
Gegen 'traege': patience 2->1, STARTUP_SUPPRESSION_MS 1500->600, Foreground-Cooldown 3000->1000, Resume-Cooldown 1500->500, Threshold-Default 0.6->0.45. Fehlausloeser faengt die Speaker-ID (E3) ab. Wort bleibt 'computer'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:57:24 +02:00
duffyduck 0350dd33c8 release: bump version to 0.2.3.5 v0.2.3.5 2026-08-15 12:07:01 +02:00
duffyduckandClaude Opus 4.8 7b956c6606 feat(voice): Stille-Toleranz bis 8s stellbar (Denkpausen)
STT_ENDPOINT_MAX_MS 4000->8000. Der (in a) auf den aktiven Endpoint umgeklemmte 'Stille-Toleranz'-Regler geht damit bis 8s statt nur 4s — genug Zeit zum Nachdenken, ohne dass die Aufnahme endet. Fliesst per endpointMs an Voxtral/Whisper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:04:40 +02:00
duffyduckandClaude Opus 4.8 36f04f83ff fix(voxtral): willkuerliche Abbrueche — semantischen Endpoint + Live-Partials raus
Ursache: Voxtral-3B transkribiert den ganzen wachsenden Buffer (~5-6s bei langen Aufnahmen). Diese Partial-Latenz war groesser als der semantische Endpoint-Timeout (4.8s) → 'Text waechst nicht mehr' feuerte faelschlich → Abbruch nach 20-40s. Fix: keine Live-Partials mehr, kein semantischer Endpoint — Turn-Ende rein akustisch (Stille-VAD), transkribiert wird nur EINMAL im _finalize. max_new_tokens 512->4096 (512 schnitt lange Diktate ab).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:02:44 +02:00
duffyduck 01df26e6df release: bump version to 0.2.3.4 v0.2.3.4 2026-08-15 11:49:28 +02:00
duffyduckandClaude Opus 4.8 f226c91973 fix(voxtral): librosa als Dependency (Processor laedt WAV damit)
VoxtralProcessor.apply_transcription_request -> load_audio_as braucht librosa zum Einlesen der Audiodatei. Fehlte im Image.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 11:32:44 +02:00
duffyduckandClaude Opus 4.8 3324d39d50 fix(voxtral): Audio als temp-WAV-Pfad an Processor (statt rohem Array)
VoxtralProcessor.apply_transcription_request verlangt bei rohen Arrays ein 'format'. Fix: Buffer in ein temp-WAV (PCM_16, 16kHz) schreiben und den Pfad uebergeben — Processor liest Format+Samplerate selbst. Temp-Datei wird nach dem Transkribieren geloescht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 11:29:51 +02:00
duffyduckandClaude Opus 4.8 fff2e7df34 feat(xtts): Voxtral als Default-STT, Whisper als opt-in Fallback-Profil
Profile getauscht: voxtral-bridge laeuft jetzt bei jedem 'docker compose up', whisper-bridge nur noch mit --profile whisper. Loest das 'nach down/up startet whisper statt voxtral'-Problem. Immer nur EIN STT gleichzeitig (sonst stt_*-Kollision).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 11:24:45 +02:00