Der Voice-Projektwechsel ist zurueck, aber nur fuer das ausloesende Geraet:
- App merkt sich die IDs eigener Anfragen (Text-clientMsgId via dispatchWithAck +
Voice-audioRequestId an allen 4 Aufnahme-Stellen) in myRequestIdsRef.
- Bridge haengt an project_changed die ausloesende clientMsgId an — in beiden
Voice-Pfaden: send_to_core (ARIAs project_enter/exit) und _process_endpoint_text
(Voice-Router back_to_main / project_prefix).
- App folgt einem project_changed-Wechsel nur, wenn die clientMsgId eine eigene
ist → andere App-Instanzen + Diagnostic bleiben unberuehrt.
Nur Bridge + App betroffen. py-compile + tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zwei Kopplungs-Bugs behoben:
- set_project_kind nutzte den globalen active_project statt des Request-Projekts
→ markierte das falsche Projekt (belegt: global aktiv war mac_os_update_fehler,
das kind=code bekam, obwohl die App in basic_os war). _dispatch_tool bekommt
jetzt die project_id des Requests durchgereicht; set_project_kind wirkt darauf.
- Die App erzwang bei project_changed-Broadcasts (ARIA-Tool/Diagnostic/andere
App-Instanz) einen Fokuswechsel → alle Geraete wurden mitgezogen. Fokus ist
jetzt rein geraetelokal; Broadcasts aktualisieren nur Namen/Typ. Wechseln nur
noch lokal ueber den Drawer.
py-compile + tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zwei Nachbesserungen am Cockpit-Modus (noch Teil von 0.2.1.9):
- Im Cockpit werden IMMER alle vier Kacheln gezeigt (Chat/Editor/Desktop/
Vorschau) statt erst bei Code-Projekten — Editor/Desktop/Vorschau als
Platzhalter mit Status-Untertitel. Vorher wirkten sie "verschwunden".
- Der "⤢ Uebersicht"-Button hing im Chat genau ueber dem Abbrechen-Button der
"ARIA denkt"-Leiste. Jetzt sitzt er im Navigations-Header links (nur sichtbar
im Cockpit + Fokus), via neuem cockpitNav-Signal-Singleton. In-Content-Button
entfernt.
tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nach dem 0.2.1.8-Deploy sah die App "unveraendert" aus — korrekt, weil der
Hauptchat nur eine Kachel hat (= Vollbild-Chat). Jetzt explizit umschaltbar:
- services/viewMode.ts: 'compact' | 'cockpit', persistiert (aria_view_mode),
Default 'compact' (nichts aendert sich fuer normale Nutzung).
- ViewModeToggle im Navigations-Header (rechts, kollisionsfrei): "⧉ Kompakt" /
"⧉ Cockpit".
- WorkspaceScreen: compact → klassische ChatScreen direkt; cockpit → Canvas.
- Canvas: Uebersicht/Gesten/Back jetzt auch bei einer Kachel erreichbar (der
single-Force-Fokus entfaellt), damit sich Cockpit auch im Hauptchat wie ein
Desktop anfuehlt. "⤢ Uebersicht"-Button nach unten rechts verschoben (weg von
ChatScreens Kopf-Icons).
Changelog: Workspace-Release als 0.2.1.8 gefuehrt, Umschalter als 0.2.1.9.
tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Progressive Reveal: ChatScreen spiegelt bei project_changed den Projekt-Typ
sofort in projectFocus (kind_changed nach set_project_kind) → Editor/Desktop-
Kacheln erscheinen live, nicht erst beim Reconnect.
- useWorkspaceLayout: merkt pro Projekt die zuletzt fokussierte Kachel
(AsyncStorage aria_workspace_layout) und stellt sie beim Zurueckkehren in ein
Code-Projekt wieder her.
tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QEMU-VNC erscheint live in der App-Desktop-Kachel, komplett ueber RVS:
- Bridge: TCP↔RVS-VNC-Bruecke. vnc_open oeffnet asyncio-Verbindung zu
host.docker.internal:5901, Reader-Loop streamt RFB-Bytes als vnc_data;
vnc_input schreibt zurueck; vnc_close raeumt auf. check_desktop probt den
Port und meldet desktop_status. Base64-in-JSON, kein websockify/noVNC auf
dem Host noetig.
- App: novncHtml.ts laedt noVNC (CDN) und ersetzt window.WebSocket durch einen
Shim, der RFB-Bytes per postMessage ueber RVS brueckt (server-speaks-first →
timing-robust). VncTile mountet die WebView nur im Fokus, oeffnet bei 'ready'
den Tunnel (desktop.openVnc), speist Server-Bytes ein und schickt Eingaben
als vnc_input; beim Verlassen wird der Tunnel geschlossen (VM laeuft weiter).
tsc/py clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Durchgaengiger Live-Editor fuer Code-Projekte:
- App: selbstenthaltener Highlight-Editor in einer WebView (editorHtml.ts,
Textarea + Regex-Highlight-Layer, voll offline). CodeEditorTile mit Datei-Tabs,
verdrahtet mit dem codeFile-Spiegel; Bridge-Protokoll setContent/applyPatch/
setReadOnly ↔ onEditFromUser.
- Proxy-Hook (routes.js): faengt ARIAs Write/Edit/MultiEdit unter
/shared/projects/<pid>/ ab und postet den Volltext bei Erfolg an
/internal/code-file. tool_use_id→file_path-Korrelation, liest die Datei aus
dem gemounteten /shared.
- Bridge: /internal/code-file relayt als RVS code_file an die App; eingehende
code_file_edit schreiben den Volltext pfad-sicher nach /shared/projects/<pid>/
(_write_project_file, kein Ausbruch via ..).
Arbeitsverzeichnis fuer Code-Projekte = /shared/projects/<projectId>/ (in proxy/
bridge/brain gemountet, kein SSH noetig). tsc/py/js clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Chat-Tab hostet ab jetzt den Workspace-Canvas (eine ChatScreen-Instanz,
kein Doppel-Mount). Zwei Ebenen:
- Welt-Ebene (reanimated scale/translate): leichte Thumbnail-Kacheln, 2-Finger
Pinch-Zoom + 2-Finger-Pan nur in der Uebersicht (gesture-handler).
- Identity-Content-Ebene (Scale 1): schwere Inhalte (ChatScreen + kommende
WebViews) immer gemountet, nur die fokussierte per display sichtbar → Touch/
Keyboard bleiben korrekt, nichts remountet beim Fokuswechsel.
Tap auf Kachel = Fokus (voll interaktiv), "⤢ Uebersicht"/Hardware-Back = zurueck
zur Landkarte. Bei nur einer Kachel (reiner Chat) ist diese dauerhaft fokussiert
→ verhaelt sich exakt wie der bisherige Vollbild-Chat.
Neue Dateien unter android/src/workspace/: layout.ts (Kamera-Mathe, Center-
Origin fuer RN 0.73), WorkspaceCanvas.tsx, WorkspaceScreen.tsx, Tile.tsx,
tiles/{ChatTile,CodeEditorTile,VncTile,PreviewTile}.tsx. Editor/VNC sind noch
Platzhalter (Commit 5/7). tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verdrahtet die neuen Kanaele "dunkel" (noch ohne UI):
- rvs/server.js: ALLOWED_TYPES um code_file/code_file_edit, check_desktop/
desktop_status und vnc_open/close/data/input erweitert (Base64-in-JSON-Relay
wie audio_pcm, kein Binaer-Handling noetig).
- brainApi.ts: Project.kind ('code'|'chat') + desktop_url; ChatScreen publiziert
die Kinds in den projectFocus-Spiegel.
- services/codeFile.ts: Live-Spiegel der Code-Dateien (content + Deltas) mit
Ruckkanal code_file_edit fuer eigene Edits.
- services/desktop.ts: Desktop-Status + VNC-RFB-Tunnel (vnc_open/close/input/
data) durch RVS, Session = Projekt-ID.
tsc clean, rvs/server.js syntaktisch OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neues Singleton services/projectFocus.ts (Publish/Subscribe wie rvs.ts): haelt
focusedProjectId, Projekt-Namen und Projekt-Kind. ChatScreen publiziert Focus +
Namen EINWEG hinein (rein additive Effekte), damit der kommende Workspace-Canvas
den aktiven Kontext und den Code-Projekt-Status kennt, ohne dass ChatScreen den
Workspace kennt oder umgebaut wird. Verhaltensneutral, tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zwei Ursachen:
- pending_queue-Bubbles haben (noch) keine clientMsgId → der Reconnect-
History-Sync erkannte sie nicht als lokal-only und verwarf sie, waehrend
projectQueues den Eintrag behielt → 'N in Warteschlange' fror ein ohne
sichtbare Nachricht. Jetzt bleiben pending_queue-Bubbles beim Sync erhalten.
- Watchdog: haengt ein Kontext >15s auf 'running', obwohl der Brain ihn NICHT
als busy meldet (Antwort beim Abbruch verloren), schaltet die Queue jetzt
selbst weiter (dequeue oder idle) statt fuer immer zu blockieren.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher war die serialisierte Sprachausgabe zweier fast gleichzeitig fertiger
Antworten Timing-Glueck: PcmStreamPlayer.start() ruft stopInternal() (flush+
release), eine neue Antwort haette die laufende also abgeschnitten, sobald ihr
Audio waehrend der Wiedergabe der ersten ankam.
Jetzt echte Abspiel-Queue im audioService: kommt eine neue HOERBARE Antwort
waehrend eine andere noch hoerbar spielt (pcmAudiblePlaying bis
PcmPlaybackFinished, nicht nur bis Stream-Ende), werden ihre PCM-Chunks
gepuffert und erst nach dem Drain der laufenden nachgespielt. Bei wartender
Antwort meldet PcmPlaybackFinished NICHT 'fertig' (kein Wake-Word-Re-Arm).
Harter Stop/Barge-In/Mute verwirft die Queue. Race gegen gleichzeitige Chunks
einer dritten Antwort geschlossen (Flags vor await gesetzt). onPcmCached meldet
den WAV-Pfad nachgespielter Antworten fuer Mund-Button-Replay.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zweite Nachricht waehrend ARIA arbeitet wird jetzt ANGESTELLT statt den
laufenden Task abzubrechen. Stellt ARIA eine blockierende Rueckfrage, pausiert
die Queue und die naechste Eingabe beantwortet sie — bis eine finale Antwort
kommt, dann laeuft der naechste Queue-Eintrag (pro Projekt unabhaengig).
- Brain: ARIA deklariert Rueckfragen per unsichtbarem [[AWAIT]]-Marker
(wie speak/converse; kein '?'-Raten). _extract_await_marker strippt ihn,
chat() gibt 5-Tupel (+awaiting_reply), System-Prompt erklaert den Marker.
- Bridge: awaiting_reply aus Brain-Response in die chat-Broadcast-Payload.
- App: app-lokale Queue + Zustandsautomat (idle/running/awaiting_reply) pro
Projekt; Send-Flow von Abbruch auf Anstellen; Stop-Button (cancelRequest)
schaltet die Queue weiter; sichtbare pending_queue-Bubbles (tippen loescht);
Rueckfrage-/Queue-Banner ueber dem Eingabefeld. Voice bricht nicht mehr ab
(haltet nur TTS, serialisiert im Brain-Lock); Text-Send erkennt Brain-busy
als Fallback. Pro-Projekt-Textfeld-Entwuerfe (Draft-Map + AsyncStorage).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
colorForScannerFrame existiert in react-native-camera-kit v13 nicht (No-Op) —
entfernt. Die Lib markiert zudem etliche optionale CameraScreen-Props faelschlich
als required (defaultProps fuellen sie zur Laufzeit); Props lokal als any
gespreadet, um die fehlerhaften .d.ts zu umgehen ohne Runtime-Verhalten zu
aendern. scanBarcode/onReadCode bleibt die korrekte v13-Barcode-API.
Projekt jetzt komplett tsc-clean (0 Fehler).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sendTextMessage stand vor interruptAriaIfBusy und sendPendingAttachments,
hatte beide aber im deps-Array → Temporal Dead Zone (TS2448/2454). Lief nur,
weil Babel const→var hebt (deps auf erstem Render undefined, Body laeuft erst
bei Interaktion). Deklaration hinter beide verschoben — Abhaengigkeit ist
einseitig, sendTextMessage wird nur in der JSX genutzt. tsc-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die Live-chat-Payload trug keine files — Anhaenge kamen nur als separates
file_from_aria-Event (eigene, teils unsichtbare Bubble). An der Text-Nachricht
tauchte die Datei erst nach einem Seitenwechsel auf (Reload aus chat_backup,
das die files kennt).
- Bridge: files jetzt direkt in der chat-Payload (selbe Struktur wie Backup).
- App: chat-Handler haengt payload.files an die Text-Bubble (wie der Reload-Pfad)
und entfernt die redundante Solo-file_from_aria-Bubble mit passendem serverPath.
- App: file_from_aria legt keine Doppel-Bubble an, wenn die Datei schon an einer
Nachricht haengt (Event-Reihenfolge-unabhaengig).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
- 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>
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>
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>
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>
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>
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>
#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>
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>
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>
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>
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>