Commit Graph
1186 Commits
Author SHA1 Message Date
duffyduckandClaude Opus 5.5 60f2d2e087 feat(diagnostic): pro LLM-Karte ein Dropdown — und Kontextfenster an Clients melden
Einstellungen → Modelle & KI → "LLM-Karten": fuer jede Karte, die auf einer
Box fuers LLM vorgesehen ist, ein Dropdown mit den Katalog-Modellen und
"— leer —" (entlaedt die Karte). Nur echte LLM-Karten erscheinen. Modelle,
die auf derselben Box schon auf einer anderen Karte liegen, werden nicht
angeboten; zu grosse (Katalog-Groesse > VRAM der Karte) sind als "passt
nicht" markiert. Waehrend eines Ladevorgangs ist das Dropdown gesperrt und
zeigt den Fortschritt.

Box meldet dafuer pro Karte den VRAM (nvidia-smi memory.total) und das
Kontextfenster, mit dem llama-server das Modell startet (-c).

Kontext an Clients: llm_models enthaelt jetzt "ctx". Anlass: die Software-
Schmiede rechnete mit 32768 Tokens, die Box startet die Modelle mit 8192 —
lange Auftraege waeren gescheitert oder falsch gekuerzt worden, und der Client
konnte es nicht wissen. Doku entsprechend ergaenzt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 01:22:06 +02:00
duffyduckandClaude Opus 5.5 a1aa90177d fix(diagnostic): Status-Meldung unten rechts immer wegklickbar
Das X war absichtlich versteckt, sobald ein Dienst einen Fehler meldete — es
erschien nur, wenn alles gruen war. Gerade eine Fehlermeldung blieb also
stehen. Jetzt ist das X immer da; Wegklicken vergisst Fehler- und
Fertig-Meldungen (sonst kaemen sie mit der naechsten Statusmeldung eines
anderen Dienstes wieder hoch), ein laufender Ladevorgang darf wiederkommen.

Dazu: httpx haengt an jeden Fehler "For more information check: <Mozilla-
Link>" — raus, in der Anzeige und im llm-adapter an der Quelle. Bei nur einer
Meldung stand das ❌ doppelt (grosses Symbol + Zeile) — jetzt einmal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:46:25 +02:00
duffyduckandClaude Opus 5.5 ae79d9e8f6 fix(llm): Modelle mit anderer Quantisierung laden (Gemma 4 von unsloth)
Laden von unsloth/gemma-4-12B-it-qat-GGUF scheiterte mit "500": der Katalog
trug pauschal Q4_K_M ein, das Repo hat aber nur UD-Q4_K_XL. llama-server fand
die Datei nicht und beendete sich sofort; llama-swap reicht den Grund nicht
weiter ("upstream command exited prematurely"), es kam nur "500" an.

llm-adapter: fragt vor dem Laden die Dateiliste des Repos ab. Gibt es die
gewuenschte Quantisierung nicht, nimmt er die naechstbeste aus einer festen
Reihenfolge (Q4_K_M, UD-Q4_K_XL, Q4_K_XL, Q4_K_S, IQ4_XS, …) und sagt das im
Download-Fenster an. Kein ladbares GGUF im Repo → sofortige Absage, die Karte
bleibt unberuehrt. Bild-Projektor (mmproj), MTP-Zusatzdateien und imatrix
zaehlen nicht als Modell. Endet llama-server trotzdem sofort, nennt die
Meldung die haeufigsten Gruende statt nur "500".

Diagnostic: "Von HuggingFace aktualisieren" traegt pro HF-Repo die tatsaechlich
vorhandene Quantisierung und die echte Groesse ein (bisher 0 GB) — auch bei
schon vorhandenen HF-Eintraegen. Repos ohne ladbares Modell kommen nicht mehr
in den Katalog bzw. werden als "nicht ladbar" markiert. Katalog zeigt die
Quantisierung pro Modell.

Gemma 4 selbst: llama.cpp kann es seit Juni (#24077), der Stand auf der Box ist
vom 14.08. — es lag nur an der Datei.

Getestet gegen echte HF-Repos (Gemma → UD-Q4_K_XL 6,7 GB, Qwen/Mistral bleiben
Q4_K_M) in Adapter und Diagnostic identisch, und gegen llama-swap: Ausweichen
angesagt + geladen; Repo ohne GGUF → Absage ohne Download.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:43:55 +02:00
duffyduckandClaude Opus 5.5 23feb3ba2d feat(llm): ein Modell pro Karte, Clients waehlen nur aus — und Doku fuer Drittprogramme
Bisher konnte jeder Client per Modellname llama-swap zum Wechseln bringen —
ein Fremdprogramm haette ARIA das Modell unter den Fuessen weggetauscht.
Jetzt:

- Jede LLM-Karte einer Box (LLM_GPUS=1,2, alt LLM_GPU=1) haelt GENAU ein
  Modell, dauerhaft. Welches, legt nur die Diagnostic fest. Karten, die nicht
  fuers LLM vorgesehen sind, tauchen beim LLM nicht auf und werden als Ziel
  abgelehnt.
- Clients (ARIA, Fremdprogramme ueber RVS, OpenHands ueber den OpenAI-Port)
  sehen nur die geladenen Modelle und laden nie: Anfrage fuer ein anderes →
  model_not_loaded mit Liste des Geladenen (RVS) bzw. HTTP 404 (OpenAI-Port).
  Nichts wird verdraengt.
- Mehrere Clients koennen dasselbe Modell gleichzeitig nutzen.

llm-adapter: Belegung in /models/aria_slots.json. Die llama-swap-Config
enthaelt nur belegte Karten: pro Modell CUDA_VISIBLE_DEVICES=<Karte>, ttl 0,
groups.swap=false, hooks.on_startup.preload. Alles gegen llama-swap
ausprobiert (zwei Modelle gleichzeitig ready, keins verdraengt, Unbekanntes
404, env kommt beim jeweiligen llama-server an). llama-swap laeuft mit
--watch-config; die frueher aufgerufenen Reload-Endpunkte (/reload,
/api/config/reload) gibt es nicht (404). Neu: llm_unload_model (Karte leeren),
llm_models_request → llm_models (Erkundung, jede Box antwortet einzeln).
Fehlgeschlagenes Laden setzt die Karte auf ihr vorheriges Modell zurueck,
sonst risse jedes Neuladen die anderen Karten mit. Uebernahme: ohne
Belegungsdatei landet LLM_MODEL auf der ersten LLM-Karte.

Compose: llama-swap sieht alle Karten (NVIDIA_VISIBLE_DEVICES=all) und
zaehlt sie wie nvidia-smi (CUDA_DEVICE_ORDER=PCI_BUS_ID); das Pinnen pro
Modell macht der Adapter.

Bridge: uebernimmt models/slots auch aus worker_ping (nach "Entladen" sonst
30 s veraltet) und sagt sofort ab, wenn das Modell auf keiner Box liegt,
statt per Broadcast in den Timeout zu laufen.

Diagnostic: Flotte zeigt pro LLM-Karte Modell + Zustand + "Entladen";
Katalog waehlt eine Ziel-KARTE (leere vorausgewaehlt, Ersetzen mit
Rueckfrage); Download-Fenster nennt Karte und was ersetzt wird.

RVS: llm_unload_model, llm_models_request, llm_models freigegeben.

docs/LLM-SCHNITTSTELLE.md: Schnittstelle fuer Drittprogramme (RVS + OpenAI-
Port), Fehlercodes, Gleichzeitigkeit, Python-Beispiel.

Tests: 23 gegen llama-swap (Adapter direkt) + Ende-zu-Ende ueber den echten
RVS mit dem Adapter als Prozess: Laden per RVS mit Fortschritt, das
Doku-Beispiel unveraendert (nur Adresse) findet beide Karten und bekommt eine
Antwort, Absage/Schweigen wie dokumentiert, zwei Clients parallel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:04:24 +02:00
duffyduckandClaude Opus 5.5 89cb3a765b feat(llm): Diagnostic zeigt, welches Modell gerade in der Karte liegt
Die Flotte listete "qwen3-4b, qwen3-8b" — das sind die Modelle, die die Box
fahren KANN (llama-swap /v1/models), nicht die geladenen. Ohne groups-Config
liegt immer nur eins in der Karte; ein anderes anzufragen entlaedt es
(gegen llama-swap ausprobiert: A laden, B anfragen → A unloaded).

Der Adapter fragt jetzt llama-swap /running und schickt das Ergebnis in jedem
worker_ping (10 s) und im worker_hello mit; nach einem Modellwechsel sofort.
None = llama-swap nicht erreichbar, [] = nichts geladen — die Anzeige
unterscheidet das ("geladen: ?" vs. "keins geladen").

Diagnostic: das geladene Modell gruen mit "● geladen", die anderen grau mit
Hinweis, dass sie beim ersten Aufruf laden und das geladene verdraengen.

"GPU void": die LLM-Zeile zeigt jetzt LLM_GPU (die Karte von llama-swap) statt
der NVIDIA_VISIBLE_DEVICES des Adapters, die mit der Modell-Karte nichts zu
tun hat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:40:17 +02:00
duffyduckandClaude Opus 5.5 864237ff2c feat(llm): Download-Fortschritt beim Modell-Laden — und "Laden" kommt endlich an
Eigentlicher Fehler zuerst: llm_provision_model/_result und llm_remove_model
standen nie in der RVS-Allow-List. Der RVS verwirft unbekannte Typen mit einem
stillen return — jeder Klick auf "Laden" im Modell-Katalog ist seit Stage D
(19.09.) an dieser Liste gestorben, die Box hat nie davon erfahren. Laden
liessen sich nur die Modelle aus der Basis-Config der Box.

Gefunden, weil grep rvs/server.js fuer eine Binaerdatei hielt und still
uebersprang: in Zeile 164 stand seit a7998bcd ein ECHTES Nullbyte im
Template-String statt eines Escapes. Node stoert das nicht, aber git zeigte
fuer jede Aenderung am RVS nur noch "Binary files differ". Jetzt \u0000 —
zur Laufzeit derselbe String.

Fortschritt: llama.cpp zieht das GGUF per -hf selbst und meldet nichts. Der
llm-adapter fragt vorab bei Hugging Face, welche Dateien (Groesse, sha256)
kommen, und misst genau diese im HF-Cache-Layout, das llama.cpp verwendet:
  /models/models--org--repo/blobs/<sha256>.downloadInProgress  (waechst)
  /models/models--org--repo/blobs/<sha256>                     (fertig)
Layout gegen den echten llama.cpp-Server ausprobiert, nicht angenommen. Geht
auch bei gesplitteten Modellen und zwei Downloads gleichzeitig; Fallback fuer
aeltere Layouts ueber alle *.downloadInProgress. Phasen: queued → start →
download (%, MB/s, Restzeit) → load ("laedt in die Karte") → ready ("in Karte
N geladen", Name + VRAM aus nvidia-smi) bzw. error.

Mitgefixt: der Warmup wartete LLM_TIMEOUT_SEC (Default 60 s). Ein Modell mit
mehreren GB meldete deshalb "Fehler", waehrend llama-swap weiterlud. Jetzt
LLM_WARMUP_TIMEOUT_S (1900 s, ueber llama-swaps healthCheckTimeout).

Diagnostic: Knopf "⬇ Downloads" neben "Von HuggingFace aktualisieren" mit
Zaehler fuer laufende; "Laden" oeffnet das Fenster direkt. Der Server merkt
sich den Stand pro (Box, Modell), ein spaeter geoeffneter Tab sieht laufende
Downloads. Antwortet die Box nicht, steht nach 30 s eine Warnung da statt
nichts.

Compose (KI-Box): llm-adapter bekommt LLM_GPU (Karte fuer die Meldung) und
HF_TOKEN (Dateiliste gesperrter Repos).

Test: Fortschritt gegen einen echten llama.cpp-Download (Qwen2.5-0.5B Q8,
676 MB): Prozent steigend 0 → 91.6, dann load; zweiter Lauf aus dem Cache
direkt load mit cached=true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:33:54 +02:00
duffyduckandClaude Opus 5.5 d684bbb5f0 feat: ARIA kann E-Mails lesen und senden
IMAP/SMTP mit SSL/TLS, STARTTLS oder unverschluesselt. Senden mit Text, HTML,
Anhaengen und eingebetteten Bildern (cid:), Antworten mit In-Reply-To. Lesen,
Suchen, Ordner anlegen (auch Unterordner), Mails verschieben.
Tools: email_status/send/list/read/folders/folder_create/move.

Whitelist als harte Grenze im Sendepfad, nicht im Prompt: alles, was nicht
drinsteht, ist gesperrt, leer = nirgendwohin. Jeder Empfaenger zaehlt (An, Cc,
Bcc), ein fremder blockiert die ganze Mail. Genau eine Schreibweise:
name@domain, '*' nur als *@domain, *@*.domain oder *@*. '*hacker-net.de' liest
sich, als passte es auf 'evilhacker-net.de' — solche Kurzformen lehnt das
Speichern mit Korrekturvorschlag ab. Verglichen wird die Domain als Ganzes.

Zugangsdaten sieht ARIA nie:
- liegen im Brain unter /data. NICHT in /shared — das ist in ARIAs Container
  eingehaengt, /data nicht.
- keine Schnittstelle gibt das Passwort zurueck, nur password_set.
- Brain-Export laesst email_secret.json + email_admin.key aus (GNU- und
  busybox-tar geprueft, der Diagnostic-Container ist Alpine).
- Anhangspfade nach realpath geprueft: ein Symlink in /shared/uploads auf die
  Geheimnis-Datei wuerde sonst im Brain aufgeloest und verschickt.
- Konfiguration nur mit Admin-Schluessel (X-Aria-Admin, liegt in /data). Die
  Diagnostic haengt ihn an, weist aber Anfragen aus ARIAs Container ab —
  sonst koennte eine eingehende Mail sie per curl die Whitelist oeffnen lassen.
Grenze: ARIAs Container hat SSH auf die VM. Gegen einen gezielten Einbruch
schuetzt das nicht, nur dagegen, dass Zugangsdaten von selbst auftauchen.

Eingehende Mails sind fuer ARIA als Daten markiert, nicht als Anweisungen.

Diagnostic: Einstellungen → Integrationen → ARIA E-Mail, plus
"📬 Postfach oeffnen" — ARIAs ganzes Postfach in eigenem Fenster, Unterordner
eingerueckt, Posteingang + Gesendet oben. Nur lesend, markiert nichts als
gelesen. Mail-HTML in einem Rahmen ohne Skripte und ohne Nachladen von aussen
(kein Tracking-Pixel); eingebettete Bilder kommen als data: mit. Jede
gesendete Mail landet als Kopie in "Gesendet" — fehlt der Ordner, wird er
angelegt, statt die Kopie still zu verlieren.

Tests: 65 Einheitstests (Whitelist inkl. evilhacker-net.de, MIME-Aufbau,
Header-Injection, Symlink-Ausbruch, Geheimnis nie in Ausgaben) + 50 gegen einen
echten Mailserver (GreenMail): TLS-Login, Senden mit Anhang + Bild, Flags,
Suche mit Umlaut, Gesendet-Kopie, In-Reply-To, Ordner mit Umlaut, MOVE und
der Fallback ohne MOVE (der keine fremden geloeschten Mails mitreisst).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:14:31 +02:00
duffyduckandClaude Opus 5.5 6d978d72b3 feat: Modell pro Chat waehlen — und sehen, welches gerade laeuft
Nach einem Sicherheitsfilter-Treffer fuhr ein Projekt still auf dem
Ausweich-Modell weiter, und nirgends war zu sehen, auf welchem. Zurueck ging
nur ueber "Liste leeren" im Filter-Panel, und das gleich fuer alle Chats.

Jetzt drei Ebenen (aria-brain/context_model.py):
  Einstellung (brainModel) < pro Chat < Ausweich (Sicherheitsfilter)

- Pro Chat: Hauptchat und jedes Projekt koennen ein eigenes Modell haben, in
  /shared/config/context_models.json. Bewusst kein Feld im Projekt-Manifest —
  der Hauptchat ist kein Projekt und waere sonst aussen vor.
- "↩ zurueck" pro Chat: loescht nur die Filter-Vermerke DIESES Chats. Blockt
  der Filter erneut, weicht das Brain eben wieder aus.
- Angezeigt wird, was wirklich laeuft: chat() gibt das Modell aus modelUsage
  der CLI zurueck (aufgeloeste Version statt Alias) samt Grund; Brain → Bridge
  → chat_backup + Live-Payload → App und Diagnostic. Als Grundeinstellung
  zaehlt das Modell, mit dem das Brain laeuft, nicht der Dateiwert — so faellt
  ein ausstehender Neustart nach brainModel-Aenderung auf.

Diagnostic: 🧠-Auswahl neben "+ Projekt" fuer den fokussierten Chat, Modell im
Untertitel jeder Kontext-Karte, Modell statt "Claude" am Bubble-Badge (orange
mit ↪ bei Ausweich).

App: hinter der Quell-Anzeige (Einstellungen, Default aus — Kopfzeile bleibt
fuer Mama unveraendert) ein Modell-Chip in der Kopfzeile; Tippen oeffnet die
Auswahl fuer diesen Chat inkl. "Zurueck". Laeuft ueber den vorhandenen
brain_request-Kanal, also keine neuen RVS-Typen.

Tests: 47/47 (12 neue fuer die Aufloesungsreihenfolge gegen agent.chat()),
tsc ohne Fehler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:39:30 +02:00
duffyduckandClaude Opus 5 d221c1a320 feat(brain): Sicherheitsfilter blockiert nicht mehr das ganze Projekt
Anthropics Filter ("safeguards flagged this message", Details: [cyber]) trifft
genau unsere Themen — Ports, Tunnel, Zugangsdaten. Bisher war danach der
Projekt-Chat tot, und zwar aus einem Grund, den man der Meldung nicht ansieht:

Der Filter meldet sich NICHT als HTTP-Fehler. Die CLI liefert ein voellig
normales Ergebnis mit is_error=true und dem Fehlertext im content — also dort,
wo sonst ARIAs Antwort steht. proxy_client.py warf das Flag weg, der Agent
konnte Fehler und Antwort nicht unterscheiden und schrieb den Text per
conversation.add("assistant", ...) in den Verlauf. Weil der Verlauf bei jedem
Turn mitgeht, scheiterte ab da JEDE weitere Frage an derselben Stelle.

Zwei Stufen dagegen (aria-brain/safeguard.py):

1. Modellwechsel. Der Filter haengt am Modell ("Opus 5's safeguards ..."),
   Anthropic raet selbst zu "change your model". Derselbe Turn wird automatisch
   mit dem Ausweich-Modell neu gefragt; laeuft der Rest des Turns (Tool-Schritte)
   gleich mit darauf weiter. Der ausloesende Turn wird vermerkt, damit die
   naechsten Turns nicht erst wieder gegen die Wand laufen. Der Vermerk
   verfaellt von selbst, sobald der Turn aus dem Rolling Window gerollt ist —
   nichts muss aufgeraeumt werden, und das Projekt faellt von allein aufs
   Hauptmodell zurueck.

2. Quarantaene. Blockt auch das Ausweich-Modell, liegt es am Verlauf selbst.
   Dann faellt der ausloesende Turn aus dem Prompt-Fenster. NICHT geloescht: er
   bleibt in conversation.jsonl, steht mit Text in der Diagnostic und ist per
   Klick zurueckholbar. Der rohe Fehlertext wird nie persistiert — ARIA sagt in
   eigenen Worten, was passiert ist.

Der Zustand liegt in /data/safeguard.json, bewusst nicht in conversation.jsonl:
ein Markieren dort erzwaenge ein Rewrite, und das schreibt nur das In-Memory-
Window zurueck — alles schon Destillierte waere weg.

Erkennung bewusst zweistufig: mit is_error genuegt ein Marker, ohne muss der
Text mit "API Error" beginnen. Sonst wuerde eine Antwort, in der ARIA ueber den
Filter *spricht*, einen Modellwechsel ausloesen.

Mitgenommen:
- Das Memory-Destillat schickt denselben Verlauf nochmal durch und wurde
  genauso blockiert — nur fiel es dort als "unparsbares JSON" auf und
  wiederholte sich bei jedem Lauf. Weicht jetzt ebenfalls aus.
- clean_poisoned_turns.py raeumt die Altlasten: Fehlertexte, die vor diesem Fix
  schon im Verlauf stehen. Neu auch in Projekten (dort sterben sie ja), mit dem
  ausloesenden User-Turn desselben Kontexts, Dry-Run per Default.
- Diagnostic: Ausweich-Modell einstellbar (runtime.json, ohne Neustart, weil
  safeguard.py erst im Ernstfall liest) und ein Panel, das zeigt was blockiert
  wurde und ob es NOCH wirkt.

Tests: 35/35 fuer die Rettungskette gegen den echten agent.chat(), 16/16 fuer
das Aufraeum-Script.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 10:16:24 +02:00
duffyduckandClaude Opus 5 d53dd83df4 feat(diagnostic): "+ Projekt" direkt ueber dem Chat
Im Kontext-Streifen gab es keinen Weg, ein Projekt anzulegen — das ging nur
unten in den Einstellungen. Der Knopf steht bewusst AUSSERHALB des scrollenden
Streifens: bei mehreren Projekten waere er dort nach rechts rausgerutscht.

Anlegen zieht jetzt beide Projekt-Oberflaechen nach (Einstellungs-Liste UND
Streifen) und setzt den Fokus aufs neue Projekt. Vorher aktualisierte nur die
Liste, das neue Projekt blieb im Streifen bis zum 15s-Poll unsichtbar — und der
Fokus blieb im Hauptchat, obwohl create_project() serverseitig umschaltet. Man
legte also ein Projekt an und schrieb weiter in den Hauptchat.

Dazu ein Fehlerpfad: ein fehlgeschlagenes Anlegen (z.B. leerer Name, 400 vom
Brain) wurde stillschweigend verschluckt — das Modal schloss, als waere alles
gut. Jetzt kommt die Begruendung des Brains als Meldung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 10:16:01 +02:00
duffyduckandClaude Opus 5 58d60adbaf feat(diagnostic): Satelliten und Host-Agenten umbenennen
Beide hiessen bisher so, wie es in IHRER .env steht. Umbenennen hiess also: auf
den Rechner, Datei aendern, Dienst neu starten — beim Fire TV sogar neu
provisionieren. In der Oberflaeche gab es dafuer gar keinen Weg.

Jetzt ein ✎ pro Eintrag. Der Alias liegt zentral in
/shared/config/display_names.json ({"host": {...}, "satellite": {...}}) und
wirkt sofort fuer JEDEN schon ausgerollten Agenten, ohne ihn anzufassen.

Der entscheidende Teil ist die Rueckrichtung: die Bridge loest den Alias beim
ADRESSIEREN wieder auf (_resolve_host / _resolve_satellite). Ohne das haette man
etwas umbenennen koennen und es danach nicht mehr ansprechen — der Agent
vergleicht die Adresse gegen seine EIGENE Kennung und kennt den Alias nicht.
Angesprochen werden kann weiterhin alles: Alias, Eigenname oder technische ID.

Der Eigenname bleibt in der Liste klein daneben sichtbar, damit man den Bezug
zum Geraet nicht verliert. Leerer Name entfernt den Alias.

Diagnostic und Bridge teilen sich die Datei ueber /shared — kein neuer
Nachrichtentyp, keine Allow-List-Aenderung, eine Wahrheit.

Nebenbei behoben: _display_name_set legte fest "/shared/config" an, obwohl der
Dateipfad konfigurierbar ist. Damit scheiterte das Schreiben stillschweigend,
sobald der Pfad abwich. Verzeichnis wird jetzt aus dem Pfad abgeleitet.

17 Pruefungen: Aliasvergabe, Liste, Aufloesung in beide Richtungen,
Gross/Kleinschreibung, alter Name bleibt gueltig, anderer Agent unberuehrt,
beide Bereiche in einer Datei ohne sich zu stoeren, Entfernen, unbekanntes Ziel
wird nicht still umgebogen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-04 18:54:36 +02:00
duffyduckandClaude Opus 5 6404b99510 feat(satellite): der Satelliten-Rechner steht selbst mit in der Geraeteliste
Der Satellit laeuft auf einem ganz normalen Rechner — bei Stefan auf
duffy-desktop. Dort laufen typischerweise noch andere Dienste (FreeCAD-MCP,
Web-App, Dateiserver). Bisher war ausgerechnet dieser Rechner unsichtbar: man
findet sich nicht per mDNS/ARP selbst.

Er erscheint jetzt als normales Geraet — ansprechbar wie jedes andere, nur
zusaetzlich mit 🛰️ markiert und blau abgesetzt, damit klar ist, dass dort der
Satellit traegt. Abschaltbar ueber LIST_SELF=false, falls man den Traeger
bewusst heraushalten will.

Nebeneffekt, der ein echtes Problem loest: der eigene Rechner gilt damit als
"gefunden" und ist ohne Eintrag in TUNNEL_ALLOW tunnelbar. Genau der Fall, um
den es ging — der FreeCAD-MCP laeuft auf dem Satelliten-Host selbst.

Beim Zusammenfuehren bleibt die Markierung erhalten: findet mDNS denselben
Rechner nochmal, haette der Merge isSatellite sonst verschluckt. hostname kommt
aus demselben Grund mit in die erhaltenen Felder.

Dazu ein ✎-Knopf pro Geraet in der Diagnostic: Namen vergeben, damit ARIA
"duffy-desktop" ansprechen kann statt einer IP. Der Name haengt an der MAC und
ueberlebt einen DHCP-Wechsel.

8 Pruefungen: Selbst-Eintrag vollstaendig, Markierung ueberlebt den Merge,
mDNS-Infos bleiben erhalten, eigener Rechner ohne TUNNEL_ALLOW erlaubt, fremde
IP weiterhin blockiert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-04 18:49:49 +02:00
duffyduckandClaude Opus 5 cfd17b200a feat(satellite): Port-Tunnel ins fremde Netz + Geraete-Namen
ARIA sitzt woanders und kommt nicht ins Buero-Netz. Der Satellit kann beides:
er sieht das LAN und haelt eine ausgehende Verbindung zum RVS. Also reicht er
eine TCP-Verbindung durch — wie der VNC-Tunnel, nur fuer beliebige Ports.
Damit erreicht ARIA einen FreeCAD-MCP, eine Web-App, ein NAS-Interface.

Entscheidend fuer die Brauchbarkeit: die Bridge macht daraus einen GANZ
NORMALEN lokalen Port (aria-bridge:19000...). Dadurch funktioniert alles, was
sowieso TCP spricht — MCP-Client, curl, Browser — ohne Sonder-API. Jede
eingehende Verbindung bekommt einen eigenen Tunnel, parallele Verbindungen
sind also kein Problem (ein Browser oeffnet gern sechs).

Geraete-Namen: "duffy-desktop" ist ansprechbar, "192.168.1.47" nicht. Stefan
kann jedem gefundenen Geraet einen Namen geben, ARIA adressiert danach.
Schluessel ist die MAC, nicht die IP — per DHCP wandert die IP, die MAC bleibt.

Absicherung, weil das ein Weg in ein fremdes Netz ist:
- TUNNEL_ENABLED default AUS, pro Satellit.
- Ohne TUNNEL_ALLOW nur Ziele, die bei der Suche GEFUNDEN wurden. Der Tunnel
  kann damit nichts ansteuern, was nicht ohnehin im LAN sichtbar ist — und
  nichts im Internet.
- Obergrenze gleichzeitiger Tunnel + Reaper fuer ungenutzte (sonst bleibt eine
  vergessene Verbindung offen, bis der Satellit neu startet).
- Faehigkeit 'tunnel' wird im sat_hello gemeldet, damit Diagnostic und ARIA
  sehen, ob der Satellit das ueberhaupt erlaubt.

Neue RVS-Typen eingetragen (sat_tunnel_*, sat_name_*) — ohne das verwirft die
Allow-List sie still, und nichts funktioniert ohne Fehlermeldung.

16 Pruefungen gegen BEIDE echten Implementierungen (nur der RVS dazwischen ist
kurzgeschlossen): Namensaufloesung, Freigabe-Regeln, fremder Satellit
ignoriert, abgeschaltet blockt, echter Byte-Durchlauf (PONG:HALLO), unbekanntes
Ziel schliesst sauber statt zu haengen, Liste/Schliessen/Doppel-Schliessen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-04 18:35:40 +02:00
duffyduckandClaude Opus 5 ae3a112868 docs: README holt den Nachmittag nach — Modelle, Datenschutz, Flugwetter, Overlay
Die README war vom Vormittag, danach kamen neun Commits. Keiner davon stand
drin.

Neu dokumentiert:
- Sprachmodell: Liste aus Anthropics Modell-Katalog, Tier vs. feste Version,
  Test-Knopf samt seiner vier Ergebnisse (inkl. dem gelben Fall "nicht
  erkannt", bei dem der Proxy still opus eingesetzt haette)
- Datenschutz-Schalter: die drei Sendekanaele, warum kein Neustart noetig ist,
  und warum eine per docker-compose gesetzte Variable als gesperrt erscheint
- Warnung bei veralteten Proxy-Patches (die Falle hat zweimal Zeit gekostet)
- Anhang-Limit, Zeitstempel/Sortierung/Zeitraum bei den Gedanken
- Overlay: wie es ohne feste Dauer wieder weggeht, und warum der
  Accessibility-Weg genommen wurde statt des naheliegenden
- flugwetter als Beispiel-Skill: Quellen mit Kosten, pc_met als dokumentierte
  Kundenschnittstelle, Seitenwind gegen Pistenrichtung, Herkunft pro Zahl

Neuer Abschnitt "Secrets und was bei Anthropic landet" unter den
Sicherheitsprinzipien. Der gehoert dorthin und nicht in eine Fussnote: alles
was ARIA sieht, wird uebertragen und gespeichert, und ob daraus Trainingsdaten
werden, haengt an einer Konto-Einstellung. Dazu die drei Regeln (nie in den
Chat, Zugangsdaten in die Konfiguration, oauth_call statt Token-Herausgabe) und
ehrlich auch die Grenze: ARIA laeuft mit Root im Proxy-Container und kann
/shared/config lesen, wenn sie hinschaut.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 12:42:57 +02:00
duffyduck 517a8ea942 release(agent): bump to 0.0.1.5 agent-v0.0.1.5 2026-10-03 12:40:58 +02:00
duffyduckandClaude Opus 5 4589f4abc3 feat(android-agent): Einblendung geht weg, wenn Stefan etwas tut — statt nach fester Zeit
Eine feste Anzeigedauer ist immer geraten: mal zu kurz zum Lesen, mal steht sie
laengst im Weg. `seconds=0` liess die Einblendung bisher einfach stehen.

Jetzt heisst seconds=0: bleibt, bis der Nutzer etwas tut.

- Fernbedienung: der Accessibility-Dienst bekommt onKeyEvent und raeumt die
  Einblendung weg. Er gibt die Taste mit `return false` WEITER an den Film —
  es geht also kein Tastendruck verloren und der Player bleibt bedienbar.
  Dafuer brauchte der Dienst canRequestFilterKeyEvents + flagRequestFilterKeyEvents.
- Touch: FLAG_WATCH_OUTSIDE_TOUCH meldet eine Beruehrung ausserhalb. Die geht
  dank FLAG_NOT_TOUCHABLE gleichzeitig an die App darunter durch.
- Notbremse: nach 10 Minuten verschwindet sie in jedem Fall.

Bewusst NICHT der naheliegende Weg (Overlay faengt Tasten selbst ab): dafuer
muessten FLAG_NOT_FOCUSABLE und FLAG_NOT_TOUCHABLE weg, und dann nimmt die
Einblendung dem laufenden Film JEDE Eingabe, solange sie steht. Genau das soll
sie nicht.

host_notify beschreibt seconds=0 jetzt als den richtigen Wert fuer alles zum
Lesen (Wetter, Listen, Zahlen) und die feste Dauer fuer kurze Hinweise.

APK baut durch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 12:29:14 +02:00
duffyduckandClaude Opus 5 e67659b78a feat(diagnostic): warnt, wenn der Proxy mit veralteten Patches laeuft
Der Proxy kopiert proxy-patches/*.js beim START ueber den npm-Code. Wer nach
einem git pull nur `restart` macht — oder neu startet, BEVOR der Pull durch ist
— laeuft still mit der alten Fassung weiter. Nichts deutet darauf hin: der
Container ist "Up", die Oberflaeche sieht normal aus, die Aenderung wirkt
einfach nicht.

Das hat heute zweimal Zeit gekostet: einmal wirkte ein Proxy-Fix nicht, einmal
blieb die Modell-Liste alt, obwohl der Code laengst gepullt war (Proxy
gestartet 08:51, Patches gepullt 09:28).

Die Einstellungen vergleichen jetzt die Zeitstempel der Quelldateien mit den
eingespielten und zeigen einen Hinweis samt fertigem Befehl — inklusive der
Angabe, welche Datei wann geaendert und wann eingespielt wurde. Toleranz 5s,
weil Quelle und Kopie beim Start kurz nacheinander entstehen. Ein Fehler beim
Pruefen loest bewusst KEINEN Alarm aus, sonst stuende dort eine Warnung, die
nichts ueber die Patches aussagt.

Dazu: der Datenschutz-Block sagt jetzt ausdruecklich, dass KEIN Neustart noetig
ist — die CLI liest ~/.claude/settings.json bei jedem Aufruf neu. Nur ein
gerade laufender Turn nutzt noch die alte Einstellung.

4 Pruefungen gegen den echten Container, in beide Richtungen: schweigt nach
frischem Start, erkennt eine veraltete Kopie samt Zeitstempeln, schweigt nach
Wiederherstellung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 11:38:04 +02:00
duffyduck 988b074478 release(agent): bump to 0.0.1.4 agent-v0.0.1.4 2026-10-03 11:27:12 +02:00
duffyduckandClaude Opus 5 d5eea920a6 feat(diagnostic): Datenschutz-Schalter von Claude Code pruefen und setzen
Drei Kanaele, ueber die Claude Code zusaetzlich zur eigentlichen Anfrage Daten
verschickt — bisher nur per Hand in der docker-compose.yml abschaltbar, und
niemand konnte sehen, ob sie an sind:

  DISABLE_ERROR_REPORTING   Stacktraces an einen Fehler-Dienst. Bei Pro/Max ab
                            CLI 2.1.198 standardmaessig AN.
  DISABLE_TELEMETRY         Nutzungs-Metriken.
  DISABLE_FEEDBACK_COMMAND  /feedback sendet sonst die ganze Unterhaltung
                            inkl. Code — 5 Jahre Aufbewahrung.

Gesetzt wird in ~/.claude/settings.json unter "env". Die Datei ist in den
Proxy-Container gemountet, die Diagnostic erreicht sie ueber den vorhandenen
dockerExec — also kein Anfassen der docker-compose.yml und KEIN Neustart: die
CLI liest die Datei pro Aufruf.

Geprueft wird beides: was in settings.json steht UND was im Prozess wirklich
ankommt. Eine echte Container-Umgebungsvariable gewinnt gegen settings.json —
so ein Eintrag wird darum als "per docker-compose gesetzt" angezeigt und der
Haken gesperrt, statt eine Einstellung anzubieten, die nichts tut.

Geschrieben wird atomar (temp + mv) und nur der env-Block angefasst; bestehende
Einstellungen (tui, theme) bleiben. Sind alle drei abgewaehlt, verschwindet der
env-Block wieder ganz.

Der Hinweis im Panel sagt ausdruecklich, dass das NICHT die Trainings-
Einstellung ist — die steht im Anthropic-Konto und laesst sich hier nicht
umstellen. Link dorthin ist daneben.

12 Pruefungen gegen den echten Proxy-Container: lesen, setzen, Teilmengen,
JSON-Integritaet, Erhalt bestehender Werte, CLI laeuft damit, Abwaehlen raeumt
auf, Ausgangszustand wiederhergestellt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 11:21:47 +02:00
duffyduckandClaude Opus 5 2c60a19ed6 feat(oauth): ARIA bekommt Zugangstokens nicht mehr zu sehen
oauth_get_token hat den access_token im Klartext als Tool-Ergebnis
zurueckgegeben. Tool-Ergebnisse sind Teil der Unterhaltung — der Token ging
damit an Anthropic und lag je nach Konto-Einstellung 30 Tage oder 5 Jahre dort.

Neu: das Brain fuehrt den API-Aufruf SELBST aus.

- `oauth_call(service, url, method, body)` haengt die Authorization an,
  schickt den Request und gibt nur die Antwort zurueck. Der Token verlaesst
  den Brain-Prozess nicht.
- `oauth_get_token` sagt nur noch, OB authentifiziert ist und wie lange noch.
  Refresh laeuft weiter automatisch.
- Skills bleiben unveraendert: die holen den Token serverseitig ueber
  {BRAIN_INTERNAL_URL}/oauth/<service>/token — der laeuft nie durch ein
  Gespraech.

Host-Sperre als Sicherheitskern: die URL kommt vom Modell, also koennte ein
Aufruf den Token sonst an einen beliebigen Server schicken. Erlaubt sind nur
Hosts, die zum Provider gehoeren (aus auth_url/token_url, plus optionale
api_hosts). Verglichen wird auf den letzten zwei Labels, NICHT per endswith —
"boesespotify.com" endet sonst auch auf "spotify.com". Dazu: nur https (sonst
ginge der Token im Klartext ueber die Leitung) und nur die ueblichen Methoden.

19 Pruefungen, darunter die Angriffsvarianten: boesespotify.com,
spotify.com.angreifer.de, accounts.spotify.com.evil.io, spotify.co, leerer
Host — alle blockiert; api.spotify.com und abschliessender Punkt erlaubt.

System-Prompt und seed_rules nachgezogen, sonst erklaert ARIA sich das alte
Verhalten selbst. Beide sagen jetzt zusaetzlich ausdruecklich: Secrets gehoeren
nie in den Chat, und ein Secret nie in einer Antwort wiederholen. Genau so ist
hier naemlich ein client_secret abgeflossen — nicht ueber ein Tool, sondern
weil es eingetippt und dann zurueckzitiert wurde.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 11:16:41 +02:00
duffyduckandClaude Opus 5 752e2abc5f feat(models): Test-Knopf — zeigt, welches Modell wirklich geantwortet hat
Bisher liess sich nicht nachsehen, ob eine Modell-Einstellung ueberhaupt
greift. Der Proxy hat die Information sogar aktiv weggeworfen:
normalizeModelName() nahm den echten Namen aus modelUsage (z.B.
"claude-opus-5-5") und schrieb ihn auf "claude-opus-4" um. Das `model`-Feld
der Antwort log also — und damit war jede Ueberpruefung unmoeglich.

- normalizeModelName gibt den echten Namen durch. Im OpenAI-Protokoll soll
  `model` das Modell nennen, das geantwortet HAT; ein erfundener Name ist
  schlechter als keiner.
- Zusatzfelder aria_model / aria_error / aria_context_window / aria_max_output
  aus modelUsage.canonicalModel. Noetig, weil die CLI bei einem unbekannten
  Modell gar nicht bis zum Call kommt und ihre Begruendung einfach in den
  Antworttext legt — ohne Flag waere ein Fehlschlag nicht von einer Antwort
  zu unterscheiden.
- Knopf "🔬 Testen" neben Dropdown und Freitextfeld. Er schickt eine echte
  Mini-Anfrage durch den GANZEN Weg (Diagnostic → Proxy → extractModel →
  claude CLI → Anthropic).

Ausdruecklich NICHT gemacht: das Modell nach seiner eigenen Version fragen.
Darauf ist kein Verlass — Modelle kennen ihren Versionsstring oft nicht und
raten. Und den Dropdown-Wert zurueckzugeben waere sinnlos. Die Wahrheit kommt
aus modelUsage der CLI.

Vier unterscheidbare Ergebnisse:
  Tier-Alias      gruen  — zeigt die aufgeloeste Version ("opus" → Opus 5.5)
  exakte Version  gruen  — angefragt == geantwortet
  nicht erkannt   GELB   — der Proxy hat still "opus" eingesetzt; warnt
                           ausdruecklich davor, die ID so zu setzen
  ungueltig       ROT    — CLI-Begruendung im Klartext

Der gelbe Fall war wichtig: ein Name, der nicht wie ein Claude-Modell aussieht,
faellt auf "opus" zurueck und haette sonst als Erfolg gegolten — man setzt dann
eine ID, die nie benutzt wird, und merkt es nie.

Geprueft gegen ECHTE CLI-Ausgaben aus dem Proxy-Container (9 Faelle: Alias,
exakte Version, Tippfehler) plus 7 Faelle fuer die Anzeige-Logik. Nebenbefund:
`--model opus` loest derzeit auf claude-opus-5-5 auf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 10:54:22 +02:00
duffyduckandClaude Opus 5 09d06011d5 fix(models): Modell-Liste kommt jetzt von Anthropic — und feste Versionen greifen wirklich
Zwei Fehler. Der zweite war der schlimmere.

1) Die Liste war handgepflegt und damit garantiert veraltet.
   Sie kannte "Opus (aktuell: Opus 5)" — waehrend es laengst Opus 5.5,
   Sonnet 5.5 und Fable 5.1 gab. Der Knopf "↻ Aktualisieren" las nur eine
   lokale Datei neu und aktualisierte damit gar nichts.

   Jetzt kommt die Liste aus Anthropics eigenem Modell-Katalog
   (downloads.claude.ai/model-catalog/v1/catalog.json, Surface 'cc') —
   dieselbe Quelle, aus der sich Claude Code selbst bedient. Oeffentlich,
   ohne API-Key (die Models-API braucht einen; mit dem Max-Abo gibt es keinen).
   6h-Cache plus Datei-Cache, der auch offline und bei abgelaufenem TTL
   einspringt: eine etwas alte echte Liste ist besser als keine.

   Die Tier-Aliase (opus/sonnet/haiku/fable) bleiben vorne und bleiben die
   Empfehlung — sie folgen automatisch der neuesten Version. Darunter die
   exakten Versionen, aeltere ausdruecklich als solche markiert.

2) Eine feste Version liess sich faktisch nicht waehlen.
   extractModel() hatte eine MODEL_MAP und fiel fuer alles Unbekannte STILL
   auf "opus" zurueck. Wer "claude-opus-5-5" setzte, bekam ohne jede Meldung
   das Tier-Default. Schlimmer noch: claude-opus-5 und claude-fable-5 standen
   selbst in der Map und wurden auf ihr Tier eingeschmolzen — eine bewusst
   gewaehlte aeltere Version wurde also durch die neueste ersetzt.

   Volle Modellnamen gehen jetzt unveraendert an die CLI. Die versteht sie
   ("--model: an alias for the latest model or a model's full name") und
   meldet sich selbst klar, wenn sie einen Namen nicht kennt — ehrlicher als
   ein stilles Downgrade. Nur echter Muell faellt noch auf "opus", und das
   wird geloggt.

Nachgewiesen gegen den LIVE-Katalog und die echte CLI im Proxy-Container:
20 Pruefungen gruen, u.a. dass alle 12 gelieferten exakten Versionen den Proxy
unveraendert passieren. `claude --print --model claude-opus-5-5` antwortet.
Stefans aktive Einstellung ist der Alias "opus" — am laufenden Betrieb aendert
sich dadurch nichts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 10:47:34 +02:00
duffyduckandClaude Opus 5 4c0d8ce3fe feat(agent): Bild mit Text einblenden — host_notify kann jetzt image_path
Der Android-Agent konnte schon immer ein Bild MIT Text einblenden
(OverlayView.show nimmt ein imageB64), aber ARIA kam nicht dran: in der
Parameter-Liste von host_notify fehlte das Feld einfach. Die Faehigkeit war
gebaut und unerreichbar.

ARIA gibt jetzt image_path an (ein Pfad unter /shared/uploads/, damit arbeitet
sie sowieso) und das Brain wandelt die Datei nach base64 — statt ARIA
megabyteweise base64 durch den Tool-Call schieben zu lassen. Pfad-Praefix wird
geprueft, und ueber 4 MB wird abgelehnt: fuer eine Einblendung auf einem
Fernseher ist mehr sinnlos und blaeht die WS-Nachricht auf.

Nur Android-Agenten koennen das; die Beschreibung sagt das ausdruecklich.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 10:34:04 +02:00
duffyduckandClaude Opus 5 734868efab feat(diagnostic): Anhang-Limit einstellbar + Datum/Uhrzeit bei Gedanken
Zwei Dinge, die bisher nur im Code bzw. nur in der DB standen.

Anhang-Limit (Einstellungen → System & Wartung):
- Das Limit war eine Env-Variable mit 20 MB Default — zum Anheben musste man
  die .env editieren und das Brain neu starten. Jetzt steht es in runtime.json
  und das Brain liest es bei JEDEM Upload neu: Aenderung greift sofort.
- Reihenfolge: runtime.json > ENV > Default. Anders als beim OpenAI-Endpunkt
  gewinnt hier NICHT die .env — dort erzwingt ein Docker-Port-Mapping das,
  hier gibt es keinen solchen Zwang, und eine Einstellung die nichts tut waere
  schlimmer als keine.
- Default von 20 auf 64 MB. Genau so viel holt die App auch auf ausdruecklichen
  Tipp noch herunter (TAP_DL_MAX_BYTES); darueber warnt die UI, verbietet es
  aber nicht — der Anhang liegt dann sauber auf dem Server, ist mobil nur nicht
  mehr ladbar.
- Neuer Endpunkt GET /attachments/limit. Das fest eingetippte "max 20 MB pro
  Datei" im HTML ist weg — es stimmte nach jeder Aenderung nicht mehr.
- Die Fehlermeldung nennt jetzt MB statt Bytes und sagt, wo die Einstellung ist.

Datum/Uhrzeit bei Gedanken:
- created_at/updated_at lagen schon immer in der DB und wurden ausgeliefert —
  nur nie angezeigt. Jede Zeile zeigt jetzt "angelegt <Zeit>" und, falls
  abweichend, "geaendert <Zeit>" plus ein relatives "(vor 3 Std)". Damit sieht
  man auf den ersten Blick, was der letzte Eintrag war.
- Sortier-Dropdown: nach Typ gruppiert (wie bisher) oder nach Anlage/Aenderung
  auf- und absteigend. Bei Zeit-Sortierung wird die Liste flach dargestellt —
  man will sehen was zuletzt passiert ist, nicht in welche Schublade es gehoert.
- Zeitraum-Filter (🕒) mit von/bis, umschaltbar zwischen angelegt und geaendert,
  plus Schnellwahl Heute / 7 / 30 Tage. Gilt auch fuer Such-Treffer, damit
  "suche X, aber nur letzte Woche" geht. Zuklappen schaltet ihn aus, und das
  ✕-Zuruecksetzen raeumt ihn mit auf (sonst filtert er unsichtbar weiter).

Beides gegen den echten Code getestet: 7 Faelle fuers Limit (inkl. kaputter
runtime.json und einem 50-MB-PDF an beiden Grenzen), 10 fuer Sortierung und
Zeitraum.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 10:33:54 +02:00
duffyduckandClaude Opus 5 7b46881677 docs: README auf den aktuellen Stand — Android-Agent, Jellyfin, OpenAI-Endpunkt, Phase C
Die README war auf dem Stand vom 24.09. Seitdem sind ~60 Commits dazugekommen,
von denen ganze Baustellen gar nicht auftauchten.

Neu dokumentiert:
- Android-Agent 📱📺 (eigener Abschnitt): eine APK fuer Handy und Fire TV —
  sehen, steuern, ausgeben (Overlay + ARIAs echte Stimme), Audio-Fokus,
  Sprachdialog, Turn-Ownership im Broadcast-Relay, fester Signaturschluessel,
  Ausrollen per Satellit via ADB inkl. Fern-Provisionierung
- GUI-Steuerung der Desktop-Host-Agenten (capability-gated)
- Jellyfin-Integration (mehrere Server, API statt Klick-Weg)
- OpenAI-Endpunkt fuers lokale LLM + openhands-Profil, inkl. der Regel
  ".env gewinnt immer -> Diagnostic-Felder nur lesend"
- Agent-Historie und Auslastungs-Monitor in der Diagnostic
- App: Teilen-Ziel, Sprachnachricht zu Anhaengen, Zwischenruf per Sprache
- Trigger: Projekt-Kontext + ehrlicher Zustand in trigger_list
- Roadmap "Phase C" samt der fuenfteiligen Ursachenkette hinter den verlorenen
  Sprachnachrichten — jede Ursache sah fuer sich plausibel aus, das ist
  dokumentationswuerdig

Korrigiert (waren schlicht falsch):
- "Bildanalyse eingeschraenkt, ARIA sieht den Dateipfad, nicht das Bild" —
  widersprach der eigenen Roadmap; Vision laeuft
- "Grosse Dateien >5MB koennen WebSocket-Limits ueberschreiten" — die echten
  Grenzen sind jetzt 1500 MB Transport, 25 MB Auto-Download, 64 MB auf Tipp
- Audio-Format: Live-PCM-Streaming ist der Normalweg, AAC/MP4 nur noch Fallback

Neu als Limitierung benannt, weil heute gefragt: die Sprachaufnahme ist NICHT
wiederaufsetzbar — ein Verbindungsabbruch mitten in der Aufnahme verliert das
Stueck, es wird nicht gepuffert. Gleiches gilt fuer die TTS-Richtung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 10:21:50 +02:00
duffyduckandClaude Opus 5 cb89ebcd10 fix(stt): lange Sprachnachrichten brachen mitten im Satz ab
Der Endpointer hat seine EIGENE Rechenzeit als Stille gezaehlt.

Der Tick-Loop laeuft alle 200ms und ist die einzige Stelle, die akustische
Sprach-Energie misst (last_voice_at). Er hat aber auf die Zwischen-
Transkription gewartet — und die laeuft jedes Mal ueber den KOMPLETTEN Buffer.
Bei 10s Audio harmlos, bei zwei Minuten dauert sie mehrere Sekunden. In dieser
Zeit tickte nichts, danach war `now - last_voice_at` um die ganze Rechenzeit
groesser als endpoint_ms (5s) — Endpoint mitten im Satz, obwohl durchgehend
gesprochen wurde. Deshalb traf es genau die LANGEN Nachrichten, und zwar immer
spaeter werdend je laenger die Aufnahme.

Nachgewiesen: die Nachricht vom 02.10. 21:28 (1231 Zeichen) war die einzige mit
reason=endpoint statt stream_end und endet mitten im Satz ("...weil ich
weiss..."). Alle vollstaendigen Nachrichten im Thread haben reason=stream_end,
also den Stop-Knopf.

Fix:
- Zwischen-Transkription laeuft als eigener Task. Der Tick bleibt frei und
  misst weiter alle 200ms — die Rechenzeit kann nicht mehr als Stille gelten.
  Laeuft noch eine, wird keine zweite gestartet.
- Zwischen-Transkriptionen sehen nur noch die letzten 30s an. Damit bleibt ihre
  Kosten konstant statt mit der Aufnahmelaenge zu wachsen. Der FINALE Text
  entsteht unveraendert aus dem gesamten Buffer mit beam_size=5 — es geht kein
  Wort verloren. stt_partial bekommt dafuer ein Feld 'windowed'.
- _finalize und drop brechen eine laufende Zwischen-Transkription ab, damit der
  finale Text nicht hinter ihr auf den GPU-Lock warten muss.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 10:18:02 +02:00
duffyduckandClaude Opus 5 443b36bb9d fix(android-agent): Sprachausgabe meldet erst Erfolg, wenn wirklich Ton laeuft
Der Agent meldete "gesprochen", sobald die TTS-Anfrage im Socket lag. Ob sie
ueberhaupt rausging, ob der Server antwortete oder ob ein einziger Ton kam,
hat nie jemand geprueft. Auf dem Fire TV blieb es damit still, waehrend ARIA
"erledigt" sagte — ein Fehler, der sich nicht melden kann.

Drei Ursachen:
- OkHttps send() WIRFT NICHT, sondern gibt false zurueck, wenn die Verbindung
  zu ist. Der Rueckgabewert wurde verworfen: Senden ins Leere sah aus wie
  Erfolg. send() gibt das jetzt durch, requestTts wirft bei abgelehntem Senden.
- Niemand wartete auf Audio. Neu: awaitSpeechStart() wartet, bis fuer diese
  messageId wirklich Ton laeuft, sonst Fehler mit Grund.
- Warten darf NICHT im WS-Lese-Thread passieren: handle() laeuft dort, und das
  erwartete audio_pcm kommt ueber genau diese Verbindung — wir haetten uns
  selbst ausgehungert. speak/notify laufen jetzt in einem eigenen Thread und
  schicken ihr host_result von dort.

Brain-seitig haben speak/notify 40s statt 20s Zeit, damit die ehrliche Antwort
(TTS-Render + Uebertragung) nicht in den Timeout laeuft.

APK baut durch (Gradle, nur die zwei bekannten Deprecation-Warnungen).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 10:17:48 +02:00
duffyduckandClaude Opus 5 12b8362d26 fix(trigger): Antwort landet im Projekt des Timers — und der Zustand luegt nicht mehr
Zwei Fehler, die zusammen wie "der Timer hat nicht gefeuert" aussahen.

1) Trigger hatten KEIN Projekt. Beim Feuern ging die Antwort ohne projectId
   durch _process_core_response — also immer in den Hauptchat, auch wenn der
   Timer mitten in einem Projekt-Thread gestellt wurde. Im Projekt blieb es
   still, und es sah aus wie "nicht gefeuert". Jetzt wird das Projekt des
   Requests mit angelegt, der getriggerte Turn laeuft darin, und die Antwort
   kommt dort zurueck.

2) trigger_list zeigte nur "aktiv" / "DEAKTIVIERT" und verschwieg fire_count
   und last_fired_at. Ein One-Shot-Timer wird NACH dem Feuern automatisch
   deaktiviert — "hat sauber gefeuert" und "war nie an" sahen damit identisch
   aus. ARIA hat genau das falsch gelesen und berichtet, sie habe vergessen den
   Timer zu aktivieren, obwohl er puenktlich gefeuert hatte. Die Ausgabe nennt
   jetzt ausdruecklich "SCHON GEFEUERT (Nx, zuletzt <Zeit>)" plus den Kontext,
   in dem die Antwort landet.

Nachgewiesen am Fall flugwetter-whv-freitag-0900: gefeuert 07:00:05 UTC
(= 09:00 lokal), Antwort 46s spaeter, fire_count=1 — aber active=false im
Manifest und die Antwort im Hauptchat statt im Projekt-Thread.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 10:17:35 +02:00
duffyduck e628170760 release(agent): bump to 0.0.1.3 agent-v0.0.1.3 2026-10-03 10:16:20 +02:00
duffyduck 7af0b76ad3 release: bump version to 0.2.5.5 v0.2.5.5 2026-10-02 07:35:59 +02:00
duffyduckandClaude Opus 5 614ba48531 feat(app): Sprachnachricht zu Anhaengen + Zwischenruf per Sprache
Bisher war die Eingabeleiste ein Entweder-oder: sobald Text im Feld stand oder
ein Anhang dranhing, verschwand das Mikro. Damit ging genau das nicht, was man
eigentlich will — ein Bild anhaengen und den Auftrag dazu SPRECHEN. Und ein
Zwischenruf ging nur getippt.

Das Mikro ist jetzt immer da; Senden-Pfeil und Mund-/Ohr-Knoepfe weichen je
nach Lage, damit die Zeile nicht ueberlaeuft.

Anhang + Sprachnachricht:
- Mikro antippen mit wartenden Anhaengen laedt sie SOFORT hoch (parallel zum
  Reden) und markiert sie mit awaitVoice + clientMsgId.
- Die Bridge schickt solche Anhaenge nicht nach 0.8s alleine ans Brain, sondern
  wartet auf den stt_endpoint und fuehrt Anhang + gesprochenen Satz zu EINEM
  Turn zusammen. Ohne das sah ARIA ein Bild ohne Auftrag und danach einen Satz
  ohne Bild.
- Der Merge ist strikt an die clientMsgId gebunden (require_match): eine
  Sprachnachricht, die zufaellig waehrend eines fremden Uploads fertig wird,
  reisst dessen Anhaenge nicht an sich. Der Text-Pfad behaelt seinen Fallback.
- Bild und gesprochener Text landen in EINER Bubble (gemeinsame audioRequestId).
- Klappt das Mikro nicht, bleibt die Bubble mit den Bildern stehen statt zu
  verschwinden — die Dateien sind ja schon auf dem Server.
- Das Notfall-Fenster (420s) liegt ueber dem Aufnahme-Hard-Cap von 5 Minuten,
  sonst waeren die Anhaenge bei einer langen Aufnahme vorher alleine raus.

Zwischenruf per Sprache:
- Der 📣-Knopf erscheint jetzt immer, wenn ARIA im Kontext arbeitet. Mit Text
  schickt er sofort (wie bisher), ohne Text schaltet er die naechste Aufnahme
  scharf (sichtbar hervorgehoben, gilt fuer genau eine Aufnahme).
- Eine so markierte Aufnahme geht per stt_stream_start als interject raus; die
  Bridge schiebt sie in den laufenden Turn statt sie anzustellen.
- Dabei wird der laufende Turn NICHT abgebrochen (das war der Sinn) — nur die
  Wiedergabe stoppt, damit ARIA nicht ins offene Mikro redet.
- Die Bubble zeigt 📣 Zwischenruf statt 🎤, damit man nicht auf eine eigene
  Antwort wartet.

Merge-Logik gegen den echten Bridge-Code getestet (6 Faelle), tsc sauber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-02 02:11:06 +02:00
duffyduck 5b5591aa72 release: bump version to 0.2.5.4 v0.2.5.4 2026-10-02 01:51:14 +02:00
duffyduckandClaude Opus 5 b70623bb78 fix(app): Absturzschleife — Auto-Re-Download holte ungefragt ein 150-MB-Video
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>
2026-10-02 01:48:27 +02:00
duffyduckandClaude Opus 5 73b6827f6b fix(bridge): Log-Fluten durch wiederholte hello-Meldungen abstellen
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>
2026-10-02 01:43:52 +02:00
duffyduckandClaude Opus 5 a7998bcd8b fix(rvs): Datei-Antworten gezielt an den Fragenden statt an den ganzen Raum
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>
2026-10-02 01:43:04 +02:00
duffyduck 07df538763 release: bump version to 0.2.5.3 v0.2.5.3 2026-10-02 01:37:49 +02:00
duffyduckandClaude Opus 4.8 5761c5f6b9 fix(xtts): Worker-Neustartschleife — max_size an den RVS angleichen (50MB -> 1500MB)
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>
2026-10-02 01:33:22 +02:00
duffyduckandClaude Opus 4.8 87755b9c58 fix(app): Aufnahme-Sperre fragt den Socket statt den gemerkten Zustand
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>
2026-10-02 01:24:57 +02:00
duffyduckandClaude Opus 4.8 b6195d3a53 feat(app): Teilen-Ziel — Bilder/Videos/Dateien aus anderen Apps an ARIA
'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>
2026-10-02 01:19:55 +02:00
duffyduck b9354283af release: bump version to 0.2.5.2 v0.2.5.2 2026-10-02 01:14:25 +02:00
duffyduckandClaude Opus 4.8 bf1db6cbf9 fix(app): Diagnostic-Nachrichten verschwanden in Projekten (projectId fehlte)
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>
2026-10-02 01:11:18 +02:00
duffyduckandClaude Opus 4.8 ea159d4ed7 fix(app): Zombie-Verbindung — App zeigt 'getrennt', empfaengt aber weiter
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>
2026-10-02 01:08:37 +02:00
duffyduckandClaude Opus 4.8 dd1e1ac458 fix(app): verlorene Aufnahme sichtbar machen statt still zu loeschen
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>
2026-10-01 17:08:42 +02:00
duffyduckandClaude Opus 4.8 ddd77c7ad4 docs(xtts): openhands-Profil in .env.example und README sichtbar machen
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>
2026-09-30 22:40:14 +02:00
duffyduckandClaude Opus 4.8 7c3c932054 docs(xtts): OpenHands — SSH-Tunnel statt LAN-Port als Absicherung
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>
2026-09-30 22:34:14 +02:00
duffyduckandClaude Opus 4.8 d6c7502810 feat(xtts): OpenAI-Endpunkt fuers lokale LLM + OpenHands-Profil
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>
2026-09-30 22:30:37 +02:00
duffyduckandClaude Opus 4.8 dc77933014 fix(jellyfin): Erfolgsmeldung sagt ausdruecklich, dass kein Neustart noetig war
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>
2026-09-30 22:01:54 +02:00
duffyduckandClaude Opus 4.8 6a6b320f37 fix(jellyfin): eingeschlafene Sitzung erkennen und selbst neu aufbauen
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>
2026-09-30 21:50:43 +02:00
duffyduck cc91497923 release: bump version to 0.2.5.1 v0.2.5.1 2026-09-30 21:18:09 +02:00
duffyduckandClaude Opus 4.8 10e7faa92b fix(app): verlorene erste Sprachaufnahme — Watchdog, Reihenfolge, Startzustand
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>
2026-09-30 13:02:55 +02:00