Die Absturzschleife (alle ~70s, stundenlang) war selbsttragend:
App startet → Auto-Re-Download fordert 2s spaeter ALLE fehlenden Anhaenge an,
ohne Groessen-Grenze → Bridge schickt das 150-MB-Video als ~200 MB base64 in
EINER WebSocket-Nachricht → die App muss das puffern und durch JSON.parse
schicken → nativer OOM (kein JS-Fehler, darum nie ein Stacktrace) → Android
startet den Vordergrund-Dienst neu → App startet → von vorn.
Dieselbe Nachricht hat parallel whisper/f5tts/voxtral mit Code 1009 umgebracht
— darum lag der Absturz immer direkt hinter dem Audio-Abbau, und darum kam
jede gerade laufende Sprachaufnahme nie an.
Drei Bremsen:
- Auto-Re-Download ueberspringt alles ueber 25 MB. Bleibt als "150 MB —
tippen zum Laden" stehen; der User entscheidet.
- Ausdrueckliches Antippen bremst bei 64 MB mit einer ehrlichen Meldung samt
Server-Pfad, statt die App zu killen.
- Der Brotkrumen wird von der Crash-Meldung nicht mehr ueberschrieben. Bisher
merkte sich der zweite Absturz nur noch "letzte Aktivitaet: Crash-Meldung" —
in einer Schleife war nach dem ersten Durchlauf jede Spur weg.
Diagnose kam aus /shared/logs/app.log (app_log via RVS), kein ADB noetig.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sat_hello und worker_hello kommen im Takt von ~25-30s erneut und wurden jedes
Mal mit "online" geloggt. Dadurch war das Bridge-Log praktisch unlesbar: die
interessanten Zeilen (Fehler, STT, Turns) gingen zwischen hunderten
identischen Meldungen unter.
Jetzt wird nur geloggt, wenn eine Box/ein Satellit NEU ist oder sich etwas
Echtes geaendert hat (Modell, GPUs, Standort, caps, control) — so wie es
host_hello schon vorher gemacht hat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der RVS ist ein Broadcast-Relay — bisher bekam jeder Client im Raum jede
Nachricht. Bei einem Datei-Re-Download war das fatal: ein 150-MB-Video wird
als base64 ~200 MB gross und ging an ALLE Clients, auch an die STT/TTS-Worker.
Die sind daran mit Code 1009 (message too big) gestorben und haben die gerade
laufende Sprachaufnahme mitgerissen — die Ursache von "erste Sprachnachricht
kommt nicht durch".
Wer eine Anfrage mit requestId stellt, wird jetzt gemerkt; die Antwort geht
nur an ihn zurueck (file_response, file_zip_response,
file_version_download_response, brain_response). Unbekannte requestId wird
wie bisher gebroadcastet, ein weggefallener Fragender verworfen statt an alle
geschickt.
Verifiziert mit drei WS-Clients gegen einen lokalen RVS: der Fragende bekommt
seine Antwort, der Unbeteiligte nicht, chat bleibt Broadcast.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ursache der staendigen whisper/f5tts-Neustarts, direkt aus dem Box-Log:
sent 1009 (message too big) frame with 209221780 bytes
exceeds limit of 52428800 bytes
Der RVS ist ein BROADCAST-Relay: eine grosse Nachricht (Datei-Transfer,
Voice-Upload, APK-Update) geht an JEDEN Client im Raum. Die aria-bridge nutzt
deshalb max_size=1500MB — die Worker standen auf 50MB (llm-adapter sogar 16MB).
Ein 209-MB-Frame liess sie die Verbindung mit Code 1009 schliessen; danach
TLS-Fallback auf ws:// (scheitert an Caddy), Reconnect, naechster Frame, von
vorn. Ergebnis: Neustart im Minutentakt, und jede laufende Aufnahme war weg —
das ist die eigentliche Ursache fuer 'erste Sprachnachricht kommt nicht durch'.
Alle vier Worker nutzen jetzt RVS_MAX_SIZE_MB (Default 1500), passend zu
RVS/Bridge. Die GPU war NICHT das Problem (4,3 von 8 GB belegt, zweite Karte leer).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Beweis aus app.log 02.10 01:22: beim Aufnehmen fehlte 'bg.start slot=rec' —
die Aufnahme wurde abgelehnt, BEVOR sie begann. Ursache ist die in 10e7faa
eingefuehrte Sperre 'ohne Verbindung nicht aufnehmen': sie prueft den gemerkten
Zustand, und der steht wegen des Zombie-Bugs faelschlich auf 'disconnected',
obwohl die Verbindung laeuft (TTS kam ja an). Ergebnis: Stefan konnte gar nicht
mehr sprechen — die Sperre hat das Problem verschlimmert statt es zu verhindern.
rvs.isSocketOpen() fragt jetzt ws.readyState direkt; audio.ts nutzt das. Damit
ist die Sperre immun gegen falsche Zustandsmeldungen und blockt nur, wenn der
Socket wirklich zu ist. tsc: 0 Fehler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'Teilen -> ARIA' aus WhatsApp, Galerie, Browser & Co. Ablauf wie gewuenscht:
sehen WAS geteilt wurde -> WOHIN (Projekt oder Hauptchat) -> optional Text dazu.
Ohne Text geht genauso.
- Manifest: intent-filter fuer ACTION_SEND (text/image/video/audio/application)
und ACTION_SEND_MULTIPLE. launchMode=singleTask war bereits gesetzt.
- ShareIntentModule (Kotlin): liest den Intent und kopiert die Inhalte SOFORT in
den App-Cache — die Leseerlaubnis fuer content://-URIs gilt nur fuer diesen
Intent und waere nach der Projektauswahl womoeglich erloschen. Liefert echte
Dateipfade, dadurch laeuft Teilen durch exakt dieselbe Upload-Strecke wie die
Bueroklammer (RNFS -> Base64 -> Anhang im Chat).
- MainActivity.onNewIntent: setIntent + Event an JS (best effort). Zweitweg in
JS ueber AppState 'active' -> getSharedData(), falls das Event je nach
RN-Architektur nicht durchkommt; getSharedData markiert den Intent als
verbraucht, also kein doppeltes Aufpoppen.
- ShareModal: Vorschau der geteilten Inhalte, Projektliste (inkl. Hauptchat),
optionales Textfeld. Senden erst nach Zielwahl moeglich.
- sendPendingAttachments nimmt die Anhaenge jetzt optional als Parameter — beim
Teilen waere der State zum Aufrufzeitpunkt noch leer.
tsc --noEmit: 0 Fehler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stefan: 'die antwort kam auf der app, aber meine frage ist nicht in der app'.
Die App zeigt Diagnostic-Nachrichten durchaus an (sender=diagnostic -> als
User-Bubble), legte sie aber OHNE projectId ab. Die Chat-Ansicht filtert nach
Projekt (ChatScreen.tsx:2078) — damit war die Nachricht nur im Hauptchat
sichtbar und in jedem Projekt unsichtbar. ARIAs Antwort trug die projectId und
erschien korrekt; nur die eigene Frage fehlte. Jetzt wird die mitgeschickte
projectId uebernommen. tsc: 0 Fehler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Symptom (live beobachtet): die App steht auf 'getrennt' und nichts, was Stefan
schreibt oder spricht, erreicht den Server — ARIAs Antwort und TTS kommen aber
trotzdem in der App an.
Ursache: Beim Verwerfen einer Verbindung wurden nur onclose und onerror
entfernt, NICHT onmessage. Die aufgegebene Verbindung lieferte deshalb weiter
Nachrichten an die App-Listener (daher die ankommende Antwort), waehrend this.ws
auf die neue/tote zeigte — send() lehnte ab und der Zustand blieb
'disconnected'. Betraf beide Abriss-Pfade (Neu-Connect und TLS-Fallback).
Fix: detachSocket() klemmt eine Verbindung vollstaendig ab (onopen/onmessage/
onclose/onerror + close) und wird in beiden Pfaden genutzt. Zusaetzlich ignoriert
der onmessage-Handler Nachrichten, die nicht von der AKTUELLEN Verbindung
stammen — sonst taeuscht ein Zombie weiter Betrieb vor. tsc: 0 Fehler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Beweis aus den Logs: die Whisper-Bridge startete MITTEN in der ersten Aufnahme
neu (stream.start 16:35:43 -> boot 16:36:02), damit war die Sitzung weg. Die App
fiel in den 3s-Fallback (reason=...no-response) und entfernte die Bubble
kommentarlos — fuer den Nutzer sah es aus, als waere nie etwas gesendet worden.
Jetzt wird bei 'no-response' die Platzhalter-Bubble durch einen Hinweis ersetzt
('Aufnahme ging verloren — Spracherkennung war nicht erreichbar'). Der stille
no-speech-Fall (Du hast wirklich nichts gesagt) bleibt wie bisher. tsc: 0 Fehler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Das Profil existierte bereits in der compose-Datei, tauchte aber nicht in der
Profil-Liste der .env.example auf — man konnte also nicht sehen, dass es waehlbar
(und per Default AUS) ist. Jetzt dort gelistet, mit dem Hinweis, dass ohne
Eintrag gar kein Container angelegt wird, plus Warnung, dass Profile compose v2
brauchen (das alte docker-compose 1.x behandelt sie unzuverlaessig).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Host-Socket bleibt (bewusste Entscheidung fuer den Heimbetrieb). Dafuer der
wirksamste kostenlose Schutz explizit dokumentiert: OPENHANDS_BIND bleibt auf
127.0.0.1, Zugriff von anderen Rechnern per 'ssh -L 3000:127.0.0.1:3000'. Dazu
der Hinweis, was echte Isolation waere (eigene VM / rootless Daemon).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
llama-swap ist bereits OpenAI-kompatibel, war aber nur im Compose-Netz erreichbar
(kein ports-Mapping). Jetzt nutzbar fuer OpenHands, Continue, Open WebUI & Co.
- compose: llama-swap veroeffentlicht 8080 als
${LLM_OPENAI_BIND:-127.0.0.1}:${LLM_OPENAI_PORT:-8081} — sicher per Default
(nur localhost), fuer LAN bewusst BIND=0.0.0.0 setzen.
- llm-adapter: API-Key wird in JEDEN llama-server-Befehl der generierten
llama-swap-Config eingehaengt (--api-key, shlex-gequotet). Quelle: .env
(Vorrang) sonst /models/openai_key.json (0600). Neuer Handler
llm_openai_config setzt ihn zur Laufzeit und lehnt bei .env-Vorrang ab.
worker_hello meldet Port/Bind/keySet/envLocked — den Key NIE (Broadcast!).
- Diagnostic: pro LLM-Box Endpunkt-Zeile mit Base-URL, Key-Status und Eingabe.
Bei .env-Vorrang ist das Feld schreibgeschuetzt (🔒). Warnung, wenn im LAN
offen ohne Key. Der PORT wird nur ANGEZEIGT: er ist ein Docker-Port-Mapping
und steht beim Container-Start fest — dafuer ist die .env zustaendig.
- rvs: llm_openai_config(_result) in ALLOWED_TYPES (sonst still verworfen).
- Neues Profil 'openhands' (COMPOSE_PROFILES=llm,openhands) mit Port/Bind/Modell
aus der .env, lokales LLM vorbelegt. Ohne Profil aendert sich nichts.
- .env.example + README: alle Variablen, Sicherheitshinweise (offener Endpunkt,
Docker-Socket bei OpenHands), curl-Test und wann der Key greift.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nachgeprueft: der Selbstheilungs-Fix lief korrekt — die Wiedergabe startete auf
Anhieb (Nachkontrolle positiv), es gab KEINEN App-Neustart (kein host_app_launch
in der Agent-Historie). ARIA behauptete trotzdem 'Session war eingeschlafen, ich
hab Jellyfin neu gestartet' — nacherzaehlt aus dem vorherigen Turn, wo das
tatsaechlich noetig war.
Die Erfolgsmeldung stellt jetzt klar, dass nichts repariert werden musste, damit
das Modell die alte Panne nicht weitererzaehlt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Beim zweiten Film ging nichts mehr ('Session abgelaufen') — ARIA musste Jellyfin
von Hand neu starten. Ursache: Jellyfin listet Clients noch, wenn die App laengst
weg ist, und quittiert den Play-Befehl auch dann mit Erfolg. Es lief nur nichts.
- /Sessions wird jetzt mit activeWithinSeconds gefiltert (Play + devices) —
Karteileichen tauchen gar nicht erst als Ziel auf.
- _jellyfin_playing(): nach dem Play-Befehl wird nachgesehen, ob NowPlayingItem
wirklich gesetzt ist (bis ~6s). Ohne das meldet man faelschlich Erfolg.
- Laeuft nichts und ist 'host' gesetzt: Jellyfin wird ueber den Host-Agenten neu
gestartet (host_app_launch), frische Sitzung geholt, einmal nachgelegt — also
genau der Handgriff, den ARIA vorher manuell machen musste. Ohne 'host' gibt es
eine klare Handlungsanweisung statt eines stillen Fehlschlags.
- Tool-Beschreibung draengt auf 'host', damit die Selbstheilung greift.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drei App-seitige Ursachen fuer 'erst beim zweiten Aufnehmen' (tsc --noEmit: 0 Fehler):
1) No-Speech-Watchdog: Der Aufrufer uebergibt die Sprechpausen-Toleranz
(sttEndpointMs, Default 2400ms, per Setting bis 1000ms). Die misst ab dem
ENDE des Sprechens, der Watchdog lief aber ab dem START der Aufnahme. Bis ein
Zwischenergebnis zurueck sein KANN, vergehen realistisch 1,3-2,2s (600ms
Mindestpuffer + 700ms Transkriptions-Takt + Netzweg). Schon kurzes Ueberlegen
nach dem Gong brach die Aufnahme ab. Jetzt +2500ms Zuschlag.
2) stt_stream_start ging ERST NACH PcmStreamRecorder.start() raus — die ersten
Chunks (200ms-Takt) trafen auf eine Bridge ohne Sitzung und wurden verworfen
(Wortanfang weg; bei kurzen Kommandos blieb nichts uebrig). Jetzt davor.
3) recordingState wurde erst nach mehreren awaits auf 'recording' gesetzt. In
diesem Fenster war isStreamingRecording() true, der Zustand aber 'idle': ein
Abbruch dort liess den Start weiterlaufen, der Recorder blieb haengen und die
NAECHSTE Aufnahme wurde abgelehnt. Zustand wird jetzt sofort gesetzt, plus
Session-Guard nach den awaits.
Zusaetzlich: ohne RVS-Verbindung wird gar nicht erst aufgenommen (send()
verwirft still, kein Puffer) — nach App-Resume fiel sonst die erste Aufnahme
komplett aus. Nachtraeglich gepuffertes Audio waere sinnlos, die STT-Sitzung
laeuft in Echtzeit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vier unabhaengige Ursachen fuer 'erst beim zweiten Aufnehmen' bzw. 'wird gar
nicht verarbeitet' (server-/box-seitig, ohne App-Update wirksam):
1) rvs/server.js: stt_lease, stt_lease_request, stt_transcribe_blob,
stt_transcribe_result und chat_ack fehlten in ALLOWED_TYPES und wurden STILL
verworfen (dieselbe Falle wie bei worker_*/host_*). Folgen: das Lease lief
immer in den 250ms-Timeout -> jede Aufnahme startete verspaetet und OHNE
targetInstance nahmen alle STT-Worker dieselbe Session an (doppelte
stt_endpoint); chat_ack kam nie an -> getippte Nachrichten galten in der App
nach 3 Versuchen als fehlgeschlagen, obwohl sie verarbeitet waren.
2) xtts/whisper/bridge.py: Beim Kaltstart wurde die Session erst NACH dem
Modell-Load angelegt — alle Chunks der ersten Aufnahme liefen ins Leere und
der Take war weg (die zweite klappte, weil das Modell dann warm war). Session
wird jetzt sofort angelegt, der Endpointer wartet solange (runner.model None).
3) bridge/aria_bridge.py: Text-Dedup-Fenster 5s -> 1.5s. Es soll nur den
Zwilling zweier Worker abfangen; mit 5s fielen echte Wiederholungen weg —
also genau das Nachsprechen nach einem verschluckten Take, und kurze
Kommandos ('lauter', 'nächstes').
4) bridge/aria_bridge.py Voice-Router: Komma als Prefix-Trenner entfernt (nur
noch ':' und '-'), Fuzzy-Schwelle 0.6 -> 0.82, Sticky wird nicht mehr bei
jeder Nachricht verlaengert. Vorher matchten normale Saetze wie 'In Ordnung,
mach mal ...' als Projekt-Prefix: die Nachricht landete still in einem
Projekt und war im Hauptchat unsichtbar — verifiziert mit Beispielsaetzen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gefunden ueber die Brain-Logs: mehrere Nachrichten blieben unbeantwortet, weil
POST /chat mit 502 endete. Ursache:
Proxy HTTP 500: {"error":{"message":"spawn E2BIG"}}
execve() begrenzt argv+envp ZUSAMMEN auf ARG_MAX — im Proxy-Container nur
131072 Bytes (getconf ARG_MAX). Der System-Prompt wurde als einzelnes Argument
uebergeben und war im Fehlerfall 76881 Zeichen gross (gepinnte Erinnerungen +
Skills + Projektkontext) -> Start scheitert, Turn verloren, App bekommt 502.
11 solcher Faelle im aktuellen Log.
Fix: --system-prompt-file <tmpfile> statt --system-prompt <text> (die CLI
unterstuetzt das). Damit faellt die Groessengrenze praktisch weg. Die Temp-Datei
wird bei close/error/catch wieder entfernt; schlaegt das Schreiben fehl, wird
wie bisher das Argument genutzt (kleine Prompts laufen weiter).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Agent meldete 'gesprochen', es kam aber kein Ton: Fire TV Sticks haben in
der Regel keine (deutsche) TTS-Engine, und wir haben weder setLanguage noch den
Rueckgabewert von speak() geprueft -> stiller Fehlschlag.
- speak/notify fordern jetzt ARIAs ECHTE Stimme ueber den bestehenden Serverpfad
an: tts_request -> Bridge -> F5/XTTS -> audio_pcm, das der Agent abspielt.
Keine Server-Aenderung noetig (beide Typen stehen in der RVS-Allow-List).
VoiceSession merkt sich die angeforderte messageId und spielt genau dieses
Audio ab — die Turn-Ownership bleibt sonst unveraendert (Handy redet nicht mit).
- Geraete-TTS nur noch auf ausdrueckliche Anforderung (engine='device') und nun
ehrlich: fehlende Engine, fehlende deutsche Sprachdaten und ein abgelehntes
speak() werden als Fehler gemeldet statt als Erfolg.
Android-Version 0.0.1.6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Statt sich durch die Fire-TV-Oberflaeche zu klicken (fehleranfaellig: ARIA ist am
Titel vorbeigescrollt und auf Home gelandet) spricht ARIA den Jellyfin-Server
direkt an: suchen -> Sitzung des Abspielgeraets finden -> abspielen.
- Diagnostic (Einstellungen > Integrationen): Jellyfin-Abschnitt mit MEHREREN
Servern (+-Button; eigener, vom Kumpel ...), einer davon Standard. Pro Server
Name/URL/API-Key + Test-Knopf (prueft /System/Info, meldet Name und Version).
Gespeichert in /shared/config/jellyfin.json mit Datei-Rechten 0600 (API-Keys).
- Brain: jellyfin_search / jellyfin_devices / jellyfin_play / jellyfin_control.
Serverwahl per Name oder Standard. 'resume' ist echt implementiert (liest
PlaybackPositionTicks und setzt startPositionTicks) statt nur zu existieren.
Fehlertexte sind handlungsleitend: 401 -> API-Key pruefen; kein Geraet -> die
Jellyfin-App muss offen sein, sonst erst host_app_launch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher stand im Namensfeld die technische Kennung aus der Netz-Erkennung
(F8FCE10B4E6C@AS-AFTKA[AirPlay]) — auch wenn das Geraet laengst 'Fire-TV
Schlafzimmer' heisst. Jetzt in dieser Reihenfolge:
1. Name, den der Agent auf DIESEM Geraet traegt — Zuordnung ueber die IP, die
der Agent neu in host_hello mitmeldet (localIp()); Server reicht sie durch.
2. zuletzt hier eingegebener Name (localStorage je Satellit+IP).
3. Name aus der Erkennung, aber nur wenn menschenlesbar — Seriennummern, IPs
und Kennungen mit @/[]/: werden verworfen (Feld bleibt leer).
Android-Version 0.0.1.5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher wurde KEIN Audio-Fokus angefordert: Android mischt dann beide Tonspuren,
d.h. Netflix/Jellyfin/Spotify liefen in voller Lautstaerke weiter und ARIA war
kaum zu verstehen — und beim Zuhoeren nahm das Mikrofon den Filmton mit auf.
AudioFocusHelper mit drei Modi:
- speak/notify: AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ('duck', Standard) -> Medien
werden leiser und danach automatisch wieder lauter. Per focus-Param auch
'pause' (Medien anhalten) oder 'none'.
- listen: AUDIOFOCUS_GAIN_TRANSIENT ('pause') -> Medien halten an, solange das
Mikrofon offen ist; danach laufen sie weiter.
- Fokus wird in allen Pfaden zurueckgegeben (Completion, Fehler, stop, shutdown),
sonst blieben die Medien dauerhaft leise.
Brain: focus-Parameter in host_notify/host_speak. Android-Version 0.0.1.4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die BERECHTIGUNGEN bleiben dauerhaft (pm grant / appops in appops.xml), die
MediaProjection-SITZUNG aber nicht — Android laesst sie nicht speichern. Nach
Reboot/App-Neustart war der Agent also verbunden, konnte aber keinen Screenshot
machen, bis jemand am Fernseher 'Bildschirm-Zugriff erlauben' drueckt.
- CaptureActivity: unsichtbarer Helfer (Theme.Translucent, noHistory,
excludeFromRecents), fordert die Projektion an und reicht das Ergebnis an den
Dienst. Mit gesetztem AppOp PROJECT_MEDIA laeuft das OHNE Dialog durch.
- AgentService startet sie beim Dienststart automatisch nach (2,5s Verzoegerung),
aber nur wenn captureEnabled gesetzt ist — wer die Aufnahme nie wollte, bekommt
auch keinen Dialog.
- AgentConfig.captureEnabled: wird gesetzt, sobald die Aufnahme einmal lief, und
kann beim Fern-Einrichten mitgegeben werden (nur wenn der Satellit PROJECT_MEDIA
wirklich erteilen konnte).
Android-Version 0.0.1.3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1) ARIA hatte KEIN Werkzeug fuer die Einblendung — der Agent kann notify/speak/
volume laengst, aber im Brain fehlten die Tools. Sie hat deshalb improvisiert
('cmd notification post', gibt es auf Android 9 nicht) und ist gescheitert.
Neu: host_notify (Overlay/Sprache, mode ueberschreibbar), host_speak,
host_volume — mit dem ausdruecklichen Hinweis, es NICHT ueber adb zu versuchen.
2) _adb_grant_permissions: appops PROJECT_MEDIA (Screenshots ohne Dialog) +
SYSTEM_ALERT_WINDOW (Overlay) + pm grant RECORD_AUDIO (Mikro) werden beim
Einrichten automatisch gesetzt — kein Zusammenklicken mit der Fernbedienung
mehr. Best effort; was scheitert, wird als 'manuell noetig' gemeldet. Neue
Aktion adb.permissions setzt sie einzeln nach.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Android vergleicht beim Activity-Start Komponente/Action/Daten, aber NICHT die
Extras. Ein zweites Provisioning galt damit als 'derselbe Intent': die Activity
wurde nur nach vorn geholt, onCreate/onNewIntent liefen nie — der alte Name blieb
stehen (und kam nach dem Loeschen aus der Liste sofort wieder).
- Satellit: eindeutige Daten-URI (aria://provision/<ms>) je Aufruf -> der Intent
ist nie 'gleich'. Zusaetzlich wird die am-start-Ausgabe auf 'brought to the
front'/'not started' geprueft und als Fehler gemeldet statt als Erfolg.
- Android: MainActivity launchMode=singleTop -> erneutes Provisioning landet
zuverlaessig in onNewIntent (statt neuer Instanz); setIntent() ergaenzt.
Android-Version 0.0.1.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1) 'adb shell' haengt seine Argumente zu EINER Kommandozeile zusammen, die auf
dem Geraet nochmal von der Shell zerlegt wird — '-e name Wohnzimmer Fire TV'
kam als 'Wohnzimmer' an, der Rest fiel weg. Alle Werte werden jetzt mit
shlex.quote gequotet.
2) Umbenennen erzeugt eine neue hostId, der alte Eintrag blieb fuer immer in der
Diagnostic stehen (nach 5 min nur rot, nie weg). Neuer ✕-Button je Agent
(host_forget) mit Rueckfrage; laeuft der Agent noch, meldet er sich von selbst
wieder — das Entfernen stoppt ihn also nicht.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fire TV meldet sich in der Netz-Erkennung oft nur mit der Seriennummer
(0d94400fa5b63301) — genau der Name, den man auf dem Fernseher nicht tippen will.
- Namensfeld im Rollout-Dialog (am PC getippt), wird beim Einrichten mitgegeben.
- Vorbelegung filtert technische Namen (Hex-Seriennummern, IPs) raus.
- Neuer Button 'Nur einrichten': setzt Zugang + Namen per adb.provision, OHNE die
APK neu zu installieren — zum Umbenennen bereits ausgerollter Geraete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Am Fire TV gibt es keine Kamera (kein QR) und Host/Token mit der Fernbedienung
einzugeben ist eine Qual. Der Satellit hat aber ohnehin schon eine ADB-Verbindung
— darueber bekommt der Agent seine Zugangsdaten direkt.
- Android: MainActivity.applyProvisioning() liest Intent-Extras (host/port/tls/
token/name/control/reply_mode), speichert sie, startet den Dienst und zeigt
'Von ARIA eingerichtet' (Transparenz). Auch via onNewIntent.
- Satellit: adb.provision (am start mit Extras) und adb.status (installiert?
laeuft? welche Version?). adb.install richtet direkt mit ein, wenn rvs_host/
rvs_token mitkommen -> ein Klick = installieren + einrichten + online.
- Diagnostic: Checkbox 'Gleich einrichten' (default an) + 'Status pruefen'-Button.
Der RVS-Zugang wird SERVERSEITIG ergaenzt (params.provision) — das Token steht
nie im Browser-Formular.
- Allowlists (Code + .env.example) und README ergaenzt, inkl. Sicherheitshinweis.
Android-Version 0.0.1.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NET bekommt ein 'mac'-Feld (_own_mac): liest /sys/class/net des Interfaces, das
zur primaeren IP gehoert — wichtig, weil sonst die Docker-Bridge-MAC (02:42:...)
statt der echten Karte kaeme. Fallback uuid.getnode(). Wird ueber sat_hello.net
mitgeschickt (Server reicht net bereits durch) und in der Satelliten-Zeile hinter
der IP angezeigt; auch im Startup-Log.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug der alten Python-Version beim Ersetzen eines Containers mit Volume:
get_container_data_volumes liest image_config['ContainerConfig'], das neuere
Docker-Versionen nicht mehr schreiben. Fix: 'docker compose' (v2) nutzen oder
den alten Container vorher mit 'rm -sf' entfernen (dann greift der create- statt
recreate-Pfad). Trat schon beim frueheren Satelliten-Build auf — jetzt dokumentiert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher: 1. Klick -> 'unauthorized' (Fehler), am Fire TV bestaetigen, 2. Klick ->
klappt. Jetzt wartet der Satellit nach dem ersten connect bis zu ADB_AUTH_WAIT_SEC
(90s) auf die Autorisierung und installiert danach automatisch weiter — der
Dialog auf dem Fernseher erscheint ja erst DURCH das connect.
- _adb_wait_authorized(): pollt adb devices alle 2s und verbindet neu (nach dem
Bestaetigen wechselt der Zustand oft erst mit frischem connect auf 'device').
- adb.install/adb.connect nutzen es; scheitert es doch, nennt die Meldung Wartezeit
und Zustand statt nur 'unauthorized'.
- Brain-Timeout fuer adb.* auf 320s (Warten + Download + Installation).
- Diagnostic: Hinweis erscheint SOFORT beim Klick ('Jetzt auf den Bildschirm des
Geraets schauen'), nicht erst als Fehler hinterher.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Satellit kann Android-Geraete im LAN erkennen und die Agent-APK ausrollen,
statt sie von Hand aufs Geraet zu kopieren.
- satellite.py: adb.devices/connect/install/shell. _apk_fetch laedt die APK per
HTTP (gecached in /data/apk, sha256 optional) statt sie durchs RVS zu schicken —
der RVS ist ein BROADCAST-Relay (jede base64-APK ginge an alle Clients, und das
Satelliten-max_size liegt bei 8 MB). adb.install ist selbstheilend (connect ->
install -r) und uebersetzt die typischen Fehler in klare Ansagen (unauthorized,
Signatur-Mismatch, Speicher voll). Imports lokal in den Funktionen (Hausmuster).
- Discovery: mDNS-Typen _amzn-wplay/_adb-tls-connect/_androidtvremote2, _mdns_kind
-> firetv/android-adb/androidtv, plus _enrich_adb (TCP-Probe 5555) -> adbCapable.
- Dockerfile: adb; compose: HOME=/data (ADB-Key persistent, sonst fragt das Geraet
nach jedem Rebuild neu) + ANDROID_ADB_SERVER_PORT=5038 (Host-Kollision).
- .env.example: neue Aktionen + Warnung, dass die Allowlist den Code-Default
ueberschreibt (sonst lehnt ein bestehender Satellit adb.* ab).
- Brain: adb.* in der satellite_command-Beschreibung; WICHTIG Timeout 200s fuer
adb.*/ssh.exec — der Bridge-Default von 20s haette jede Installation abgewuergt.
- Diagnostic: sat_command (Browser->RVS) und sat_result (RVS->Browser) gab es
bisher GAR NICHT; beide ergaenzt. 📦-Button je ADB-faehigem Geraet + Rollout-
Modal mit Status/Log, ADB-Tag in der Geraeteliste.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Stick wird vom gesteuerten Geraet zum Gespraechspartner:
- VoiceSession: Mikro -> stt_stream_start/stt_audio_chunk(pcm,16k mono s16le,
6400B)/stt_stream_end (exakt das App-Protokoll, keine Server-Aenderung noetig);
Antwort via chat(sender=aria) + audio_pcm(base64, 24k) in AudioTrack. AEC/NS an.
TURN-OWNERSHIP: der RVS broadcastet an alle — es wird nur ausgegeben, was aus der
EIGENEN Frage entstand (audioRequestId -> messageId), sonst reden Handy und Stick
gleichzeitig.
- OverlayView: TYPE_APPLICATION_OVERLAY, Einblendung ueber dem laufenden Bild
(Text + optional Bild), nicht fokussierbar (Fernbedienung bleibt frei).
- Speaker: ARIAs echte Stimme (fertiges Audio) bevorzugt, Geraete-TTS als Fallback,
Lautstaerke-Steuerung.
- Neue Aktionen: notify (Einblendung/Sprache, mode ueberschreibbar), speak, volume,
listen/listen_stop.
- Einstellung replyMode (overlay|speech) in der App, pro Anfrage ueberschreibbar.
- Sprech-Taste in der UI (Fire TV: OK-Taste) + Mikro-/Overlay-Berechtigungen.
Version 0.0.1.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kein zweiter Agent: dieselbe APK laeuft auf Fire TV, Fähigkeiten capability-gated
(wie beim Desktop-Agent).
- Manifest: touchscreen/faketouch/leanback als required=false (sonst gilt die App
als Touch-Pflicht bzw. laeuft nicht mehr aufs Handy), LEANBACK_LAUNCHER (ohne
das fehlt die App im Fire-TV-Menue), TV-Banner (Launcher zeigt Banner statt Icon).
- RvsClient: isTv-Erkennung (leanback/television/UiModeManager) -> caps bekommen
'tv', os meldet 'Fire TV / Android TV'.
- AriaAccessibilityService.globalKey: D-Pad (up/down/left/right/ok) via Fokus-
Navigation, Medientasten via dispatchMediaKeyEvent, Lautstaerke/Mute via
AudioManager — deutsche Aliase (hoch/runter/lauter/stumm).
- MainActivity/Layout: auf TV QR-Scan ausgeblendet (keine Kamera), erster Fokus
gesetzt, Buttons fokussierbar (Fernbedienung).
Version 0.0.0.8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Desktop-Agenten (Win/Linux/Mac) koennen jetzt bedienen, nicht nur sehen — mit
DENSELBEN Actions/Brain-Tools wie Android (ui_tap/ui_text/ui_key/ui_swipe/
app_launch), ein Werkzeugset fuer Handy und Rechner.
- host_agent.py: _gui_input_method() erkennt X11(xdotool)/Wayland(ydotool)/
macOS(osascript+cliclick)/Windows(PowerShell). CAPS werden dynamisch erweitert
-> reiner Terminal-Server (kein DISPLAY) meldet KEINE ui_*-Caps. _do_ui_tap/
text/key/swipe + _do_app_launch je Methode; info liefert 'gui'.
- Brain: Tool-Beschreibungen decken Desktop ab (Koordinaten aus dem Screenshot,
kein ui_dump; button/double bei tap; command bei app_launch); _UI_ACTIONS
reicht die neuen Params durch.
- README: Fähigkeiten/Plattform-Tabelle + Helfer je OS + HiDPI-Hinweis.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Erkenntnis aus dem Live-Test: bei gesperrtem Bildschirm greifen Gesten/Tasten
nicht (Android-Sicherheit), der Agent meldete aber still 'ok' -> Verwirrung
(home,home,home, Screenshot zeigt trotzdem alte App). Fix: KeyguardManager.
isKeyguardLocked() gate fuer Steuer-Aktionen (ui_tap/text/swipe/key/app_launch)
-> klare Meldung 'Geraet ist gesperrt - bitte entsperren'. Sehen (screenshot/
ui_dump/info) bleibt auch gesperrt erlaubt. README-Hinweis ergaenzt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Jede Host-Aktion wird als Historie-Eintrag festgehalten und in der Diagnostic
pro User-Nachricht gruppiert aufklappbar dargestellt (mit Screenshot-Thumbnails).
- Brain: trace_id+msg pro chat(); _dispatch_host als Wrapper um _dispatch_host_inner
emittiert je Aktion einen Eintrag an POST /internal/agent-history (Datei-Pfad aus
Screenshot-Text erkannt).
- Bridge: /internal/agent-history persistiert nach /shared/config/agent_history/
<hostId>.jsonl (Ringpuffer 200) + broadcastet agent_history; agent_history_query
-> agent_history_list.
- RVS: agent_history/_query/_list in ALLOWED_TYPES (sonst still verworfen).
- Diagnostic-Server: relay browser<->RVS + /uploads/<file> Bild-Endpoint (nur
/shared/uploads, kein Traversal).
- index.html: 'Historie'-Button je Host, Modal mit Akkordeon (Gruppe=Nachricht,
aufklappbar, Screenshot-Thumbs), Live-Refresh bei agent_history.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Geraet (Android 12) meldete beim Projizieren:
'Media projections require a foreground service ...
FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION'. Der Dienst lief als reiner dataSync.
Fix: foregroundServiceType=dataSync|mediaProjection + Permissions
FOREGROUND_SERVICE_MEDIA_PROJECTION/-DATA_SYNC; beim Projektions-Start
startForeground(..., MEDIA_PROJECTION|DATA_SYNC) VOR getMediaProjection().
Normale Starts nutzen explizit nur dataSync (Android-14-konform). Version 0.0.0.5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>