Commit Graph
975 Commits
Author SHA1 Message Date
duffyduck 629a3d82ac release: bump version to 0.2.1.3 v0.2.1.3 2026-07-11 23:18:27 +02:00
duffyduckandClaude Opus 4.8 9bb90a777a fix(app): Sprachnachricht-Bubble verschwindet nach manuellem Stop nicht mehr
Ohne Ohr (manuelle Aufnahme) + Stop-Button: das echte STT-Endpoint (mit Text)
fuellt die Bubble, aber danach klappt ein zweites, LEERES stream_end-Endpoint
nach (Audio schon transkribiert). Der Empty-Handler loeschte die Bubble
bedingungslos per audioRequestId — auch die schon mit Text gefuellte.
Ergebnis: Bubble weg, obwohl ARIA an der Antwort arbeitet; erst nach
App-Neustart (aus chat_backup) wieder da.

Fix: leeres Endpoint entfernt nur noch den NOCH UNAUFGELOESTEN Platzhalter
(Text enthaelt 'Spracheingabe wird verarbeitet'), nicht eine bereits
aufgeloeste Bubble.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 23:17:04 +02:00
duffyduckandClaude Opus 4.8 c3bb7985a2 fix(skills): skill_get-Tool nachgereicht — kein Blind-Rewrite mehr
Skandal-Ursache: die skill_update-Anleitung sagt "erst skill_get lesen, dann
updaten" — aber skill_get existierte GAR NICHT als Tool. ARIA/Claude konnte
einen Skill also nie LESEN vor dem Aendern → Blind-Rewrite aus Beschreibung +
Gedaechtnis. (Claude sagte korrekt "skill_get steht mir nicht zur Verfuegung"
und hat den Spotify-Skill trotzdem komplett neu geschrieben.)

Neu: skill_get(name) liefert Manifest (args/fast_patterns/speak/converse) +
kompletten entry_code (echter Python) + README. skills.read_skill_source().
So sieht ARIA den Ist-Code und aendert gezielt statt blind zu ueberschreiben.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 23:04:37 +02:00
duffyduckandClaude Opus 4.8 debd96b16f fix(fast-path): converse folgt dem Skill (Manifest/Output), nicht hart False
Fast-Path hatte converse hart auf False — ein Fast-Path-Skill konnte damit nie
ein Dialog-Skill sein. Jetzt konsistent zu local/Claude: speak UND converse
kommen aus dem Skill-Manifest-Default und werden vom Skill-JSON-Output pro
Aufruf ueberschrieben. Default (kein Flag) bleibt false/false = stumm/stop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:57:52 +02:00
duffyduck 486d7dedbb release: bump version to 0.2.1.2 v0.2.1.2 2026-07-11 22:48:43 +02:00
duffyduckandClaude Opus 4.8 d5bcbb4814 feat(skills): Skill steuert Ausgabe selbst — speak + converse, pro Aufruf
Verallgemeinert die Ausgabesteuerung, dynamisch statt hardcoded:
- Zwei Flags: speak (vorlesen?) + converse (danach 30s weiterlauschen?).
  Getrennt, weil 'vorlesen' und 'Dialog offen halten' verschiedene Dinge sind
  ('was laeuft gerade' → vorlesen JA, aber keine 30s).
- Statischer Manifest-Default (speak/converse) PLUS: der Skill kann beide im
  JSON-Output PRO AUFRUF setzen und den Default ueberschreiben — so kann EIN
  Skill gemischt sein (Spotify: 'next' stumm/stop, 'was laeuft' vorlesen/stop).
- chat() gibt jetzt (reply, answered_by, speak, converse) zurueck; gilt fuer
  Fast-Path (converse immer False), local UND Claude (run_*-Skill setzt beide).
- Brain: _last_skill_flags aus Skill-stdout-JSON, _skill_response_flags mergt
  Output > Manifest > False. main.py ChatOut.converse, background.py angepasst.
- Bridge: converse aus /chat gelesen + in Chat-Payload + _process_core_response.
- App: converseRef aus der Payload; onPlaybackFinished endet mit skipPassive
  wenn converse=false (vorlesen ohne 30s).
- Skill-Bau-Anleitung (skill_create/update-Schema): speak+converse dokumentiert
  MIT dem WARUM, damit ARIA sie beim Bauen sinnvoll setzt (nicht nur mechanisch).
- Prompt-Hardcode fuer Spotify-Faehigkeiten raus → Skills beschreiben sich selbst.

Bestehende Skills ohne Flags = false/false = stumm/stop (kein Verhaltenswechsel).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:47:30 +02:00
duffyduckandClaude Opus 4.8 54ebd57990 fix: generischer Skill-Prompt (kein Hardcode) + Stop im 30s-Lauschen beendet
- router.py: die run_spotify-Faehigkeiten NICHT mehr im Prompt aufzaehlen
  (war selbst Hardcoding). Generisch: "run_*-Skills — was sie koennen steht in
  IHRER Tool-Beschreibung, lies + nutze sie fuer alles Passende". Skills
  beschreiben sich selbst (dynamisch, ARIA-authored). Anti-Halluzination bleibt.
- App: Stop-Button waehrend passivem 30s-Lauschen ('listening') beendet jetzt
  sauber (exitPassiveListening) statt via leerem Endpoint den Passiv-Stream neu
  zu starten — die 30s liefen sonst von vorne los.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:36:01 +02:00
duffyduckandClaude Opus 4.8 fb06ed928c fix(local-llm): Anti-Halluzination — behauptete Skill-Aktion ohne Tool → Claude
Local hat "Spotify: überspringe 3 Titel" / "Playlist X abspielen" geantwortet
OHNE run_spotify je zu rufen (Skill-Logs: kein Aufruf) — reine Fabrikation,
real passierte nichts. Zwei Ursachen behoben:
- Prompt: run_spotify-Beschreibung von "next/pause/weiter" auf JEDE
  Musiksteuerung erweitert (Playlist, Gerät, überspringen mehrerer, Lautstärke)
  + harte Anti-Halluzinations-Regel (nie eine Aktion behaupten ohne Tool-Call).
- Guard: sagt local eine Steuerbefehl-Quittung ('Spotify: …', 'Playlist …
  abspielen', 'überspringe … Titel') aber hat KEINEN run_*-Skill gerufen
  (_local_ran_skill=False) → eskalieren, Claude ruft das Tool zuverlässig.

_looks_like_skill_action bewusst eng (Doppelpunkt-Praefix / spezifische Verben),
trifft normale Musik-Konversation nicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:30:00 +02:00
duffyduckandClaude Opus 4.8 08bb257791 fix(bridge): Ortsname rechtzeitig da — Geocode vor Präfix-Bau awaiten
Der Reverse-Geocode lief nur async im Hintergrund — die ERSTE Nachricht an
einem Ort (v.a. direkt nach Bridge-Neustart, Cache leer) wurde gebaut BEVOR
der Name fertig war → Präfix ohne Ort → local suchte Wetter mit rohen
Koordinaten und fand nichts.

Neu: _ensure_place_name() awaitet den (laufenden oder frisch gestarteten)
Geocode kurz mit Timeout, bevor der Standort-Präfix gebaut wird. Gecacht →
nur die erste Nachricht an einem neuen Ort zahlt ~0,5s, danach instant.
Timeout/Fehler → Fallback auf reine Koordinaten (kein Hänger).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:18:42 +02:00
duffyduckandClaude Opus 4.8 44982a9b3c feat(bridge): exakte Peilung (Grad) zusätzlich zur Himmelsrichtung im Präfix
Neben "nordwestwaerts" jetzt auch die genaue Bearing-Gradzahl (0-359) —
"unterwegs nordwestwaerts (304 Grad)". Die KI nutzt, was die Frage braucht
(Pannenhilfe → Ort/km, Luftfahrt/Navigation → exakte Peilung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:12:38 +02:00
duffyduckandClaude Opus 4.8 ddb1bfac6a feat(bridge): Fahrtrichtung (Himmelsrichtung) im Standort-Präfix
Aus dem permanenten GPS wird die Bewegungsrichtung berechnet: Bearing zwischen
einem Anker-Punkt und der aktuellen Position, gemappt auf 8-Wind-Adverb
(nordwestwaerts …). Der Anker rueckt erst bei echter Bewegung (>25 m) nach,
sonst zaehlt GPS-Jitter im Stand nicht — Stehenbleiben behaelt die letzte
Richtung, Umdrehen liefert automatisch die neue.

Praefix jetzt z.B.:
  [Stefans aktueller Standort: A 28 bei km 55,6, Oldenburg, Niedersachsen
   (GPS 53.12, 8.22), unterwegs nordwestwaerts. ...]

Reiner Bridge-Fix (Haversine + Bearing lokal, kein Dienst noetig).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:11:07 +02:00
duffyduckandClaude Opus 4.8 4605698b02 feat(bridge): Standort-Präfix mit Straße+Hausnr, Straßen-Ref + km (Autobahn)
Erweitert den Reverse-Geocode: Nominatim zoom=18 → Straße + Hausnummer, PLZ,
Stadt, Bundesland ("Am Wunderburgpark 6, 26135 Oldenburg, Niedersachsen").
Bei Autobahn/Bundes-/Kreisstraße wird die Ref (A 28 / B 75 / K …, aus
extratags) vorangestellt + best-effort Kilometer via Overpass-Milestone
(highway=milestone, distance-Tag, naechster im Umkreis) → z.B.
"A 28 bei km 55,6, Oldenburg, Niedersachsen". Für Pannenhilfe & Co.

Cache-Raster von ~1km auf ~110m verfeinert (Straßen-Genauigkeit). Overpass
nur wenn's nach Straße/Autobahn aussieht (ref/Autobahn im Namen). Alles
best-effort mit stiller Fallback-Kette (Fehler → gröberer Ort → nur GPS).
NOMINATIM_URL + OVERPASS_URL env → self-hostbar.

Richtung ("Richtung Leer") bewusst weggelassen — nicht zuverlaessig aus
einem Punkt ableitbar (braucht Fahrtrichtung/Routing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:06:15 +02:00
duffyduckandClaude Opus 4.8 1c11cb6a2f feat(bridge): GPS→Ortsname im Standort-Präfix (Reverse-Geocode, keine Tokens)
Das lokale 8B kann Koordinaten nicht zuverlässig in eine Stadt uebersetzen
(riet "Berlin" fuer Oldenburg). Claude kann's, aber das kostet Tokens.
Loesung: die Bridge loest lat/lon EINMAL via Nominatim (OpenStreetMap, gratis,
keyless) auf und schreibt den Namen in den Praefix:
  [Stefans aktueller Standort: Oldenburg, Niedersachsen (GPS 53.12, 8.22). ...]
So muss KEIN Modell mehr raten — local UND Claude lesen den Ort direkt ab,
Token-Ersparnis bleibt voll erhalten.

- _persist_location merkt sich den letzten Ort + triggert den Geocode async
  (im Hintergrund, gecacht pro ~1km-Raster, nur bei Bewegung → Nominatim-
  Rate-Limit locker eingehalten).
- _build_core_text nutzt frische ODER letzte bekannte Position (Praefix faellt
  beim passiven Weiterreden ohne frisches GPS nicht mehr weg) + den Ortsnamen.
- NOMINATIM_URL env (Default oeffentlicher Dienst) → spaeter self-hostbar.
Reiner Bridge-Fix, kein APK/Brain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 22:02:47 +02:00
duffyduck 6cf75644d8 release: bump version to 0.2.1.1 v0.2.1.1 2026-07-11 21:46:56 +02:00
duffyduckandClaude Opus 4.8 2c1de6705b feat(skills): speak-Flag gilt jetzt auch im Claude-Pfad
Vorher gab Claude immer speak=true zurück — führte Claude einen speak=false-
Steuerbefehl aus (z.B. "spiele auf duffy desktop weiter"), wurde die Antwort
trotzdem vorgelesen + 30s. Jetzt konsistent zu Fast-Path/local: führt Claude
einen run_*-Skill mit speak=false erfolgreich aus → speak=false (kein TTS,
App STOP). Der Text landet aber normal in der Bubble (Stefans Wunsch: Text ok,
nur nicht vorlesen). Antwort-Skills (speak=true) + reine Konversation bleiben
gesprochen; bei Skill-Fehler bleibt's gesprochen (Erklaerung soll man hoeren).

Reiner Brain-Fix — App liest speak bereits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:45:27 +02:00
duffyduckandClaude Opus 4.8 2716bc62ff feat(skills): Skill entscheidet selbst ob vorgelesen wird (manifest.speak)
Verallgemeinert das "run_* → stumm"-Heuristik: jeder Skill deklariert im
Manifest ein speak-Flag.
- speak=false (Default) = Steuerbefehl (Spotify, Licht) → kein TTS, App
  beendet direkt (STOP), wie bisher.
- speak=true = Antwort-Skill (Info/Ergebnis) → Antwort wird vorgelesen +
  Gespraechs-Fenster bleibt offen.

Gilt für BEIDE Pfade: Fast-Path (_fast_path_speak aus skill.speak) und
lokale Skill-Ausführung (_local_turn_speak = _skill_speak_flag(run_*)).
Info-Tools (web_search/memory_search/trigger_timer) bleiben gesprochen.

- skills.py: speak in create_skill + update_skill-allowed.
- agent.py: skill_create/skill_update Tool-Schema dokumentiert speak (damit
  ARIA es beim Skill-Bau setzen kann), Dispatch reicht es durch.
- App: _isSilent nur noch speak===false (kein answeredBy=fast-path-Fallback
  mehr, sonst waere ein speak=true-Fast-Path faelschlich stumm).

Bestehende Skills ohne Feld = false = stumm (kein Verhaltenswechsel).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:39:55 +02:00
duffyduckandClaude Opus 4.8 a9e1a46a2f feat(local-llm): Skill-Ausführung via local = kein TTS (speak=False)
Wenn das lokale LLM einen echten Skill (run_*, z.B. Spotify) ausführt, ist es
ein Steuerbefehl — genau wie Fast-Path. Jetzt speak=False: kein Vorlesen, und
die App beendet direkt (STOP) statt ins 30s-Gespräch zu gehen. Deckt den Fall
ab, wo Whisper sich verhört ("Nächster Slick") und local statt Fast-Path
den Skill auffängt.

Info-Tools (web_search/memory_search/trigger_timer) zählen NICHT — deren
Antwort/Bestätigung bleibt gesprochen. Reiner Brain-Fix: die App liest speak
bereits (2b48e5c), kein neuer APK nötig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:33:17 +02:00
duffyduck 245dfc4d73 release: bump version to 0.2.1.0 v0.2.1.0 2026-07-11 21:10:02 +02:00
duffyduckandClaude Opus 4.8 6a94b574f2 fix(app): Mund-Button stoppt laufendes Vorlesen wirklich (Cache bleibt)
Zwei Fehler: (1) War der PCM-Stream schon komplett empfangen (isFinal durch),
sind pcmStreamActive+isPlaying false — der AudioTrack spielt aber seinen
Buffer noch sekundenlang aus. stopPlayback() returnte dann früh ("nichts
aktiv") und rief PcmStreamPlayer.stop() NIE → Mund-Button wirkungslos.
(2) stopPlayback() wirft pcmBuffer weg → die Cache-WAV waere unvollstaendig,
Nachhoeren via Lautsprecher-Symbol kaputt.

Neue _silenceAudibleOutput(): stoppt den AudioTrack IMMER (auch im Drain-Fall),
laesst aber pcmBuffer/pcmMessageId/pcmStreamActive stehen — restliche Chunks
cachen stumm weiter, isFinal schreibt die vollstaendige WAV. setMuted(true)
nutzt das statt stopPlayback(). stopPlayback bleibt fuer Barge-In/Cancel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:07:41 +02:00
duffyduckandClaude Opus 4.8 a5e2256a44 feat(app): Wake-Word-Empfindlichkeit + Weiterreden-Fenster in Settings
Fehlauslösung im Auto: Spotify läuft über die Lautsprecher, das Mikro hört
mit, openWakeWord halluziniert "computer" rein (Log: wake.detect state=armed
→ leerer Transkript). Der Echo-Canceler kann nur ARIAs eigenes TTS
rausrechnen, nicht Spotify (fremde App, kein Referenzsignal).

- Wake-Word-Threshold jetzt konfigurierbar (loadWakeThreshold), Default von
  0.5 auf 0.6 hoch (strenger → weniger Fehlauslösung). Slider im Wake-Word-
  Settings-Bereich (0.30–0.90, greift beim "Speichern + Aktivieren").
- Weiterreden-Fenster (passives Lauschen) als Slider, Default 30s (10–60s).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:01:48 +02:00
duffyduckandClaude Opus 4.8 9fb29aa517 fix(voice): klarer Befehl = STOP, Gespraech = 30s passiv (kein zweiter Gong)
Nach dem Re-Arm-Fix rief die App resume() → das spielte einen zweiten Gong
und oeffnete ein neues Aufnahme-Fenster, obwohl Stefan nur einen Steuerbefehl
gab. Sein gewuenschtes Verhalten:
- Klarer Befehl (Fast-Path, speak=false, z.B. Liedersteuerung) = KEINE
  Konversation → STOP: direkt zurueck aufs Wake-Word (kein Gong, keine
  Aufnahme, kein 30s-Fenster).
- Gespraech (gesprochene Antwort) = kein neuer Gong, direkt 30s passives
  Lauschen (weiterreden wie mit einem Menschen), 30s still → Wake-Word.

endConversation(skipPassive) neu: true springt direkt zu armed statt in
passives Lauschen. onPlaybackFinished (Gespraech) → passiv (Vordergrund) bzw.
direkt armed (Hintergrund); stille Fast-Path-Antwort → skipPassive; manueller
Stop-Button → skipPassive. resume() bleibt nur noch Mic-Fail-Retry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:52:04 +02:00
duffyduck a0494e90ee release: bump version to 0.2.0.9 v0.2.0.9 2026-07-11 20:42:25 +02:00
duffyduckandClaude Opus 4.8 2b48e5cac6 fix(voice): Ohr re-armt nach Fast-Path-Befehl (kein TTS → kein Re-Arm-Trigger)
Fast-Path-Antworten sind speak=False → kein TTS → onPlaybackFinished feuert
nie. Das Wake-Word-Re-Arm haengt aber genau daran → nach "nächster Titel"
blieb das Ohr grau in 'conversing' stecken (Stefan im Auto, 2 Min gewartet,
kein Re-Arm). Log bestaetigt: nach stream.final kam kein wake.end/wake.start.

- Bridge: chat-Payload traegt jetzt 'speak' (war nur answeredBy).
- App: bei stiller Antwort (speak=false ODER answeredBy=fast-path) und
  laufender Konversation dieselbe Re-Arm-Logik wie bei TTS-Ende anstossen
  (aktiv → resume/Konversationsfenster, sonst → endConversation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:40:57 +02:00
duffyduck aefdff89dc release: bump version to 0.2.0.8 v0.2.0.8 2026-07-11 20:33:12 +02:00
duffyduckandClaude Opus 4.8 f99e90f524 fix(voice): Doppel-Ausfuehrung — identischen STT-Endpoint-Text <5s deduppen
Im Auto cancelt der App-seitige No-Speech-Watchdog den conversing-Stream
vorzeitig (Whisper-Partials kommen bei Fahrgeraeusch verspaetet) und startet
passives Lauschen, waehrend die Bridge dieselbe Aeusserung parallel noch
finalisiert. Ergebnis: derselbe Befehl ("nächstes lied") wird zweimal
ausgefuehrt — aus zwei verschiedenen audioRequestIds, darum griff die
bestehende ID-Idempotenz nicht.

Text-Fenster-Dedup: identischer normalisierter Endpoint-Text innerhalb 5s
wird verworfen. Serverseitig, kein APK noetig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:30:37 +02:00
duffyduckandClaude Opus 4.8 1dd47888a8 fix(app): STT-Endpoint großzügiger + Mikro vor Re-Arm freigeben
#2 Vorzeitiges Absenden: endpointMs war hart 1500ms — im Auto (Sprechpausen)
schnitt das mitten im Satz ab (die 11.8s-Frage wurde bei "…ohne dass ein"
gekappt). Jetzt konfigurierbar (aria_stt_endpoint_ms, Default 2400, 1000-4000).

#3 Ohr bleibt ausgegraut: beim Re-Arm rief der Wake-Word-Service
OpenWakeWord.start(), waehrend die passive Streaming-Aufnahme noch das Mikro
hielt → start() schlug fehl → state=off. Neuer micReleaseHook cancelt die
Aufnahme VOR start() (endConversation / exitPassiveListening /
discardIfFreshlyTriggered). ChatScreen registriert den Hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:29:04 +02:00
duffyduckandClaude Opus 4.8 8b22557ca5 fix(fast-path): Voice-Befehle matchen wieder (GPS-Praefix, Umlaute, Kommas)
Voice-Nachrichten tragen den GPS-Hint-Praefix der Bridge
("[Stefans aktuelle GPS-Position: ...] nächstes lied bitte") und echte
Umlaute + Whisper-Kommas. Die ^...$-verankerten fast_patterns matchten damit
NIE — jeder Voice-Spotify-Befehl ging unnoetig an local/Claude (Tokens +
Latenz + TTS statt 0-Token-Fast-Path, was das Doppel-Ausfuehren mit anheizte).

_normalize_for_fast_match: fuehrende [ ... ]-Hint-Bloecke strippen, Umlaute
falten (ä→ae …), interne Satzzeichen (Komma/Punkt) raus. Pattern wird gleich
gefaltet. Router strippt den Praefix ebenfalls (Laengen-/Hint-Heuristik).

Wirkung: "nächstes lied bitte", "play", "nächster titel" -> Fast-Path
(instant, speak=False). "spiele auf duffy desktop weiter" bleibt Router-Sache.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:25:26 +02:00
duffyduckandClaude Opus 4.8 bd78f384a0 fix(diagnostic): Verstecken im Kontext-Streifen (Main-Tab) statt nur Settings
Das Auge/Toggle war faelschlich nur in der vertikalen Projektliste unter
Einstellungen — Stefan managed Projekte aber ueber die Ordner-Bubbles im
Kontext-Streifen auf dem Main-Tab, und der filterte hidden gar nicht
(verstecktes Projekt blieb sichtbar, kein Auge).

renderContextStrip: versteckte Projekte standardmaessig raus, 🙈/👁-Auge pro
Bubble (eigenes onclick + stopPropagation, wechselt nicht den Kontext),
"versteckte anzeigen (N)"-Toggle-Chip am Ende; setStripProjectHidden patcht
+ raeumt Focus wenn noetig. project_changed refresht jetzt Streifen UND Liste.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 17:18:07 +02:00
duffyduckandClaude Opus 4.8 14bdd4dbf4 fix(diagnostic): no-store fuer index.html — Browser servierte alte Version
Die /-Response hatte keinen Cache-Control-Header. Der Browser cachte das
grosse Single-HTML-Dashboard heuristisch und servierte trotz (soft) Reload
die alte Inline-JS/CSS — neue Features (Projekte-verstecken-Button, Auge,
Token-Ersparnis-Card) tauchten erst nach Hard-Reload auf. Jetzt no-store +
no-cache, so dass jeder Load die frische Version bekommt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 17:15:34 +02:00
duffyduck bbfa1f73f6 release: bump version to 0.2.0.7 v0.2.0.7 2026-07-11 17:14:32 +02:00
duffyduckandClaude Opus 4.8 fed3cb9f18 fix(projects): Live-Sync verstecken zwischen App und Diagnostic (ohne Refresh)
Kern-Bug: der Diagnostic-Server reichte ein von RVS empfangenes
project_changed NIE an die Browser weiter — der Handler in index.html war
toter Code, der Diagnostic aktualisierte die Projektliste also nie live.

- server.js: RVS-Handler forwardet project_changed jetzt an die Browser-Tabs;
  der /api/brain-Proxy broadcastet project_changed (RVS + lokal) nach jeder
  erfolgreichen Projekt-Mutation (create/switch/end/archive/PATCH inkl. hidden).
- App ProjectsBrowser: sendet project_changed nach dem Verstecken/Sichtbar-
  machen und laedt bei eingehendem project_changed selbst neu.

Damit: verstecken in der App -> sofort im Diagnostic weg (und umgekehrt),
ohne Seiten-Refresh. Diagnostic-Hide funktionierte schon (Daten/Proxy/Logik
korrekt) — versteckte sind per "Versteckte anzeigen (N)"-Toggle sichtbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 17:11:51 +02:00
duffyduckandClaude Opus 4.8 9ec9119bd9 docs(changelog): 0.2.0.6 abgebunden (Unreleased → [0.2.0.6])
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:51:10 +02:00
duffyduck 06d0064c8c release: bump version to 0.2.0.6 v0.2.0.6 2026-07-11 16:50:22 +02:00
duffyduckandClaude Opus 4.8 a7cde52ee6 docs(changelog): Plan-B-Epos (0.2.0.4/5) + heutige Fixes nachgetragen
Changelog endete bei 0.2.0.3 (07-10). Nachgetragen:
- 0.2.0.4/5 (07-11): Plan B Lokales LLM (Router, Fast-Lane, SearXNG-Tools,
  llama-swap/B0.5, Quell-Badge, speak-Flag, --system-prompt Identity-Fix,
  Diskretions-Regel).
- Unreleased (07-11): Spotify-Resume via MEDIA_PLAY, TTS-Zahlen ausschreiben,
  "ARIA denkt"-Sync, weiche Tool-Fehler-Eskalation, Token-Ersparnis-Metrik,
  Projekte verstecken (Diagnostic + App), von ARIA geschaerfter Spotify-Skill.
Alt-Label "Unreleased 07-10" korrekt auf [0.2.0.3] gesetzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:49:02 +02:00
duffyduckandClaude Opus 4.8 a353b62b37 feat(app): Projekte verstecken auch in der App (Auge + Toggle)
Spiegelt das Diagnostic-Feature auf App-Seite:
- Project-Interface + updateProject um hidden erweitert; setProjectHidden().
- ProjectsBrowser: versteckte Projekte standardmaessig ausgeblendet (mama
  sieht sie nicht). Auge pro Zeile (🙈 verstecken / 👁 sichtbar), Toggle
  "👁 Versteckte anzeigen (N)" blendet sie temporaer gedimmt + Badge ein
  zum Ansehen/Auswaehlen/Wieder-Sichtbarmachen. Eigener Touch am Auge, damit
  der Tap nicht das Projekt wechselt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:45:15 +02:00
duffyduckandClaude Opus 4.8 bc47c2053b feat(projects): Projekte verstecken (Auge-Toggle im Diagnostic)
Neues hidden-Flag pro Projekt (default false, bleibt voll nutzbar — nur
optisch aus der Liste ausgeblendet, unabhaengig von status/archived).
- projects.py: hidden in create_project + update_project-Patchkeys.
- main.py: ProjectUpdateBody.hidden → PATCH /projects/{id} setzt es.
- Diagnostic: pro Projekt-Bubble ein Auge (🙈 verstecken / 👁 sichtbar
  machen); Header-Toggle "Versteckte anzeigen (N)" blendet sie temporaer
  ein (gedimmt + Badge) zum Ansehen/Auswaehlen, ohne sie permanent
  sichtbar zu machen. Auge im eingeblendeten Zustand macht sie dauerhaft
  wieder sichtbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:41:56 +02:00
duffyduckandClaude Opus 4.8 dc043ceb4d feat(metrics): lokaler LLM-Verbrauch + Claude-Ersparnis in Diagnostic
metrics.jsonl-Eintraege tragen jetzt 'source' (claude|local|fast-path).
- log_local_call(): echte usage-Tokens vom Adapter (prompt/completion),
  sonst chars/4-Schaetzung. Geloggt pro Tool-Runde im lokalen Fast-Lane.
- log_fast_path(): reiner Skill, 0 Prompt-Tokens — gesparter Claude-Call.
- aggregate() liefert zusaetzlich by_source (calls/tokens_in/tokens_out).
  Alt-Eintraege ohne source zaehlen als claude (rueckwaerts-kompatibel).

Diagnostic Gehirn-Tab: neue Card "Lokales LLM & Claude-Ersparnis" — pro
Fenster (1h/5h/24h/30d) gesparte Claude-Calls (local + fast-path) und
lokale Token-Last (eigene HW, kein Quota) + Info-Block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:39:02 +02:00
duffyduckandClaude Opus 4.8 8bac7c56bc fix(local-llm): weiche Tool-Fehler eskalieren (Exit 0 + Fehlertext)
Die Escalate-on-Tool-Error-Logik griff nur bei hartem FEHLER-Prefix (Exit
!= 0). Action-Skills melden Fehlschlaege aber oft im stdout-Text bei Exit 0
(Spotify: "Fehler beim Uebertragen", "Geraet nicht gefunden"). Das lokale
LLM las den Fehler dann brav vor statt zu eskalieren — und wiederholte beim
Nachhaken dieselbe leere Absichtserklaerung.

Generisch (nicht Spotify-spezifisch): fuer run_*-Skills werten weiche
Fehler-Marker im Ausgabetext ebenfalls als Fehlschlag → Claude uebernimmt
das mehrstufige Mitdenken. Info-Tools (web_search/memory_search) ausgenommen,
damit deren Inhalt das Wort "Fehler" tragen darf. Eskalieren ist immer sicher.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:25:52 +02:00
duffyduckandClaude Opus 4.8 3b6d36f2de fix(sync): "ARIA denkt" bleibt haengen obwohl Turn fertig
Zwei Ursachen, beide adressiert:
- App raeumte den (kontext-scoped) Thinking-Indikator nur ueber das
  agent_activity 'idle' der Bridge. Kam das nicht an (dedup/mismatch),
  blieb "ARIA denkt" + Abbrechen stehen. Jetzt raeumt die App den
  Indikator beim Eintreffen der Antwort selbst (definitiver Turn-Ende-Beweis).
- Bridge sendete 'thinking' mit der REQUEST-projectId, 'idle' aber mit der
  TURN-projectId (Brain kann umrouten, z.B. Voice-Sticky). Bei Abweichung
  bekam der Request-Kontext nie sein idle → zusaetzliches idle fuer die
  Request-projectId am Turn-Ende.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:23:41 +02:00
duffyduckandClaude Opus 4.8 7f6e266d15 fix(tts): freistehende Ganzzahlen tag-unabhaengig ausschreiben
Regression seit das lokale LLM leichte Turns (Wetter, Spotify) beantwortet:
es nutzt bewusst KEINE <voice>-Tags, an denen frueher die Zahl-Aussprache hing.
clean_text_for_tts schrieb nur Uhrzeiten/Dezimalzahlen/Zahl+Einheit aus, nie
freistehende Ganzzahlen ('23°C', '100%', '7 Nachrichten').

Neuer vollstaendiger Konverter _int_to_words_de (0..999999) + generischer
Durchlauf am Ende von clean_text_for_tts, der alle uebrigen Ganzzahlen zu
Woertern macht. Tag-unabhaengig -> gilt fuer local UND claude. Lange
Ziffernfolgen (IDs/Codes, >6 Stellen) bleiben Ziffern; an .,:/ klebende
Zahlen (IPs, Versionen) werden uebersprungen. Nebenbei: fehlendes Leerzeichen
bei '23°C' -> 'dreiundzwanzig Grad Celsius' gefixt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:18:58 +02:00
duffyduckandClaude Opus 4.8 4d1664a328 fix(app): zuverlaessiger Spotify-Resume via MEDIA_PLAY-KeyEvent statt Focus-Nudge
Native AudioFocus: dispatchMediaPlay() (echter KEYCODE_MEDIA_PLAY an die aktive
MediaSession, wie die Kopfhoerer-Play-Taste) + isMusicActive() zum Gaten.
audio.ts merkt vor dem Focus-Grab ob Musik lief und resumt am Dialog-Ende nur
dann — deterministisch statt des auf OnePlus flakigen nudgeMediaResume.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:13:18 +02:00
duffyduck 1ff02f9763 release: bump version to 0.2.0.5 v0.2.0.5 2026-07-11 16:11:12 +02:00
duffyduck 5b5d61513f fix(router): lokales Tier eskaliert Gedaechtnis-/Personen-Fragen an Claude
Test 'Wer ist Melanie?': das lokale Qwen klammte zu + verwechselte den Frager
('frag Stefan direkt' — Stefan IST der User), weil der schlanke Lokal-Prompt kein
Memory hat. Claude dagegen antwortete diskret + korrekt (und haengte sogar selbst
<voice></voice> an → stumm, 'jemand koennte mithoeren').

Fix: Local-Prompt weist an, bei Fragen zu Stefans Leben/Personen/Beziehungen/
Vergangenheit/gespeichertem Wissen NICHT aus dem Nichts zu antworten, sondern
zu eskalieren (<<ESCALATE>> → Claude mit vollem Gedaechtnis). Die Diskretions-
Regel selbst bleibt (funktioniert auf Claude einwandfrei).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EOF
)
2026-07-11 15:51:18 +02:00
duffyduckandClaude Opus 4.8 078ed17b57 fix(privacy): harte Diskretions-Regel — intime Details NIE ungefragt preisgeben
Schwerwiegend: ARIA hat bei Selbstvorstellung / "was weisst du ueber mich"
intime Details (Beziehungen, Partnerinnen, Lebensweise) proaktiv rausgedumpt —
ein Vertrauensbruch, wenn jemand mithoert. ARIA DARF das wissen, aber niemals
ungefragt aussprechen.

Regel in IDENTITY_ANCHOR (steht als einziger garantiert in BEIDEN Tiers, lokal
+ Claude): private/intime Infos sind hochvertraulich; nie von sich aus, nicht in
Vorstellungen/Zusammenfassungen/Triggern; nur auf konkrete Nachfrage, knapp und
diskret. Auf "was weisst du ueber mich": allgemein + diskret antworten, kein
Aufzaehlen. "Jemand koennte mithoeren" als Leitplanke.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 15:38:52 +02:00
duffyduckandClaude Opus 4.8 0a2e59d756 feat(tts): System-Flag speak (ja/nein) pro Antwort statt <voice>-Tag-Hack
Stefans Idee: die QUELLE entscheidet ueber Vorlesen, nicht der Reply-Inhalt.
Fast-Path (reiner Steuerbefehl) = speak=False (stumm); ARIA-Antworten
(local/claude) = speak=True (vorlesen ok — eine Ansage ist sogar nett).

- agent.chat() gibt jetzt (reply, answered_by, speak) zurueck; main.py
  ChatOut.speak; background.py-Aufrufer angepasst.
- bridge: liest speak aus /chat, reicht es an _process_core_response; bei
  speak=False wird TTS uebersprungen — VOR jeder <voice>-Logik.

Robust: unabhaengig davon, ob die Skill-fast_pattern-Reply ein <voice></voice>
enthaelt (das verlor ARIA beim semantischen Skill-Rebuild — genau der Bug).
App braucht keinen Change: kein xtts_request -> kein Audio -> stumm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 14:55:12 +02:00
duffyduckandClaude Opus 4.8 2dfe6fd9c3 feat(local-llm): B0.5-2 — Live-Lade-Status des lokalen Modells in Diagnostic
Adapter meldet bei Modellwechsel/Erst-Load service_status (service=llm):
loading -> ready (mit loadSeconds; freshlyDownloaded 🎉 bei langem Erst-Load)
oder error. Laeuft ueber den vorhandenen Pfad: Adapter -> RVS -> Diagnostic
(RVS-Client) -> Browser -> updateServiceStatus (generisch; nur Label 'Lokales
LLM' ergaenzt). Kein Bridge-/server.js-Change noetig.

Download-% gibt llama-swap nicht her — daher Zustands-Status (laedt/bereit/
Fehler), im gemeinsamen Service-Banner wie whisper/flux.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 14:29:18 +02:00
duffyduckandClaude Opus 4.8 8ae20a9bd8 feat(local-llm): B0.5 — llama-swap + lokale Modellauswahl in Diagnostic
Mehrere lokale Modelle, on-demand geladen/geswappt, in Diagnostic waehlbar.
Design: das Brain schickt den Modellnamen (aus local_llm.json) im llm_request
mit -> Adapter -> llama-swap laedt/swappt. Keine separate Gamebox-Config noetig.

- xtts: `llama`-Container -> `llama-swap` (unified-cuda), config.yaml mit
  qwen3-8b (Standard) + qwen3-4b; Auto-Download via -hf, Cache /models geteilt
  (qwen3-8b schon da). Adapter -> llama-swap:8080, Timeout 600s (Erst-Download).
- adapter: `model` aus dem Request an llama-swap durchreichen (Fallback env).
- brain: router.load_config liest localLlmModel; local_llm_chat(model=...);
  agent gibt cfg-Modell mit; bridge reicht model durch (_local_llm + Route).
- diagnostic: /api/local-models-list (aus /shared/config/local_models.json,
  seeded), local-llm-config um localLlmModel erweitert; Dropdown "Lokales
  Modell" im Settings-Block + Erst-Download-Hinweis.

BLIND gebaut (Gamebox nicht testbar hier): llama-swap CLI/Config-Pfad beim
ersten Start via `docker logs aria-llama-swap` pruefen. Live-Lade-Status
(Adapter->Diagnostic) ist B0.5-2 (Folgeschritt).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 14:11:59 +02:00
duffyduck 3ddcf665f0 release: bump version to 0.2.0.4 v0.2.0.4 2026-07-11 13:48:14 +02:00
duffyduckandClaude Opus 4.8 7ae61701ad feat(app): optionaler Quell-Badge an ARIA-Bubbles (Einstellung, pro Geraet, default aus)
Zeigt wie in Diagnostic, ob eine Antwort vom lokalen Modell / Claude / Fast-Path
kam — aber in der App als Opt-in, damit die App "Mama-tauglich" bleibt.

- ChatScreen: ChatMessage.answeredBy; aus dem chat-Payload UND der History-Sync
  eingefangen; kleiner farbiger Badge im statusRow der ARIA-Bubble, nur wenn
  showSource an. State aus AsyncStorage aria_show_source (default false, per
  2s-Reload synchron mit den Settings).
- SettingsScreen: Toggle "Antwort-Quelle anzeigen" in der Chat-Bubbles-Card,
  schreibt aria_show_source (pro Geraet — Stefan an, Mama aus).
- Bridge liefert answeredBy schon in chat_backup/History mit (kein Change noetig).

Server-Teil in 9cc1aec. APK-Rebuild noetig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 13:46:12 +02:00
duffyduckandClaude Opus 4.8 9cc1aec5ec feat(diagnostic): Quell-Badge an ARIA-Bubbles (local / claude / fast-path)
Zeigt pro Antwort, welcher Backend sie erzeugt hat — farbcodiert (lokal=gruen,
Claude=blau, Fast-Path=lila). Nur in Diagnostic (App bleibt Mama-tauglich).

- agent.chat() gibt jetzt (reply, answered_by) zurueck; an jedem Return-Punkt
  gesetzt (fast-path/local/claude). Beide Aufrufer (main.py, background.py)
  angepasst. main.py: ChatOut.answered_by.
- bridge: liest answered_by aus /chat, reicht es an _process_core_response,
  broadcastet es im chat-Payload UND persistiert es in chat_backup (answeredBy)
  → bleibt nach Reload.
- diagnostic: srcBadgeHtml() rendert den Badge in Live-Chat + History;
  server.js liefert answeredBy in der chat_history.

Erweiterbar: spaeter kann ein Bild-Backend (FLUX/Modellname) denselben Kanal
nutzen. Modellwechsel-fuer-Antworten (groessere Modelle bei mehr GPUs) bleibt
B0.5/Skalierungs-Thema im Plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 13:40:36 +02:00