169 Commits
Author SHA1 Message Date
duffyduckandClaude Opus 4.8 d030cd7bc6 fix(voice): verlorene Sprachnachrichten — Allow-List, Kaltstart, Dedup, Voice-Router
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>
2026-09-30 11:05:06 +02:00
duffyduckandClaude Opus 4.8 f4b7148cf9 feat(diagnostic): Agent-Historie pro Agent — Akkordeon je Nachricht
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>
2026-09-25 13:32:40 +02:00
duffyduckandClaude Opus 4.8 c7761284c7 feat(host-agent): Backend-Anbindung — RVS + Bridge-Registry + Brain-Tools (Phase B-D)
Damit kann ARIA Host-Agenten tatsaechlich nutzen (Ende-zu-Ende):

- RVS: host_hello/host_ping/host_command/host_result in ALLOWED_TYPES.
- Bridge: _hosts-Registry (host_hello/host_ping/host_result), _host_list,
  _host_request (requestId->Future wie Satelliten), HTTP-Endpoints
  /internal/host-list und /internal/host (op via action).
- Brain: Tools host_list/host_exec/host_read/host_write/host_info/
  host_screenshot + _dispatch_host (ruft /internal/host*). host_screenshot
  speichert das PNG unter /shared/host-screenshots. host_exec kann sudo=true.

Naechste (optionale) Phase E: Host-Agenten in der Diagnostic-Flotte anzeigen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-24 16:11:06 +02:00
duffyduckandClaude Opus 4.8 67ea68514a chore(logging): Debug-Logs raus + app.log-Rotation gegen volllaufende Platte
- diagnostic/server.js: die beiden Debug-Logs der ai-box-Fehlersuche entfernt
  (roher RX-Dump jeder worker_*/sat_hello-Nachricht + 'worker_hello empfangen'
  bei jedem 30s-Resend). Die Flotte laeuft, der Firehose kann weg.
- bridge/aria_bridge.py: /shared/logs/app.log (JSONL, wuchs unbegrenzt bei
  jeder App-Log-Zeile) rotiert jetzt bei >5 MB auf die letzten 2000 Zeilen
  (Muster wie metrics._maybe_rotate).

RVS selbst loggt nur Lifecycle (Connect/Disconnect) — kein Message-Firehose,
unveraendert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-19 16:47:50 +02:00
duffyduckandClaude Opus 4.8 39426dc70d feat(llm): Flotten-Katalog + modell-bewusstes LLM-Routing (Stage C)
ARIA nutzt lokale LLMs auf mehreren ai-boxen; jede Box (llama-swap) kann
mehrere Modelle fahren. Auswahl nach MODELL, Box wird automatisch gewaehlt.

- xtts/llm-adapter/adapter.py: fragt beim Connect llama-swap GET /v1/models ab
  und meldet die Modell-Liste in worker_hello (models:[...]), Fallback [LLM_MODEL].
- bridge/aria_bridge.py: Worker-Registry speichert models[]; _pick_worker(service,
  model=) beruecksichtigt nur Boxen, die das Modell fahren koennen (nachsichtig:
  keine → None → Broadcast/Claude-Fallback); Round-Robin je service+model
  verteilt mehrere Projekte auf mehrere Boxen; _local_llm reicht das Modell durch;
  _worker_list traegt models[].
- diagnostic/server.js: workers-Map + workerList um models[] erweitert.
- diagnostic/index.html: Compute-Flotte zeigt bei llm die Modell-Liste; das
  "Lokales Modell"-Dropdown wird LIVE aus den angemeldeten Boxen gebaut
  (Vereinigung + kuratierte Namen aus local_models.json, "· N Box(en)"/"offline"),
  darunter eine kompakte LLM-Box-Liste (Node→Modelle→Health). Speisung aus dem
  vorhandenen worker_update-Broadcast.

Kein Brain-/App-Eingriff. Routing greift nur bei aktivem Lokal-Schalter.
Deploy: diagnostic + bridge + llm-Boxen neu bauen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-19 02:49:30 +02:00
duffyduckandClaude Opus 4.8 254031bb7d refactor: "Gamebox" ueberall durch "AI-Box(en)" ersetzen
Rein terminologisch: die feste "Gamebox" ist konzeptuell zu beliebig vielen
AI-Boxen geworden (Multi-Node-Compute-Fleet). Wort-Sweep ueber Code-Kommentare,
Strings, README, docs, .env.example, CHANGELOG — keine Identifier/Keys betroffen
(Gamebox kam nirgends in Variablennamen vor). Casing erhalten: Gamebox→AI-Box,
gamebox→ai-box. NODE_NAME-Default gamebox→ai-box, Instanz-IDs f5tts@ai-box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-19 00:34:21 +02:00
duffyduckandClaude Opus 4.8 e39599ecea feat(compute): Redundanz-Routing per targetInstance (Stage 3)
Mehrere Instanzen pro Dienst nutzbar: Anfragen werden gezielt an eine freie
Instanz adressiert statt an alle gebroadcastet. Mehrere f5tts → naechstes
freies; mehrere LLM → parallele Turns (Multitasking); STT-Redundanz ueber
mehrere Apps via Lease.

- Worker (alle vier): filtern am Loop-Eingang — targetInstance gesetzt und
  != eigener INSTANCE_ID → Nachricht ignorieren. Feld fehlt → wie bisher.
- bridge (TTS/LLM, emittiert die Bridge selbst): _pick_worker() waehlt eine
  online+freie Instanz (Round-Robin), stempelt targetInstance auf
  xtts_request / llm_request. Keine Instanz bekannt → Broadcast.
- bridge (STT-Lease, emittiert die App): neuer stt_lease_request-Handler →
  _pick_stt_worker() (voxtral vor whisper) → stt_lease {instanceId}.
- app (audio.ts): requestSttLease() vor dem Stream, stempelt targetInstance
  auf stt_stream_start / stt_audio_chunk / stt_stream_end (+cancel). Kurzer
  Timeout → '' (Broadcast), Aufnahme haengt nie.

Voll rueckwaertskompatibel: Routing aktiviert sich erst, wenn Worker sich per
worker_hello (Stage 2) registriert haben — sonst bleibt alles Broadcast.
Braucht APK-Rebuild fuer die STT-Lease-Seite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-18 12:27:51 +02:00
duffyduckandClaude Opus 4.8 e255d7a659 feat(compute): Worker-Selbstanmeldung ueber RVS + Flotten-Anzeige (Stage 2)
Jeder GPU-Dienst meldet sich beim Connect mit worker_hello {instanceId,
service, node, gpus, model} und haelt die Registry per periodischem
worker_ping {instanceId, busy} (~10s) frisch. So weiss ARIA, was wo laeuft.

- xtts/{voxtral,whisper,f5tts}/bridge.py + llm-adapter/adapter.py:
  INSTANCE_ID=service@NODE_NAME, _worker_register()-Coroutine (hello + ping),
  busy-Quelle je Worker (aktive STT-Sessions / TTS-Render / in-flight LLM);
  Task sauber gecancelt bei Reconnect.
- bridge/aria_bridge.py: self._workers-Registry + Handler worker_hello/
  worker_ping (spiegelt sat_hello), _worker_list() (35s-Offline-TTL),
  _pick_worker() (Round-Robin freie Instanz, fuer Stage-3-Routing),
  /internal/worker-list-Endpoint.
- diagnostic/server.js: workers-Map, worker_hello/worker_ping-Tracking,
  worker_update-Broadcast + worker_list-Action + on-connect-Snapshot.
- diagnostic/index.html: "Compute-Flotte"-Panel im Satelliten-Tab — pro Node
  gruppiert, mit Dienst/Modell/GPU und frei/beschaeftigt/offline-Status.

Stage 2 von 3. Reine Sichtbarkeit, kein Routing-Verhalten geaendert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-18 12:22:47 +02:00
duffyduckandClaude Opus 4.8 31444f1442 feat(voice): Wake-Word per Befehl wieder anschalten (Gegenstück zu wake_off)
"Wake-Word an" / "mach das Ohr an" / "hör wieder zu" / "wach auf" → App startet
den Listener wieder. Geht per Text-Nachricht ODER manuellem Aufnahme-Button —
beide laufen unabhängig vom Wake-Word-Listener, also funktioniert das Wieder-An
auch wenn das Ohr gerade taub ist (nur nicht per "Computer", das hört ja nicht).

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

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 20:27:48 +02:00
duffyduckandClaude Opus 4.8 3889d72726 feat: GPS-Trigger-Kopplung + generatives Cockpit (aria_view)
GPS: near()/entered_near()/left_near()-Watcher schalten das Tracking automatisch an bzw. beim Loeschen des letzten wieder aus; gpsTracking.start() sichert jetzt die Background-Permission. Cockpit-Fundament: neues Brain-Tool present_view emittiert aria_view (Orb + Karten), Bridge/RVS leiten weiter, ariaView.ts haelt die Spec App-seitig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 01:21:43 +02:00
duffyduckandClaude Opus 4.8 5aa5e2b6c6 fix(chat): Nachrichten brechen nicht mehr ab (Bridge-Timeout) + Diagnostic-Selbstheilung
Ursache: Bridge-/chat-Aufruf hatte 1200s (20min) Timeout, der Proxy aber
24h. Lange Software-Dev-Turns dauern >20min → Bridge gab auf ('/chat
fehlgeschlagen: timed out'), der Brain lieferte die Antwort Minuten spaeter
(nur im Log, keine Bubble), und der Diagnostic-Kontext blieb auf 'running'
haengen (Folgenachrichten in die Queue trotz idler ARIA; nur Ctrl+R half,
verlor aber die Queue-Nachricht).

- Bridge: /chat-Timeout 1200s → 24h (env BRAIN_CHAT_TIMEOUT_SEC), passend
  zum Proxy.
- Diagnostic: reconcileDiagStates() gleicht lokalen 'running'-Zustand gegen
  die Brain-queue-status ab (2 Polls Karenz). Haengt ein Kontext, obwohl der
  Brain nicht busy ist → Queue weiterschalten: angestellte Nachricht geht
  automatisch raus statt verloren.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-08 16:29:16 +02:00
duffyduckandClaude Opus 4.8 7ab2e3c101 feat(zwischenruf): Korrektur mitten in den laufenden Turn schieben
Neben Senden ein 'Zwischenruf' (App: oranger Button, nur wenn ARIA im
aktiven Kontext arbeitet; Diagnostic: Buttons neben beiden Senden). Geht
NICHT in die Queue und bricht NICHT ab — die Nachricht wird in den
laufenden claude-Subprozess geschoben; er greift sie an der naechsten
Tool-Grenze auf.

Technik:
- proxy-patches/manager.js: claude laeuft jetzt im --input-format
  stream-json-Modus, initialer Prompt als stream-json User-Message,
  stdin bleibt OFFEN; sendMessage() schiebt weitere User-Messages nach;
  bei 'result' wird stdin geschlossen (Turn endet sauber). Ersetzt die
  bisherigen sed-Patches (jetzt volle Datei via cp, docker-compose.yml).
- proxy-patches/routes.js: Side-Channel POST /interject {projectId,text}
  → subprocess.sendMessage der Kontext-Subprozesse.
- rvs: 'interject' erlaubt. bridge: RVS interject → Proxy /interject.
- App/Diagnostic: Zwischenruf-Buttons + lokale '📣 Zwischenruf'-Bubble.

Empirisch verifiziert: mid-turn injizierte Message wird an der naechsten
Tool-Grenze aufgegriffen (nicht mitten in einem blockierenden Befehl).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 05:05:13 +02:00
duffyduckandClaude Opus 4.8 4eeba3cf5f fix(files): Bilder korrekt zuordnen bei Queue (clientMsgId-Korrelation)
Die App schickt Datei (fire-and-forget) und Text (ACK-getrackt, bei Queue
verzoegert) als getrennte RVS-Nachrichten; die Datei trug keine clientMsgId.
Die Bridge mergte gepufferte Files rein per Timing an den naechsten Text →
bei mehreren schnellen Nachrichten landeten Bilder beim falschen Text.

App: file-Send traegt jetzt dieselbe clientMsgId wie der Text.
Bridge: puffert Files mit cmid und merged beim Text-Flush NUR die Files mit
passender cmid; Rest bleibt fuer seine eigene Nachricht gepuffert. Fallback
(Legacy/kein Treffer) = altes Verhalten, damit nie ein Bild verloren geht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 09:41:14 +02:00
duffyduckandClaude Opus 4.8 35d72f6cae fix(vnc): Bridge nutzt aria-net-Gateway statt host.docker.internal (VNC refused)
Kern des "Verbinde..."-Hangs: host.docker.internal loeste zu 172.17.0.1 (docker0)
auf, QEMUs VNC ist aber ans aria-net-Gateway 192.168.64.1 gebunden (dorthin
bindet der Brain) → Connection refused. Die Bridge ermittelt den VNC-Host jetzt
selbst per _docker_gateway() (/proc/net/route) = dasselbe Gateway wie der Brain.
ARIA_VNC_HOST-Env hat weiter Vorrang.

Live belegt: Bridge->192.168.64.1:5901 liefert RFB-Handshake, ->host.docker.
internal:5901 (172.17.0.1) = refused.

py_compile clean. Deploy: bridge rebuild.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 00:35:26 +02:00
duffyduckandClaude Opus 4.8 7e3b5c0dde feat: Satelliten — ARIAs Augen & Haende in fremden Netzen (Info-/Gateway-Aussenposten)
Neuer eigenstaendiger Container satellite/ (RVS-Client, network_mode host), den
man in einem beliebigen Netz (Buero etc.) deployt. Gibt ARIA Zugriff auf dieses
Netz ohne Haupt-Stack davor.

- satellite/satellite.py: RVS-Client + Discovery (mDNS/Zeroconf, SSDP/UPnP+DIAL,
  ARP) + Steuerung (dial.launch fuer YouTube-auf-FireTV, wol, http) mit Guards
  (CONTROL_ENABLED + Allowlist + Logging, token-gated, keine offenen Ports).
  Periodisches Re-Announce (sat_hello im Heartbeat) fuer spaet joinende Bridge.
- satellite/: Dockerfile, requirements, docker-compose (host-net), .env.example
  (SATELLITE_LOCATION als Adresse), README.
- rvs: sat_hello/discover/devices/command/result whitelisted.
- bridge: Satelliten-Registry (sat_hello) + Future-Relay (_satellite_request) +
  /internal/satellite + /internal/satellite-list (Muster wie flux).
- brain: Tools satellite_list/devices/command + _dispatch_satellite + Seed-Regel.

Alle py_compile + node -c gruen. Kein APK-Rebuild noetig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 23:22:30 +02:00
duffyduckandClaude Opus 4.8 ab25dad500 feat: geraetelokaler Voice-Projektwechsel ("geh in Projekt X")
Der Voice-Projektwechsel ist zurueck, aber nur fuer das ausloesende Geraet:
- App merkt sich die IDs eigener Anfragen (Text-clientMsgId via dispatchWithAck +
  Voice-audioRequestId an allen 4 Aufnahme-Stellen) in myRequestIdsRef.
- Bridge haengt an project_changed die ausloesende clientMsgId an — in beiden
  Voice-Pfaden: send_to_core (ARIAs project_enter/exit) und _process_endpoint_text
  (Voice-Router back_to_main / project_prefix).
- App folgt einem project_changed-Wechsel nur, wenn die clientMsgId eine eigene
  ist → andere App-Instanzen + Diagnostic bleiben unberuehrt.

Nur Bridge + App betroffen. py-compile + tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 22:47:38 +02:00
duffyduckandClaude Opus 4.8 471f624547 feat: Workspace-Umbau Schritt 7 — VNC-Live-Desktop durch RVS getunnelt
QEMU-VNC erscheint live in der App-Desktop-Kachel, komplett ueber RVS:
- Bridge: TCP↔RVS-VNC-Bruecke. vnc_open oeffnet asyncio-Verbindung zu
  host.docker.internal:5901, Reader-Loop streamt RFB-Bytes als vnc_data;
  vnc_input schreibt zurueck; vnc_close raeumt auf. check_desktop probt den
  Port und meldet desktop_status. Base64-in-JSON, kein websockify/noVNC auf
  dem Host noetig.
- App: novncHtml.ts laedt noVNC (CDN) und ersetzt window.WebSocket durch einen
  Shim, der RFB-Bytes per postMessage ueber RVS brueckt (server-speaks-first →
  timing-robust). VncTile mountet die WebView nur im Fokus, oeffnet bei 'ready'
  den Tunnel (desktop.openVnc), speist Server-Bytes ein und schickt Eingaben
  als vnc_input; beim Verlassen wird der Tunnel geschlossen (VM laeuft weiter).

tsc/py clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 00:02:04 +02:00
duffyduckandClaude Opus 4.8 0ffca86b88 feat: Workspace-Umbau Schritt 5 — Live-Code-Editor + code_file-Strom
Durchgaengiger Live-Editor fuer Code-Projekte:
- App: selbstenthaltener Highlight-Editor in einer WebView (editorHtml.ts,
  Textarea + Regex-Highlight-Layer, voll offline). CodeEditorTile mit Datei-Tabs,
  verdrahtet mit dem codeFile-Spiegel; Bridge-Protokoll setContent/applyPatch/
  setReadOnly ↔ onEditFromUser.
- Proxy-Hook (routes.js): faengt ARIAs Write/Edit/MultiEdit unter
  /shared/projects/<pid>/ ab und postet den Volltext bei Erfolg an
  /internal/code-file. tool_use_id→file_path-Korrelation, liest die Datei aus
  dem gemounteten /shared.
- Bridge: /internal/code-file relayt als RVS code_file an die App; eingehende
  code_file_edit schreiben den Volltext pfad-sicher nach /shared/projects/<pid>/
  (_write_project_file, kein Ausbruch via ..).

Arbeitsverzeichnis fuer Code-Projekte = /shared/projects/<projectId>/ (in proxy/
bridge/brain gemountet, kein SSH noetig). tsc/py/js clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:53:48 +02:00
duffyduckandClaude Opus 4.8 f780e286da feat(queue): Pro-Projekt-Nachrichten-Queue mit Rueckfrage-Loop + Textfeld-Entwuerfe (App)
Zweite Nachricht waehrend ARIA arbeitet wird jetzt ANGESTELLT statt den
laufenden Task abzubrechen. Stellt ARIA eine blockierende Rueckfrage, pausiert
die Queue und die naechste Eingabe beantwortet sie — bis eine finale Antwort
kommt, dann laeuft der naechste Queue-Eintrag (pro Projekt unabhaengig).

- Brain: ARIA deklariert Rueckfragen per unsichtbarem [[AWAIT]]-Marker
  (wie speak/converse; kein '?'-Raten). _extract_await_marker strippt ihn,
  chat() gibt 5-Tupel (+awaiting_reply), System-Prompt erklaert den Marker.
- Bridge: awaiting_reply aus Brain-Response in die chat-Broadcast-Payload.
- App: app-lokale Queue + Zustandsautomat (idle/running/awaiting_reply) pro
  Projekt; Send-Flow von Abbruch auf Anstellen; Stop-Button (cancelRequest)
  schaltet die Queue weiter; sichtbare pending_queue-Bubbles (tippen loescht);
  Rueckfrage-/Queue-Banner ueber dem Eingabefeld. Voice bricht nicht mehr ab
  (haltet nur TTS, serialisiert im Brain-Lock); Text-Send erkennt Brain-busy
  als Fallback. Pro-Projekt-Textfeld-Entwuerfe (Draft-Map + AsyncStorage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 09:35:46 +02:00
duffyduckandClaude Opus 4.8 e4e7a84a94 fix(app): ARIA-Datei-Anhang erscheint live an der Nachricht, nicht erst nach Seitenwechsel
Die Live-chat-Payload trug keine files — Anhaenge kamen nur als separates
file_from_aria-Event (eigene, teils unsichtbare Bubble). An der Text-Nachricht
tauchte die Datei erst nach einem Seitenwechsel auf (Reload aus chat_backup,
das die files kennt).

- Bridge: files jetzt direkt in der chat-Payload (selbe Struktur wie Backup).
- App: chat-Handler haengt payload.files an die Text-Bubble (wie der Reload-Pfad)
  und entfernt die redundante Solo-file_from_aria-Bubble mit passendem serverPath.
- App: file_from_aria legt keine Doppel-Bubble an, wenn die Datei schon an einer
  Nachricht haengt (Event-Reihenfolge-unabhaengig).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 02:01:37 +02:00
duffyduckandClaude Opus 4.8 31ec497675 fix(bridge): leeres <voice></voice> macht TTS nicht mehr stumm
Claude haengt manchmal reflexartig ein leeres <voice></voice> an (v.a. bei
Spotify-Antworten). clean_text_for_tts nahm dann group(1)='' → text='' → gar
keine Sprachausgabe. Das erklaerte, warum Playlist-Wechsel 'Fliegen' vorgelesen
wurde (Claude schrieb Inhalt ins Tag), 'Prodigy' aber stumm blieb (leeres Tag)
— reiner Claude-Output-Zufall, nicht Skill/Playlist-Name.

Fix: leeres/whitespace <voice> ignorieren und den normalen Anzeigetext lesen.
Deterministisch, unabhaengig von Claudes Tag-Reflex.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 13:40:36 +02:00
duffyduckandClaude Opus 4.8 d772939fb3 feat(local-llm): B1b-Plumbing — tools/tool_calls durch Adapter/Bridge/Brain-Client
Traegt OpenAI-Tool-Definitionen (tools) durch den ganzen lokalen Pfad und gibt
tool_calls zurueck:
- adapter.py: tools -> llama.cpp /v1/chat/completions (tool_choice=auto),
  message.tool_calls zurueck in llm_response.
- aria_bridge.py: _local_llm + /internal/local-llm reichen tools durch, geben
  tool_calls zurueck.
- local_llm.py: local_llm_chat akzeptiert tools, result enthaelt tool_calls.

Inert bis der Brain-Tool-Loop (naechster Schritt) tools uebergibt — Verhalten
unveraendert. Tool-Set + lokale Tool-Loop + Router-Anpassung folgen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 12:36:09 +02:00
duffyduckandClaude Opus 4.8 64fdf1ef43 feat(local-llm): B0 Consumer — Bridge-Relay + Brain-Client (spiegelt FLUX)
Brain → HTTP /internal/local-llm → Bridge → RVS → llm-adapter → llama.cpp,
1:1 nach dem FLUX-Roundtrip-Muster gebaut:
- Bridge: _pending_llm (requestId→Future), llm_response-Handler (setzt Future),
  _local_llm() (sendet llm_request, wartet mit 30s-Timeout), HTTP-Route
  POST /internal/local-llm ({messages, max_tokens?, temperature?, stop?}).
- Brain: local_llm.py mit local_llm_chat() — POSTet an die Bridge, gibt
  {ok, content, model?, elapsedMs?} zurueck, wirft nie (Aufrufer eskaliert
  bei ok=false auf Claude).

Provider-Kette bereits verifiziert (718ms). Als naechstes: Test brain→bridge→
gamebox end-to-end, dann B1 (Router-Heuristik + Escalation) und der
Diagnostic-Testchat/Status (B0.5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:11:49 +02:00
duffyduckandClaude Opus 4.8 925f8dc248 feat(voice): leeres <voice></voice> = bewusst stumm (Steuerbefehl-Quittungen)
Bisher: tts_text = tts_text_preview or text — ein leeres <voice></voice> fiel
auf den vollen Text zurueck und wurde doch gesprochen. Jetzt: ein vorhandener
<voice>-Tag ist die EXPLIZITE TTS-Vorgabe (auch leer). Leeres <voice></voice>
= Bubble sichtbar, aber KEIN TTS. Nur ohne Tag Rueckfall auf vollen Text.

Motivation: Spotify-Steuerbefehle (next/pause/…) laufen jetzt ueber den
Fast-Path (<1s), aber die gesprochene Quittung klaute auf dem Handy den
Audio-Fokus -> Spotify duckte/pausierte und spielte (bei kurzem Text) nicht
weiter. Steuer-Skills koennen ihre Reply nun mit <voice></voice> stummschalten.
Generell nutzbar: ARIA kann jede Antwort bewusst stumm halten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:03:50 +02:00
duffyduckandClaude Opus 4.8 3c530c9918 feat(multitask): kontext-getaggte Activity + kontext-scoped Cancel (Restpunkte 1+2)
Beide Restpunkte teilen eine Verkabelung: die projectId fliesst jetzt bis zum
Proxy und zurueck in die agent_activity-Events.

Restpunkt 1 — per-Kontext Activity-Indikator:
- Brain: proxy_client.chat_full(project_id) → payload.aria_project_id;
  agent.py gibt active_project_id mit.
- Proxy routes.js: liest aria_project_id, taggt Tool-/Stream-Hooks damit,
  trackt Subprozesse pro Kontext.
- Bridge: _emit_activity(project_id) + payload.projectId; /internal/agent-activity
  reicht projectId durch; send_to_core/_process_core_response taggen thinking/idle
  mit dem Turn-Kontext.
- App: agentActivityByCtx-Map; „ARIA denkt"-Indikator zeigt nur den
  fokussierten Kontext statt global zu flackern.

Restpunkt 2 — kontext-scoped Cancel (Barge-In):
- Proxy: neuer /cancel {projectId} killt NUR die Subprozesse eines Kontexts
  (_cancelByProject); /cancel-all bleibt fuers NOT-AUS.
- Bridge: soft cancel_request → _cancel_proxy_for_project(projectId) statt des
  toten Diagnostic /api/cancel. Hard bleibt /cancel-all.
- App: cancel_request + Abbrechen-Button tragen die fokussierte projectId.

Damit laufen Kontexte in der App echt parallel: Arbeit in Projekt A wird durch
Senden/Abbrechen in Hauptchat oder Projekt B nicht mehr abgewuergt, und der
Indikator gehoert zum sichtbaren Chat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 21:29:59 +02:00
duffyduckandClaude Opus 4.8 f91f3b3879 fix(projects): Anhaenge landen im gewaehlten Projekt statt im Hauptchat
Bug: Bild/Datei ins Textfeld + Frage → beides landete im Hauptchat, egal
welches Projekt fokussiert war. Der Anhang-Pfad reichte die projectId nirgends
durch (im Gegensatz zum reinen Text-Pfad).

App (sendPendingAttachments):
- lokale Anhang-Bubble bekommt projectId (App-Focus)
- file-Upload (rvs.send('file')) schickt projectId mit
- Merge-Trigger chat-Nachricht schickt projectId mit

Bridge:
- file-Handler liest payload.projectId, merkt sie (_pending_files_project_id)
  und taggt die Datei per _tag_file_to_project ins richtige Projekt
- _flush_pending_files_with_text(user_text, project_id): reicht die projectId
  aus dem chat-Payload an send_to_core (Fallback: gemerkter Upload-Kontext)
- _flush_pending_files_after (Files-only): nutzt den gemerkten Upload-Kontext
- merged-Aufruf im chat-Handler gibt payload.projectId mit

Damit tragen User-Bubble, Datei-Manifest, Brain-Turn und ARIA-Antwort alle
denselben Projekt-Tag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 20:38:47 +02:00
duffyduckandClaude Opus 4.8 c610b98818 fix(voice): Sprach-Nachricht landet im richtigen Projekt (Registry-Race)
Bug: bei manuellem Stop landete die Sprachnachricht (und ARIAs Antwort) im
Hauptchat statt im fokussierten Projekt — mal so, mal so, je nachdem ob der
Endpoint natuerlich kam oder man den Stop-Button drueckte.

Ursache: stt_stream_end popte die Focus-projectId-Registry SOFORT. Nach einem
manuellen Stop folgt aber noch der finale stt_endpoint (Whisper-Final-
Transcribe), der die projectId aus genau dieser Registry braucht. War sie schon
weg → focused_pid="" → Voice-Router faellt auf Hauptchat zurueck. Die
User-Bubble (lokal getaggt) blieb im Projekt, ARIAs Antwort kam aber mit
project_id="" → nur in EINEM Kontext sichtbar.

Fix: stt_stream_end raeumt die Registry nicht mehr sofort auf, sondern per
verzoegertem call_later(20s) als Leak-Schutz. Der stt_endpoint-Handler popt
selbst wenn er zuerst kommt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 00:31:48 +02:00
duffyduckandClaude Opus 4.8 9d67151d0f fix(diagnostic): ARIA-Datei-Bubbles (Bilder) tragen project_id — kein Ausblenden mehr
Regression aus dem chat_history-projectId-Fix: nach dem History-Reload laeuft
jetzt updateChatVisibilityByFocus(). addAriaFile() setzte aber kein
dataset.projectId → Datei-/Bild-Bubbles galten als Hauptchat und wurden
ausgeblendet, sobald der (aus localStorage wiederhergestellte) Focus auf einem
Projekt lag. In der App trat das nicht auf (datengetriebener Filter mit
korrektem projectId).

- addAriaFile(): setzt dataset.projectId aus p.projectId und respektiert beim
  Live-Anhaengen den aktuellen Focus (wie addChat via hiddenByFocus).
- chat_history-Renderer: reicht m.projectId an addAriaFile weiter.
- Bridge _process_core_response: haengt turn_pid als f["projectId"] an die
  file_from_aria-Payload, damit der Live-Pfad die Zuordnung kennt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 02:46:25 +02:00
duffyduckandClaude Opus 4.7 9988ffec62 fix(voice-router): Voice folgt App-Focus + „hauptmenü" als back-to-main
Zwei Bugs aus dem ersten Live-Test des Multi-Threading-Designs.

Bug 1 — Voice ignorierte App-Focus:
Stefan hat in Projekt X reingeguckt und was reingesagt — Message landete
im Hauptchat statt in X. Der Voice-Router auf der Bridge kannte den
sichtbaren Kontext der App nicht.

Fix:
- audio.ts.startStreamingRecording nimmt neuen opts.projectId und
  schickt es im stt_stream_start-Payload mit.
- ChatScreen.tsx: alle 4 startStreamingRecording-Callsites (wake,
  barge-in, passive, manuell) uebergeben focusedProjectIdRef.current.
  Neuer useRef-Spiegel damit die Focus-ID auch in useCallbacks/
  useEffects mit alten Closures aktuell bleibt.
- aria_bridge.py: neuer Handler fuer stt_stream_start speichert die
  projectId in self._stt_stream_projects[requestId], stt_stream_end
  loescht wieder. Beim stt_endpoint wird sie an _process_endpoint_text
  weitergereicht und dort als default_project_id in den Voice-Router.
- _apply_voice_router bekommt neuen Prio-Rank 4: „App-Focus als
  Default" — greift wenn kein Meta, kein Prefix und kein aktiver Sticky.
  So folgt Voice ohne extra Marker dem sichtbaren Kontext.

Bug 2 — Back-to-Main-Regex zu eng:
„zurück ins hauptmenü" wurde nicht als Meta erkannt (Regex matchte nur
„zurück zum hauptchat") und landete deshalb im aktiven Sticky-Projekt.

Fix: Regex akzeptiert jetzt auch hauptmenü, menü, haupt, main mit
Praepositionen „zum/zur/ins/in den".

Bonus — Burger-Button heller:
Stefan konnte den ☰-Toggle im Header kaum sehen. Farbe von Default
(dunkelgrau) auf #E0E0F0 (hell) mit fontWeight 700 gesetzt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-03 01:50:50 +02:00
duffyduckandClaude Opus 4.7 6ab5f78a5e feat(bridge): Voice-Router — 30s-Sticky + Meta-Command-Interception
Phase 4 vom Multi-Threading-Redesign — der Voice-Layer routet STT-Text
per-Projekt und lässt Meta-Kommandos gar nicht erst ans Brain.

Voice-Router in _process_endpoint_text():
- „zurueck zum hauptchat" / „hauptchat bitte" / „aria hauptchat"
  → Sticky reset, project_changed(exited) broadcasten, KEIN Brain-Call.
- „fuer <name>: <text>" (Fuzzy-Match auf Projekt-Namen ≥ 0.6 Score)
  → Sticky auf gefundene project_id + Rest des Texts geht ans Brain
  im Projekt-Kontext. project_changed(entered) broadcasten damit
  App/Diagnostic den Focus mit umschalten.
- Sticky-Timeout 30s: eine Voice-Message ohne Prefix innerhalb des
  Fensters bleibt im Sticky-Projekt, refresht das Timeout. Nach Ablauf
  → Default Hauptchat.
- Meta-Kommandos aendern KEINEN Brain-State — ARIAs Arbeit in laufenden
  Projekten wird nicht abgebrochen.

send_to_core wird jetzt mit dem gerouteten project_id gerufen; das Brain
bekommt den Text im richtigen Queue-Kontext.

Broadcast-Chain: Voice-Router setzt Sticky → project_changed geht via RVS
an App+Diagnostic → Focus-Header/Kontext-Strip wechseln automatisch.

Damit ist der komplette Multi-Threading-Redesign abgeschlossen:
- Brain: per-Request project_id + per-Projekt Queue + Queue-Aware Prompt
- Bridge: Chat-Routing + Voice-Router
- App: Focus-One + Drawer + Status-Dots
- Diagnostic: Kontext-Strip + Focus-Filter
- Voice: Sticky + Meta-Interception

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-02 20:59:02 +02:00
duffyduckandClaude Opus 4.7 56b503e2ee feat(brain): Multi-Threading via per-request project_id + per-project queue
Erster Schritt zum echten Multi-Threading fuer ARIA-Projekte. Kein globaler
active_project-State mehr — jeder /chat-Request sagt selbst welche Buehne
(project_id im Body). Verschiedene Projekte laufen parallel, gleiches
Projekt queued via asyncio.Lock.

Backend:
- ChatIn.project_id: Client bestimmt pro Request wohin. Bridge routet.
- /chat: async, holt per-Projekt asyncio.Lock. Requests fuers gleiche
  Projekt reihen sich in _project_pending ein, warten am Lock. Requests
  fuer verschiedene Projekte laufen echt parallel.
- Neuer /projects/queue-status endpoint: pro Kontext (inkl. Hauptchat
  unter __main__): busy True/False + queue_size. Fuers UI-Status-Dots.
- Agent.chat() nimmt project_id + pending_queue Params. Kein
  projects_mod.get_active() mehr im Hot-Path.

Queue-Aware Prompting:
- Wenn nach dem aktuellen Turn weitere Nachrichten in der Queue liegen,
  wird der System-Prompt um ein QUEUE-Segment erweitert mit Instruktion:
  „Bevor Du den aktuellen Task loesst, pruef die Queue — widerspricht/
  annuliert eine spaetere Nachricht? Dann Skip-Antwort statt Doppelarbeit."
- Beispiel: Task 'titelleiste rot' + Queue-Tail 'doch nicht, blau'
  → ARIA skipt rot, blau kommt als naechste Anfrage sauber durch.
- Kein extra LLM-Call — reine Prompt-Injection.

Project-Tools:
- project_enter/exit sind jetzt UI-Signale (App wechselt Ansicht via
  project_changed event), aendern KEINEN Brain-State mehr. Der aktuelle
  Turn bleibt in seinem Chat-Kontext.
- project_list zeigt keinen "AKTIV"-Marker mehr (nicht mehr sinnvoll).
- projects_mod.set_active/get_active bleiben als Legacy-Helpers (kein
  Aufruf mehr aus dem Hot-Path).

Bridge:
- send_to_core packt project_id in den /chat-Body.
- User-Backup-Eintrag tag't project_id sauber, keine Brain-Query mehr.

Naechste Schritte (kommende Commits):
- App: Focus-One-View mit Drawer + Status-Dots + OS-Push
- Diagnostic: Dashboard-Stack mit Karten
- Voice-Router: 30s-Sticky + Meta-Command-Interception im wakeword.ts

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-02 17:57:30 +02:00
duffyduckandClaude Opus 4.7 aa901db6d8 fix(projects): project_id im Chat-Backup persistieren + 1 Block pro Projekt
Zwei Stefan-Reports nach dem ersten Live-Test:

1. App-Reload verlor die Projekt-Bloecke
   - chat_backup.jsonl hatte keine project_id-Felder, also kamen die
     Bubbles als Hauptchat zurueck wenn die App ueber chat_history_response
     ihre History neu lud.
   - Fix: aria_bridge schreibt jetzt project_id in jeden Backup-Eintrag.
     Assistant-Reply via turn_pid (aus ChatOut.project_id); User-Message
     via payload.projectId (oder Brain-Status-Query als Fallback fuer
     Trigger-Replies / Diagnostic-Sends).
   - App: chat_history_response-Mapper liest m.project_id → ChatMessage.projectId.

2. Raus + rein in ein Projekt erzeugte einen zweiten Block am Ende
   - Vorher: Gruppierung bei aufeinanderfolgenden gleich-getaggten Bubbles.
     Hauptchat dazwischen hat den Block "unterbrochen", neuer Block.
   - Fix: neue reorderedMessages-Stufe sortiert Messages so um, dass alle
     eines Projekts contiguous werden, verankert am LATEST-Activity-
     Timestamp des Projekts. Genau EIN Block pro projectId — bei
     Re-Enter wandert der existierende Block ans Zeitende der Liste,
     die neue Bubble haengt unten in der Gruppe.
   - Hauptchat-Bubbles bleiben chronologisch zwischen den Projekt-Blöcken.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 09:36:11 +02:00
duffyduckandClaude Opus 4.7 224bb2289d fix(projects): ARIA-generierte Dateien dem aktiven Projekt zuordnen
Bisher haben nur App-Uploads (msg_type == "file") ein Projekt-Tag bekommen.
Dateien die ARIA waehrend des Turns selbst schreibt (via [FILE: /shared/
uploads/aria_xyz.pdf]-Marker) sind dem Hauptchat zugefallen, auch wenn
Stefan in einem Projekt war.

Fix: Beim Verarbeiten der ARIA-Antwort in _process_core_response wird die
turn_project_id aus payload.projectId (ChatOut.project_id vom Brain) ge-
nutzt um jede gefundene ARIA-Datei sofort zu taggen, bevor sie als
file_from_aria broadcast wird.

Helper-Split:
- _tag_file_to_project(path, pid): pure Write, pid schon bekannt
- _tag_file_to_active_project(path): Convenience-Wrapper, fragt Brain
  nach active project (genutzt vom App-Upload-Handler, der noch nichts
  vom Projekt-Kontext weiss)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 22:08:23 +02:00
duffyduckandClaude Opus 4.7 cd7b762a00 fix+feat(projects): Spinner-Bug, Back-Button, kollabierbare Chat-Bloecke, File-Filter
Drei Stefan-Bugs aus dem ersten Deploy-Test plus die fehlenden Polish-
Features fuer die Projekt-Funktion.

Fixes:
- ProjectsBrowser-Spinner-Hang: useRef-Pattern statt useCallback([onActive
  Changed]) — Parent uebergibt inline-arrow-Callbacks, neue Identitaet
  jedes Render → useCallback recomputes → useEffect refeuert → infinite
  Spinner. Fix: Ref-Bridge fuer Callbacks, useCallback mit empty deps.
- ChatScreen Banner: zusaetzlicher × Hauptchat-Button rechts (sichtbar
  nur wenn Projekt aktiv) — ein Tap und zurueck zum Hauptthread, ohne
  Modal-Umweg.

Features:
- Brain ChatOut.project_id: aktive Projekt-ID NACH dem Turn (kann
  durch project_enter/exit-Tools waehrend Turn gewechselt sein). Bridge
  liest sie aus dem /chat-Response und haengt sie an jeden ARIA-Chat-
  Broadcast als payload.projectId.
- App: ChatMessage.projectId-Feld. User-Bubbles werden mit aktiver
  Projekt-ID getaggt vor dem Senden (auch im RVS-Payload). ARIA-Bubbles
  kriegen die ID vom Bridge.
- App: Chat-Verlauf rendert aufeinanderfolgende Project-Messages als
  einklappbaren Block mit Header (▶/▼ + Projekt-Name + Count). Auto-
  Collapse beim Projekt-Wechsel (altes ein, neues aus), Default beim
  ersten Render: alle inaktiven Projekte eingeklappt.
- File-Manager Project-Tagging:
  - diagnostic/server.js: Manifest /shared/config/file_projects.json
    + /api/files-list returnt projectId pro Datei + neuer Endpoint
    /api/files-set-project.
  - bridge/aria_bridge.py: nach App-Upload Auto-Tag mit aktivem Projekt
    (Brain-Status-Query, best-effort fail-silent).
  - App SettingsScreen: scrollbare Projekt-Pill-Reihe als Filter, default
    auf aktives Projekt wenn vorhanden, sonst "Alle Projekte".
  - Diagnostic: zweites Dropdown im Files-Tab, baut Projekt-Optionen
    dynamisch aus /api/brain/projects/list.

Bewusst nicht drin (Folgeschritt):
- Per-File "Projekt zuweisen"-Action (Long-Press / Right-Click)
- Filter-Sync zwischen ChatScreen-Banner und SettingsScreen-Filter

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 21:55:02 +02:00
duffyduckandClaude Opus 4.7 6db59e0317 feat(projects): Threads im Hauptchat verankert (Stefan-Konzept)
Projekte sind benannte Thema-Bündel die voice-gesteuert via Brain-Tools
geöffnet/verlassen werden. Default-Mode bleibt der Hauptthread — Projekte
sind eine optionale Bühne. Anchored-not-replaced: App-Open landet immer
im Hauptchat, Projekte sind nur sichtbar wenn aktiv betreten.

Brain:
- projects.py: CRUD + Fuzzy-Find + Active-State-Pointer
  (/shared/config/projects.json + active_project.txt).
- conversation.py: Turn.project_id-Feld + window(project_id) Filter.
- agent.py: 6 Meta-Tools — project_create / _enter / _exit / _list /
  _summary / _end. chat() liest aktive Projekt-ID, taggt User+Assistant-
  Turns damit, filtert das LLM-Window auf Projekt-Kontext und ergaenzt
  den System-Prompt um den aktiven Projekt-Hinweis. touch_project pflegt
  last_activity_at + turn_count.
- main.py: REST-Endpoints /projects/{status,list,create,switch,
  {id}/end,{id}/archive, PATCH /{id}}.

Bridge + RVS:
- aria_bridge.py: project_changed Event-Propagation Brain → RVS-Broadcast
  damit App + Diagnostic ihre Banner refreshen.
- rvs/server.js: project_changed in ALLOWED_TYPES.

App:
- brainApi.ts: Project-Type + 6 API-Methoden.
- ProjectsBrowser.tsx (neue Komponente, ~340 Zeilen): Status-Header,
  Hauptchat als Erster-Eintrag, Projekt-Liste mit Aktiv-Marker, Long-Press
  zum Editieren, Modals fuer Neu/Edit/End/Archiv.
- ChatScreen.tsx: Banner unterhalb des Status-Bars zeigt aktives Projekt
  oder „Hauptchat" — Tap öffnet ProjectsBrowser als Modal. Aktive Projekt-
  Info wird bei Mount + bei project_changed-Events refreshed.
- SettingsScreen.tsx: Neue Section 📁 „Projekte" zeigt ProjectsBrowser inline.

Diagnostic:
- Neue Sektion im Brain-Tab mit Liste, Aktiv-Marker, Beenden/Archivieren
  pro Zeile, Modal fuer Neu. Lädt automatisch bei Brain-Tab + bei
  project_changed-Event-Broadcast.

Was bewusst NICHT drin ist (Folgeschritte):
- Per-Message Filter im Chat-Verlauf (zeigt aktuell alle Bubbles, Banner
  zeigt Kontext) — App müsste Chat-History per project_id filtern.
- Files-by-Project Tagging.
- Inline-Collapse-Bloecke im Chat-Verlauf.
- Sub-Projekte (Stefan-Entscheidung: weglassen, „Mama-tauglich").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 13:51:26 +02:00
duffyduckandClaude Opus 4.7 fbaf4b937d fix: 5er-Bundle — Wake-Word, Spotify-Latenz, File-Limit, Connection-Refused
- WakeWord Doppel-Trigger: detectionInProgress-Guard gegen Native-Event-
  Race + setBackground/setForeground statt setResumeCooldown im AppState.
- Media-Pause beim App-Oeffnen: 1.5s Startup-Suppression im Kotlin
  emitDetected() — Mikro-Spin-up-Spike triggert kein false-positive mehr.
- Spotify Fast-Path im Brain: einfache Media-Commands (naechster Track,
  pause, play, lauter, ...) matchen via Regex und gehen direkt aufs
  spotify-Skill statt durch Claude. ~1.5s statt 5-10s pro Befehl.
- File-Limit auf 1 GB hochgezogen (war 70 MB). RVS maxPayload +
  Bridge max_size auf 1500 MB; Node-Heap im RVS-Container auf 4 GB.
- TriggerBrowser / Datei-Manager Connection-Refused: brainApi._send
  fast-failt bei disconnected RVS statt 30s zu timeouten, und beide
  UIs reloaden automatisch beim Reconnect-Event.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 08:27:08 +02:00