Bisher musste Stefan bei OAuth-Flows manuell den Auth-Code aus der
Browser-URL kopieren (redirect_uri war localhost). Jetzt: RVS hat einen
HTTP-Listener auf demselben Port wie der WebSocket, Provider redirecten
nach Auth zu https://{RVS_HOST}/oauth/callback/{service}, RVS broadcastet,
aria-bridge forwarded, Brain matched state + tauscht code gegen Token.
Token-Refresh laeuft automatisch.
- rvs/server.js: hybrid http.createServer + WebSocketServer{noServer}.
Route GET /oauth/callback/{service}, broadcast oauth_callback an alle
Raeume, schoene Dark-Mode-HTML-Antwort an den Browser (Auto-Close 4s).
- bridge/aria_bridge.py: empfaengt oauth_callback, POSTet an Brain
/internal/oauth-callback.
- aria-brain/oauth.py: neuer Manager. Pending-Store mit state+TTL,
Token-Exchange (Basic-Auth oder Body je nach Provider), persistente
Speicherung in /shared/config/oauth_tokens.json (mode 0600),
Token-Refresh wenn <60s Restzeit. Vordefinierte Configs fuer Spotify,
Google, GitHub, Strava, Microsoft.
- aria-brain/agent.py: META-Tools oauth_authorize / oauth_get_token /
oauth_revoke.
- aria-brain/prompts.py: System-Prompt-Block zeigt ARIA die feste
Callback-URL als Quelle der Wahrheit + aktuelle Service-States.
- aria-brain/main.py: HTTP-Endpoints /oauth/services, /oauth/apps,
/oauth/authorize, /oauth/{service}/revoke, /internal/oauth-callback.
- diagnostic: neue Section "OAuth-Apps". Pro Service Karte mit Status,
client_id + client_secret (Passwort-Toggle), Speichern + Autorisieren-
Buttons. Authorize oeffnet Provider-Auth in neuem Tab.
- docker-compose.yml: brain-env um RVS_HOST + RVS_PORT_PUBLIC + RVS_TLS
ergaenzt (Brain braucht die Werte zum Bau der Callback-URL).
- .env.example: RVS_PORT_PUBLIC + Brain-Timeout-Vars (PROXY_TIMEOUT_SEC
+ Connect/Write/Pool) dokumentiert.
- README.md: OAuth-Pipeline + ARIA-Live-Mirror in Diagnostic-Section,
OAuth-Apps in Einstellungen-Tab erwaehnt.
- issue.md: OAuth-Pipeline + Brain-Timeout-Fix als erledigt dokumentiert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brain timed bei langen Pentests nach exakt 20:00 min raus, obwohl ARIAs
Subprozess fleissig weiterarbeitete und der Live-View alles zeigte.
Root-Cause: proxy_client.py hatte einen 1200s httpx.Client-Timeout —
genau der Wert, den wir vor 5 Tagen am Proxy auf 24h hochgezogen hatten.
Schicht uebersehen.
- docker-compose.yml: PROXY_TIMEOUT_SEC=86400 als brain-env.
- proxy_client.py: httpx.Timeout split (connect=10, read=86400, write=30,
pool=10). Toter Proxy wird in 10s erkannt, lange ARIA-Sessions duerfen
24h laufen.
- routes.js handleNonStreamingResponse: res.on("close") + isComplete-Flag.
Brain-Disconnect killt jetzt den Subprozess statt ihn verwaisen zu lassen.
- agent.py chat(): try/except — bei Exception nach dem User-Turn wird ein
Assistant-Error-Marker geschrieben, damit Conversation user->assistant
konsistent bleibt (kein Tool-Call-Loop-Fail in Folge-Calls).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pentests u.ae. brauchen oft >20min — der bisherige 20-min Hard-Cutoff
in claude-max-api-proxy's subprocess/manager.js killte den Subprocess
mitten in der Arbeit, egal wie aktiv ARIA gerade war.
Loesung:
- Hard-Timeout via sed auf 24h hochgesetzt (Last-Resort gegen wirklich
haengende Subprozesse).
- Eigener Idle-Watchdog in routes.js: Subprocess wird gekillt erst wenn
ueber ARIA_IDLE_TIMEOUT_MS (Default 20min) keine message/content_delta
Events ankommen. Jede Aktivitaet resettet den Timer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der claude-max-api-proxy Subprocess-Manager emittiert nur 'message',
'assistant', 'content_delta', 'result', 'error', 'close', 'raw' —
KEIN 'user'. tool_result-Blocks landen daher ausschliesslich im
generischen 'message'-Event mit type==='user'. Filter darauf statt
auf einen Event-Namen der nicht existiert, sonst kam in der ARIA-Live-
View nichts an.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
transformers 4.50+ registriert in integrations/moe.py einen torch.library
.custom_op mit String-Forward-References als Type-Annotations. Torch 2.4's
infer_schema kann diese nicht aufloesen ("Parameter input has unsupported
type torch.Tensor"), erst 2.5+ macht typing.get_type_hints() draus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aktuelles transformers schaltet PyTorch ab wenn < 2.4
("Disabling PyTorch because PyTorch >= 2.4 is required, found 2.3.1").
Ohne PyTorch laed diffusers das FLUX-Modell nicht. torchvision wird
zusaetzlich von CLIPImageProcessor/SiglipImageProcessor gebraucht.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Passwort-Feld in der FLUX-Section, mit Show/Hide-Toggle und kurzem
Hinweis-Link zu den HuggingFace-Schritten (Lizenz-Agree + Token-Erzeugung).
Wert wird in voice_config.json persistiert und per config-Broadcast an
die flux-bridge gepusht; dort vor jedem from_pretrained als HF_TOKEN +
HUGGING_FACE_HUB_TOKEN env gesetzt.
HF_TOKEN aus .env.example + docker-compose.yml entfernt. Auch FLUX_MODEL
aus compose raus — Default-Modell kommt jetzt komplett aus Diagnostic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stand vorher in der Sprachausgabe-Card — falscher Ort, weil
Bildgenerierung eigene Domaene ist. Neue settings-section zwischen
Sprachausgabe und Whisper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostic-Einstellungen fuer FLUX:
- Default-Modell (dev | schnell) — wird via RVS gepusht, flux-bridge
hot-swappt die Pipeline aus dem HF-Cache (~15-30s)
- Raw-Keyword (Default 'flux') — Pipe-Modus, Brain leitet Stefans Text
1:1 als prompt durch, kein Rewriting/Beautify
- Switch-Keyword (Default 'fix') — zwingt das ANDERE Modell als Default
Brain-Tool flux_generate um model + raw erweitert, System-Prompt-Block
mit den aktuellen Diagnostic-Settings + Whisper-Toleranz-Hinweis.
Kein eager Bootstrap-Load: flux-bridge wartet auf config oder ersten
Request. Bei erstem HF-Download zeigt Banner "laedt erstmalig runter"
mit Pfeil-Icon, Toast in der App wenn fertig.
FLUX_MODEL aus der .env entfernt (Steuerung jetzt komplett ueber
Diagnostic). HF_TOKEN-Kommentar erklaert warum trotz lokaler Inference
noetig (HF Gate-Mechanismus fuer FLUX.1-dev).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eigener Compose-Stack im /flux Verzeichnis (kann auf separater Maschine
laufen). aria-bridge routet flux_request via RVS, ARIA referenziert das
fertige PNG im Reply mit [FILE: ...]-Marker. Brain-Tool flux_generate
mit Caps fuer steps/dimension.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom (aus Bridge-Log): bei chat_history_request triggert die App
file_request fuer alle fehlenden Anhaenge. Bei einem 40 MB MP4 wird das
base64-encoded ~53 MB, ueberschreitet das RVS-maxPayload (50 MB).
Server droppt mit Code 1009 'message too big', Bridge crasht im cleanup
mit AttributeError 'NoneType has no call_soon' (websockets-Lib-Bug bei
nested context-manager-cleanup nach abgerissener Verbindung).
Drei Layer:
(1) RVS-Server: maxPayload 50 → 100 MB — deckt ~70 MB binaer ab nach
base64-inflate. Comment im server.js erklaert den Hintergrund.
(2) Bridge: max_size 50 → 100 MB synchron zum Server. PLUS pre-check
im file_request-Handler — Dateien > 70 MB werden mit Fehler-Response
abgewiesen statt blind base64-zu-encoden und die WS zu killen.
Limit knapp unter Server-Limit damit Bridge proaktiv blockiert.
(3) App: file_response-Handler liest 'error'-Feld aus dem Payload und
zeigt nen Toast 'Datei X: Datei zu gross fuer Transfer (40 MB,
Limit 70 MB)'. Statt einfach zu schweigen oder endlos zu retryen.
Crash bei websockets-cleanup ist ein Lib-Bug (NoneType.call_soon) —
nicht direkt fixbar, aber tritt jetzt nicht mehr auf weil Bridge proaktiv
die zu grossen Files ablehnt und die WS nicht mehr abreisst.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bridge fuegt User-Texten Praefixe in eckigen Klammern hinzu damit Brain
Kontext hat — z.B. '[Stefans aktuelle GPS-Position: 53.0, 8.5. Nutze die
nur wenn ...]' oder '[Hinweis: Stefan hat dich gerade unterbrochen...]'.
Die landeten via chat_backup auch in der App-Bubble — Stefan sieht jeden
Hint mit, hat nichts in der UI verloren.
Fix: App-side stripSystemHints() filtert aufeinanderfolgende `[...]`-
Bloecke am Textanfang inkl. Trennleerzeichen. Wird in renderMessage
angewendet, default an (Hints versteckt). Toggle in Settings →
Allgemein → 'Chat-Bubbles' kehrt's um falls Debug gewuenscht.
Brain bekommt weiterhin den vollen Text — Bridge-Side unveraendert.
Live-Toggle: Settings setzt aria_show_hints in AsyncStorage, ChatScreen
re-liest alle 2s (gleicher Mechanismus wie tts_enabled etc.).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostic loggte konstant '[gateway] Nicht verbunden — kann nicht senden'
weil die UI bei jedem Send-Klick noch versuchte ueber den OpenClaw-
Gateway-Pfad zu schicken. Den gibt's seit Monaten nicht mehr — alles
laeuft via Diagnostic → RVS → Bridge → Brain (HTTP).
server.js:
- sendToGateway() loggt nichts mehr (No-Op, returnt false)
- sendToRVS() raeumt den 'gateway + RVS dual'-Pfad weg, geht direkt
ueber RVS
- 'test_gateway'-Action vom Client wird umgeleitet auf RVS damit alte
Browser-Sessions noch funktionieren
index.html:
- 'Gateway senden'-Buttons (Chat-Test + Vollbild) entfernt, 'Via RVS
senden' umbenannt zu 'Senden'
- Gateway-Tab im Log-Viewer raus, mapSourceToTab leitet evtl. Reste
in den server-Tab um
- testGateway() + testGatewayFS() JS-Funktionen entfernt
- btn-gw-Disable-Logik raus
connectGateway/handleGatewayMessage/gatewayWs/state.gateway im server.js
bleiben als deprecated stehen — kein aktiver Code zugreift mehr drauf,
aber rauswerfen wuerde viele Diffs erzeugen ohne Nutzen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: App bekommt im minimierten oder display-gesperrten Zustand
nicht mit ob ein Anruf angefangen oder beendet wurde — TTS spricht
weiter waehrend Telefon klingelt, oder bleibt stumm nach Auflegen.
Zwei Ursachen:
1) Kotlin: TelephonyCallback war auf reactApplicationContext.mainExecutor
registriert. Wenn die Activity pausiert ist (display aus, App im
Hintergrund), wird der mainExecutor verzoegert oder gar nicht
abgearbeitet — Call-State-Events kommen nicht durch.
Fix: eigener Executors.newSingleThreadExecutor() — laeuft unabhaengig
vom UI-Thread solange der App-Prozess lebt (Foreground-Service
garantiert das).
2) TS: TelephonyManager-Listener kann nach laengerer Hintergrund-Zeit
verloren gehen (React-Bridge-Context recreated nach Resume).
Fix: neue refresh()-Methode in phoneCallService, AppState-Resume
ruft sie auf — wenn telephonyAttached=false ist, wird der Native-
Listener neu attached.
Plus: Status-Property telephonyAttached macht in Logs sichtbar ob
Pfad 1 (TelephonyManager) wirklich greift. Pfad 2 (AudioFocus fuer
VoIP) war nie betroffen, der laeuft komplett im Native-Code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Ohr aktiv, App im Hintergrund (jetzt mit Foreground-Service
permanent lebendig), nach laengerer Zeit oeffnet Stefan die App und sie
nimmt schon auf — angeblich Wake-Word getriggert. War aber TV/Husten/
sonstige Hintergrund-Geraeusche waehrend Stefan nicht da war.
Mit dem neuen Hintergrund-Modus laeuft openWakeWord jetzt permanent und
faengt jedes False-Positive im Hintergrund auf. Ohne dieser Fall war
das nicht moeglich weil die JS-Engine pausiert war.
Fix: Heuristik beim AppState-Resume in ChatScreen.tsx
- backgroundDauer wird gemerkt (lastBackgroundAt vs Resume-Zeit)
- Wenn >30s im Hintergrund UND state='conversing' UND letzter Wake-
Trigger juenger als 15s: false-positive — Aufnahme abbrechen + zurueck
zu armed
- Resume-Cooldown 1500 → 3000 ms (Audio-Spikes beim AppState-Switch
haben gelegentlich nach 1.5s noch nicht verklungen)
Neue Methoden:
- wakeword.ts: lastTriggerAt-Tracking + discardIfFreshlyTriggered(maxAge)
- audio.ts: cancelRecording() — bricht recorder ab ohne Result zu
emittieren, loescht die Audio-Datei
Setzt voraus dass Stefan nicht laenger als 30s im Hintergrund mit ARIA
spricht ueber Wake-Word. Falls doch: bei Resume waere die Aufnahme weg
und er muesste nochmal triggern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bisher pausierte Android nach ~30s im Hintergrund die JS-Engine.
WebSocket schlief ein, Trigger-Replies vom Brain kamen nicht durch,
Timer-Erinnerungen feuerten in der App nicht obwohl im Brain
ausgeloest. Nach laengerer Hintergrund-Pause warf Android den
Prozess ganz raus → beim Wiedereroeffnen Cold-Start, sah aus wie Crash.
Loesung: Foreground-Service mit persistenter Notification — die ist
ohnehin schon da fuer TTS/Mic-Aktivitaet (`AriaPlaybackService`).
Wir erweitern das Slot-System um einen `background`-Slot der dauerhaft
aktiv ist (Settings-Toggle, default an). Notification zeigt "ARIA aktiv
— Hintergrund-Modus" wenn nichts spezifisches laeuft, escaliert zu
"ARIA spricht/hoert" bei TTS/Mic. Tap → App.
Drei Dateien:
- services/backgroundAudio.ts: 'background' als 4. Slot (niedrigste
Prio, Fallback-Notification). Bestehende tts/rec/wake unveraendert.
- App.tsx: beim Start `acquireBackgroundAudio('background')` aufrufen
wenn Settings nicht explizit deaktiviert. Plus POST_NOTIFICATIONS-
Permission-Request (Android 13+).
- screens/SettingsScreen.tsx: neuer Toggle in Allgemein-Section.
Plus Hinweis auf Android-Akku-Optimierung-Whitelist falls trotzdem
was klemmt (manche Hersteller-ROMs killen aggressiv).
AndroidManifest unveraendert — foregroundServiceType="mediaPlayback|
microphone" deckt unseren Use-Case ab (ARIA spielt regelmaessig TTS
ab, was den Type rechtfertigt). Service stoppt sich selbst wenn alle
Slots leer sind, das passiert nur wenn der User in Settings den
Hintergrund-Modus deaktiviert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(1) Gedanken-Stream Modal: vorheriger Fix mit onStartShouldSetResponder
war falsch — der View wurde komplett zum Responder, die FlatList drin
bekam null Touch-Events. Jetzt: outer View ohne Touch-Handling, ein
separates TouchableOpacity-Element oberhalb des Sheets nur fuer den
Tap-Outside-Close. Sheet-View ist plain View → FlatList scrollt frei.
(2) Such-Sprung praeziser: drei Verbesserungen
- MAX_SCROLL_RETRIES 3 → 6: bei weiten Spruengen (Bubble #150 von
Position 0) braucht FlatList mehrere Iterationen bis die Items in
der Naehe gemessen sind
- Pre-Scroll-Offset: Fallback fuer unmeasured Items ist jetzt der
dynamische Mittel der bisher gemessenen Items (statt Pauschal-150).
Beim Cold-Start sind nur die untersten 10 gemessen, aber deren
Mittel ist immer noch eine bessere Schaetzung
- Render-Pause nach Pre-Scroll 200 → 350 ms: bei weiten Spruengen
braucht FlatList Zeit die Items zu mounten und onLayout zu feuern
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
App-Inbox-Modal:
- ScrollView der Top-Section ('Aus diesem Chat') nestedScrollEnabled=true
- MemoryBrowser darunter in einen flex:1-Wrapper gepackt damit er den
verbleibenden Platz bekommt — ohne den hat seine FlatList intern
null Hoehe gehabt und Scroll-Gestures verschluckt.
Diagnostic Trigger-Tab:
- ✎ Bearbeiten-Knopf pro Zeile (neben Aktivieren/Deaktivieren/Loeschen)
- Modal hat jetzt einen Edit-Modus: Type+Name disabled, Save-Button
zeigt 'Speichern', Modal-Title 'Trigger bearbeiten — <name>'
- Fuer Timer im Edit-Modus ein zusaetzliches Feld 'Feuert am (ISO, UTC)'
damit man den absoluten Zeitpunkt direkt aendern kann (statt 'in X
Minuten ab jetzt' das nur fuer Create Sinn macht)
- saveTrigger() unterscheidet jetzt zwischen Create-Modus (POST
/triggers/timer|watcher) und Edit-Modus (PATCH /triggers/{name})
- openTriggerEdit(name) fuellt das Modal mit Werten aus dem Cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Settings hatte zwei Probleme:
1) Gedächtnis-Liste scrollte nur runter, nicht hoch. Klassisches Android
nested-Scroll-Problem: aeussere ScrollView + innere FlatList mit
fixer height:600 = nur eine Richtung wird respektiert.
Fix: outer ScrollView mit scrollEnabled=false wenn die Section eine
eigene voll-hoch-scrollende Sub-Liste hat (memory/triggers). Plus
dynamische Hoehe via useWindowDimensions (winHeight - 220 statt
hardcoded 600) damit MemoryBrowser sauber den verfuegbaren Platz
nutzt.
2) Trigger waren bisher nur via Diagnostic-Tab editierbar — keine App-
side CRUD. Stefan wollte das.
Neu: TriggerBrowser-Komponente (analog MemoryBrowser-Struktur)
- Liste aller Trigger mit Filter (alle/aktive/inaktive)
- Toggle aktiv/inaktiv via Switch direkt in der Zeile
- Tap oeffnet TriggerEditModal (Nachricht/Condition/fires_at/intervals
editieren, Loeschen-Knopf mit Confirm)
- "+ Neu"-Knopf oeffnet TriggerNewModal mit Type-Switch (Watcher/Timer),
Watcher zeigt Hinweis auf verfuegbare Funktionen + Variablen
- Live Reload-Button, Meta-Info (fire_count, last_fired_at, ...)
brainApi um Trigger-Endpoints erweitert: listTriggers, getTrigger,
createTimer, createWatcher, updateTrigger (patch), deleteTrigger,
getTriggerConditions, getTriggerLogs. Plus Trigger-Type-Definition.
Settings-Liste hat eine neue Section "⏰ Trigger" zwischen Gedaechtnis
und Protokoll.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(1) Such-Treffer jetzt neueste zuerst (analog WhatsApp/Telegram). User
ist visuell unten, der erste Sprung landet meist im Viewport ohne
weiten Pre-Scroll (= weniger Cold-Start-Fail-Risiko). „Naechster"
geht in die Vergangenheit. Plus Pre-Scroll-Wartezeit 80→200 ms damit
FlatList beim ersten Versuch wirklich Zeit zum Rendern hat.
(2) SettingsScreen Ueber-Text: `—` wurde literal gerendert weil
JSX-Text-Knoten keine JS-String-Escapes interpretieren. Fix:
`{'—'}` als JS-Expression-Block.
(3) GPS-Tracking sendete nach der initialen Position nichts mehr wenn
der User stationaer war — `distanceFilter: 30` blockiert
watchPosition-Updates ohne Bewegung. Nach 5 min (NEAR_MAX_AGE_SEC)
verwirft das Brain die Position als veraltet → near()-Watcher feuern
nie. Stefan's DRK-Trigger waren so chronisch tot.
Fix: zusaetzlich zum watchPosition laeuft ein setInterval(60s)
Heartbeat der die zuletzt empfangene Position erneut sendet. Kein
extra GPS-Wakeup — akkufreundlich. Damit bleibt der Brain-State
frisch auch bei stationaerem User; near() funktioniert sobald der
User tatsaechlich im Radius ist.
Anmerkung zu Stefan's konkretem Test: er war 1.5–2 km von den DRK-
Triggern entfernt (Radius je 300 m) — selbst mit frischen GPS-Updates
haetten die nicht gefeuert. Der Heartbeat-Fix ist trotzdem noetig
damit Trigger ueberhaupt eine Chance haben wenn er tatsaechlich dort
vorbeifaehrt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Suche nach 'cessna' sprang zur Oberhausen-Bubble (~15 Bubbles
daneben), egal welcher Versuch.
Zwei Ursachen:
1) searchMatchIds suchte in `messages` (alle Bubbles inkl. Memory/Skill/
Trigger-Spezial-Bubbles), aber gescrollt wird in `invertedMessages`
die diese filtert. Wenn 'cessna' nur in einer Memory-Bubble vorkam,
war die ID in searchMatchIds aber nicht in invertedMessages →
findIndex=-1 → kein Scroll, Pre-Scroll-Offset von voriger Aktion
blieb sichtbar. Fix: searchMatchIds aus chatVisibleMessages.
2) AVG_BUBBLE_HEIGHT=150 als Pauschalschaetzung war zu grob — Voice-
Bubbles sind ~70 px, lange ARIA-Antworten 400+. Pre-Scroll-Offset
landete bei langen Listen weit daneben. Fix: itemHeights-Ref-Map
wird per onLayout in renderMessage gefuettert. Pre-Scroll summiert
echte gemessene Hoehen (Fallback AVG fuer noch nicht gerenderte) —
beim zweiten Such-Versuch lernt der Cache, beim ersten klappt's
schon besser als mit dem Pauschalwert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Suchbegriff direkt nach App-Start eingegeben → springt an
falsche Stelle. Erst beim zweiten Versuch funktioniert es.
Ursache: FlatList rendert per Default nur 10 Items initial.
info.averageItemLength im onScrollToIndexFailed basiert nur auf diesen
10 — bei einem Suchtreffer auf Bubble 150 ist die Schaetzung katastrophal
falsch. Beim zweiten Versuch ist die FlatList „warm gelaufen" und mehr
Items sind gemessen → Schaetzung passt besser.
Drei kombinierte Fixes:
1) Pre-Scroll: vor dem scrollToIndex erst grob mit AVG_BUBBLE_HEIGHT=150
per scrollToOffset(idx*150) in die Naehe springen. FlatList rendert
die Bubbles in der Naehe, dann praezise nachsetzen nach 80ms.
2) initialNumToRender=30 (Default 10) — mehr Items beim Mount gemessen.
3) windowSize=41 (Default 21) — mehr Items im Speicher gehalten, weniger
Layout-Holes beim Weit-Scroll.
Kosten: minimal hoehere Mount-Zeit. Bei 300+ Bubbles im Backup macht
sich der UX-Gewinn lohnt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Suche zeigt Treffer, springt aber permanent zwischen Bubbles
hin und her in Endlosschleife.
Zwei Ursachen, beide angeschlossen:
1) agent_activity-Handler rief setMessages mit prev.map() — auch wenn
keine sending-Bubble da war. Das erzeugte trotzdem ein neues Array
bei jedem Tool-Event (5-10x pro Brain-Call). invertedMessages neu →
FlatList-Layouts invalidiert mitten in einer aktiven Scroll-Sequenz.
Fix: prev.some() vor map() — wenn nichts zu aendern ist, prev
unveraendert returnen (reference-stable, kein Re-Render).
2) onScrollToIndexFailed retried unbegrenzt. Jeder failed Retry rief
den Handler erneut auf → neuer setTimeout → neuer Versuch → fail →
loop. Vorher waren cascading 3 Retries, dann auf 1 reduziert um
den 3-9-27-Cascade zu fixen, aber EIN ungebremster Retry-Schluss
pro fail bleibt eine Endlos-Schleife wenn Layouts nie stabil
werden. Fix: harter Counter (MAX_SCROLL_RETRIES = 3). Counter wird
bei jedem neuen Search-Hit via clearPendingScrollRetry resettet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der innere TouchableOpacity (eigentlich nur da um Tap-Propagation an
das aeussere close-on-tap-outside-Wrapper zu blocken) hat alle Touch-
Events konsumiert — FlatList bekam nichts ab, kein Scroll moeglich.
Fix: inner durch View ersetzen, mit onStartShouldSetResponder=true
plus onResponderTerminationRequest=false. Das blockt die Propagation
ohne Scrolls der Children zu verschlucken.
Close-on-Tap-outside funktioniert weiter (aeusseres TouchableOpacity
bleibt), das X im Header schliesst auch, Hardware-Back ebenfalls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: ARIA bearbeitet die Nachricht (im Gedanken-Stream sichtbar),
aber unter der User-Bubble bleibt die Sanduhr stehen und nach ~90 s
springt sie auf ⚠ failed. ARIA-Antwort kommt trotzdem irgendwann durch
— die Bubble war also nie weg, nur visuell schief.
Wurzel: chat_ack vom Bridge kam offenbar in manchen Faellen nicht
verlaesslich an. ACK-Timer (30 s × 3 Retries) lief durch → 'failed'.
Fix: agent_activity = thinking/tool/assistant ist impliziter Beweis,
dass das Brain die Nachricht bekommen und angefangen hat zu arbeiten.
Beim ersten non-idle Event:
- alle laufenden ACK-Timer cancelen
- alle 'sending'-User-Bubbles auf 'sent' (✓) setzen
ARIA-Reply markiert dann wie gehabt 'delivered' (✓✓). Damit kann keine
Bubble mehr auf failed gehen waehrend Brain noch laeuft.
Plus: ACK_TIMEOUT_MS 30 → 60 s als Backup-Reserve fuer den Fall dass
weder ACK noch agent_activity ankommt (sehr unwahrscheinlich, aber
billig).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug-1: _append_chat_backup nutzte asyncio.get_event_loop().time() —
das ist Container-Monotonic (bei Restart wieder 0), NICHT UNIX-Zeit.
Bridge schrieb so Eintraege mit ts wie 394M (=6.5 min Uptime), App-side
generiert User-Bubbles mit Date.now() = 1.778e12. Beim Sortieren in
der App: Server-Bubbles landeten alle als "uralt" (kleine ts) ueber den
lokalen Bubbles und teilweise unter dem 500er-Cap raus — Symptom:
"alles nach Hello Kitty fehlt in der App".
Fix: _append_chat_backup nutzt jetzt time.time() * 1000 (UNIX-ms).
Bug-2: doppelte User-Bubble nach App-Hintergrund/Restart mit Retry-Knopf.
Race-Fix von vorhin (text+timestamp-Heuristik, 5-Min-Fenster) griff
nicht weil bei kaputten Server-ts (394M) und lokalen UNIX-ms (1.778e12)
das Diff 1.7 Billionen ms war → Fenster nie zutreffend → lokale Bubble
blieb als Duplikat.
Fix: Text-Match alleine reicht — wenn der Server irgendwo eine
textgleiche User-Bubble hat, ist es dieselbe Nachricht. Greift jetzt
unabhaengig von ts-Konsistenz.
Plus: tools/migrate_chat_backup_ts.py — repariert vorhandene jsonl
(284 von 299 Eintraege auf der VM hatten Container-Uptime-ts). Datei-
Reihenfolge bleibt erhalten (war eh chronologisch), ts werden ab File-
Mtime rueckwaerts 60s-Schritten vergeben. Idempotent, .bak-Backup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Proxy-Patch hookt Claude-CLI `assistant`-Events: bei jedem tool_use-
Block (Bash, Read, Edit, Grep, ...) wird per HTTP-POST an die Bridge
gemeldet. Bridge spiegelt das als `agent_activity tool=<name>` an die
RVS-Clients. App- und Diagnostic-Gedanken-Stream zeigen damit live mit
was ARIA gerade macht — vorher kam pro Brain-Call nur EIN „💭 denkt"
am Anfang und EIN „✓ fertig" am Ende.
Drei neue Bausteine:
- proxy-patches/routes.js: kompletter Replacement der npm-Version mit
`_attachToolHook(subprocess)` — feuert pro tool_use-Block ein HTTP-
POST an http://aria-bridge:8090/internal/agent-activity (URL via
ARIA_TOOL_HOOK_URL Env-Variable ueberschreibbar). Fire-and-forget,
fail-open — Brain-Call bricht NICHT ab wenn Bridge mal nicht da ist.
- docker-compose.yml: vierter cp-Schritt im proxy-Service kopiert
routes.js ueber die npm-Version (analog zu openai-to-cli + cli-to-
openai).
- bridge/aria_bridge.py: neuer `/internal/agent-activity`-Endpoint im
bestehenden _serve_internal_http. Plus _emit_activity hat jetzt
force=True-Param damit wiederholte gleiche Tool-Aufrufe (3x Bash in
Folge) als drei Eintraege im Stream sichtbar bleiben.
App + Diagnostic: pushThought-Dedup laesst tool-Events durch (3x Bash
hintereinander gibt 3 Eintraege im Gedanken-Stream).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live-Diagnose nach dem Timeout-Bump: Bridge-Brain-Call rennt jetzt zwar
20 Min — aber nach ~4 Min droppt der RVS-Server die WebSocket-Verbindung.
Symptom in App+Diagnostic: "denkt einfach abgebrochen".
Ursache: `async for raw_message in ws: await _handle_rvs_message(...)` —
das await blockt den recv-Loop solange send_to_core laeuft (bis zu 20
Min). Der mobil.hacker-net.de:444 RVS-Server droppt Verbindungen ohne
echte App-Frames nach ~4 Min als idle-Timeout. Die websockets-Lib
beantwortet Pings im Hintergrund, aber das reicht offenbar nicht — der
Server zaehlt nur Application-Frames.
Fix: chat-Handler ruft send_to_core als asyncio.create_task statt await.
Brain laeuft im Hintergrund-Task, RVS-recv-Loop bleibt frei, neue
Messages werden weiter verarbeitet, Verbindung bleibt lebendig. Gleicher
Fix in _flush_pending_files_with_text und file-empty-Edge-Case.
Tradeoff: parallele Brain-Calls wenn der User waehrend einer laufenden
Antwort schnell mehrere Nachrichten schickt. Brain (FastAPI) verarbeitet
beide, conversation.jsonl koennte racen. App macht aber bereits Barge-In
via cancel_request bei Folge-Nachrichten — in der Praxis treffen sich
parallele Calls selten. Wenn doch Probleme: Bridge-Side asyncio.Lock um
send_to_core in einer Folge-Etappe.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multi-Tool-Sessions chronisch gekappt
Live-Diagnose auf der VM: drei verkettete 5-Min-Timeouts feuern bei
jedem laengeren Brain-Call exakt gleichzeitig:
06:16:02 Brain → Proxy /v1/chat/completions
06:20:53 Bridge kappt (4m51s, urlopen timeout=300)
06:21:02 Brain bekommt HTTP 500 vom Proxy ('timed out after 300000ms')
Stefan's Karten-Rekonstruktion (curl gegen Nominatim/OSRM + viele Bash-
Tool-Calls + DB-Inserts) braucht locker 8–15 Min — alle Brain-Calls
ueber 5 Min sind reihenweise mit 'Brain-Fehler: timed out' verreckt,
auch wenn die Arbeit zu 80% durch war.
Drei Stellen patchen:
- bridge/aria_bridge.py: urlopen 300 → 1200 (20 Min)
- aria-brain/proxy_client.py: PROXY_TIMEOUT_SEC default 300 → 1200
- docker-compose.yml: dritter sed-Patch im proxy-Service
setzt DEFAULT_TIMEOUT im claude-max-api-proxy von 300000 auf 1200000
Plus App-Watchdog: 180s → 1260s (21 Min, knapp ueber Brain-Timeout)
damit der lokale Stuck-Watchdog nicht waehrend legitimer langer
Sessions feuert. Echte Verbindungsabbrueche kappen vorher per WS-
Disconnect.
UX-Tradeoff bewusst akzeptiert: User sieht jetzt bis zu 20 Min nur
'ARIA denkt...' ohne Zwischen-Updates. Echte Loesung waere Streaming
oder async-Job-API (siehe Etappe B/C im Vorschlag) — das ist groesseres
Refactoring, hier reicht erst mal der Quick-Fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persistentes chronologisches Log was ARIA intern macht — gefuettert aus
agent_activity-Events (thinking/tool/assistant/idle). Bleibt zwischen
Denk-Phasen stehen, neue Eintraege kommen unten dran, lange Pausen
werden mit Trennlinie + Minuten-Hint sichtbar gemacht.
App (ChatScreen.tsx):
- 💭-Icon in der Statusleiste neben 🗂️ und 🔍, zeigt Eintrags-Anzahl
- Bottom-Sheet (60% Hoehe) mit chronologischer Liste, Tap auf Hintergrund
schliesst, 🗑-Confirm zum Leeren
- Persistierung in AsyncStorage (aria_thought_stream, capped 500)
- Dedup gegen direkt aufeinanderfolgende identische Events
Diagnostic (index.html):
- 💭 Gedanken-Button im Chat-Test-Header neben „Vollbild"
- Zentrales Modal (720px x 70vh), Live-Update wenn neue Eintraege kommen
(autoscroll ans Ende), 🗑 Leeren-Button mit Confirm
- Persistierung in localStorage, gleiche cap/dedup-Logik wie App
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prompt sagte 'Harte Regel — IMMER Skill anlegen wenn pip-Library
noetig'. ARIA hat das wortwoertlich genommen: bei einer einfachen
pdf-extract-Frage hat sie sofort skill_create gerufen → Brain blockiert
12 Min im venv+pip-Install-subprocess.run, App zeigt 'ARIA denkt',
Diagnostic emitted nach 5 Min Timeout idle, Stefan blieb stundenlang
ohne Antwort.
Neue Regel:
- Goldene Regel: NIE ungefragt Skills anlegen.
- Aufgabe zuerst inline loesen (Bash, direkter pip install, Workaround).
- Skill nur wenn Stefan EXPLIZIT sagt 'mach daraus einen Skill' /
'leg den als Skill an'.
- Die vier Kriterien (wiederkehrend/nicht-trivial/parametrisierbar/
wiederverwendbar) sind jetzt Checkliste NACH expliziter Anfrage —
fehlt eines, soll ARIA nachfragen statt blind anzulegen.
- Begruendung steht jetzt im Prompt: Setup blockt Brain bis zu 12 Min.
Greift auf der VM ohne Re-Build, prompts.py wird beim Start geladen
(docker compose restart aria-brain reicht).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Race nach Etappe-3-Reconnect-Fix: lokale failed-Bubble (mit clientMsgId)
und Server-Backup-Eintrag (ohne clientMsgId, aus alter Bridge-Version)
landeten beide im Merge → User sah Doppelpost: einmal ueber der
ARIA-Antwort (Server), einmal mit Retry-Knopf darunter (lokal). Plus
ACK-Timer konnte weiterlaufen obwohl die Bubble schon delivered war —
Retry pushte den Status zurueck auf sending und nach 30 s auf failed.
App:
- chat_history_response-Merge faellt zusaetzlich auf text+timestamp-
Heuristik im 5-Min-Fenster zurueck wenn die Server-Bubble keine
clientMsgId hat → lokale Kopie wird verworfen, kein Doppelpost
- messagesRef + dispatchWithAck prueft vor Send/Retry ob die Bubble
bereits delivered ist → kein verspaetetes failed mehr
- ARIA-Reply cleart ALLE laufenden ACK-Timer (Bridge hat unsere
Messages ja offensichtlich verarbeitet)
Docs:
- issue.md: neuer Block 'Chat-Stabilitaet' mit den drei Etappen +
beiden Race-Fixes; AsyncStorage-Race-Punkt aus 'Offen' abgehakt
- README.md: Chat-Such-Zeile aktualisiert (highlight statt filter),
Jump-to-Bottom + Delivery-Status-Bubbles dokumentiert
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Race-Bug nach Etappe 3: Beim Reconnect schickt die App parallel
chat_history_request und (via flushQueuedMessages) die offline gestaute
Nachricht. Die history_response kam an bevor die Bridge die Bubble in
chat_backup.jsonl geschrieben hatte → Server-Liste ohne unsere Bubble →
Merge ersetzte den lokalen Stand → Bubble weg (im Diagnostic war sie
gleich danach drin).
Bridge: _append_chat_backup nimmt clientMsgId mit auf. send_to_core
reicht sie als kwarg durch (chat- und audio-Pfad).
App: chat_history_response-Merge dedupt per clientMsgId. Lokale User-
Bubbles deren clientMsgId der Server noch nicht kennt bleiben erhalten
(localOnly-Filter erweitert). Server-User-Bubbles mit clientMsgId
kriegen deliveryStatus='delivered' damit das ✓✓ auch nach Reload sichtbar
bleibt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei Etappen Chat-Fixes:
Etappe 1 — Such-Scroll permanent springen weg:
- invertedMessages raus aus dem useEffect-Deps; neue ARIA-Nachrichten triggern den Scroll-Effect nicht mehr. Aktueller Snapshot via Ref.
- onScrollToIndexFailed: statt 3 cascading Retries (120/320/600ms) nur noch EINE Retry nach 300ms. Cascading-Retries waren der Endlos-Cascade-Bug (jeder Failed-Retry triggerte 3 weitere).
Etappe 2 — AsyncStorage-Race + Stuck-Thinking:
- Init-Load merged statt overwrite — Nachrichten die zwischen Mount und Load-Done reinkommen werden nicht mehr verschluckt.
- Stuck-Thinking-Watchdog: 180s ohne agent_activity-Update → Auto-Reset auf idle + Timeout-Bubble. Gegen "App haengt auf 'ARIA denkt'".
Etappe 3 — Delivery-Handshake (WhatsApp-Style):
- Pro User-Bubble: clientMsgId + deliveryStatus (queued/sending/sent/delivered/failed).
- Offline-Queue: Send waehrend disconnected → 'queued' → flush bei Reconnect.
- Bridge sendet chat_ack zurueck → Bubble auf 'sent' (✓).
- ARIA-Reply → alle vorigen User-Bubbles 'delivered' (✓✓).
- ACK-Timeout 30s, bis zu 3 Retries, danach 'failed' (rotes Tap-fuer-Retry).
- Bridge: LRU-Idempotenz (200 cmids) verhindert Doppelte beim Retry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei kleine UX-Fixes im Chat:
1. Jump-Down-Button (↓): Bei inverted FlatList erscheint rechts ueber
der Eingabe ein blauer FAB, sobald man mehr als 250px von der
neuesten Nachricht weg gescrollt ist. Tap → scrollToOffset(0)
animated → wieder unten. Auto-hide wenn man unten ist.
2. Such-Sprung landet jetzt am TEXT-ANFANG der Treffer-Bubble:
viewPosition 0.5 (Mitte) → 0 (Item-Top am Viewport-Top). Plus
Retry-Folge (180/420/800ms) gegen Layout-Race bei langen Listen.
Vorher musste man oft nochmal hoch scrollen um den Anfang zu sehen.
onScrollToIndexFailed-Fallback genauso mit viewPosition 0.
3. issue.md: "Bilder: Claude Vision direkt nutzen" raus aus den
offenen Punkten — ist durch Stufe E (Memory-Anhaenge, Read-Tool
multi-modal) längst geloest. ARIA sieht Bilder echt.
Folge-Etappen: Such-Sprung-Resilienz war Teil davon (mehrere Retries
abgedeckt). Naechste Brocken: Doppel-Send-Haenger, AsyncStorage-Race,
Offline-Queue mit Idempotenz.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README.md:
- Diagnostic-Trigger-Tab-Beschreibung erweitert um die drei GPS-Funktionen
(near / entered_near / left_near) mit Use-Cases pro Modus
- Plus Auflösung erklaert: 8s-Tick + event-getrieben bei location_update
fuer Auto-Vorbeifahrten. 5-min-Age-Schutz gegen Phantom-Fires
- Phase B Punkt 5 in der Roadmap entsprechend nachgezogen
issue.md: neuer Block "GPS-Trigger-Verbesserungen" mit drei Punkten —
Timing-Fix, Age-Schutz, drei Modi.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: bei aktuellen near()-Watcher gibt's nur "solange drin". Reale
Szenarien wollen aber differenzieren:
- VORWARNUNG vor Ziel (Blitzer-Warner 2 km vorher) → entered_near mit grossem r
- ANKUNFT exakt am Ziel → entered_near mit kleinem r
- VERLASSEN (Parkplatz, hast du was vergessen) → left_near
- KONTINUIERLICH-DRIN (bin noch in der Naehe?) → near (Default, throttled)
Zwei neue Funktionen in der Condition-Whitelist:
- entered_near(lat, lon, r): True NUR im Moment des Uebergangs
draussen → innen. Fires einmal pro Eintritt.
- left_near(lat, lon, r): True NUR im Moment des Uebergangs innen →
draussen. Fires einmal pro Austritt.
State-Tracking:
- pro Trigger pro near-Aufruf wird der letzte Auswertungs-Wert (true/
false) im Watcher-Manifest gespeichert (Field "near_states", Key
"lat.6,lon.6,radius"). Background-Loop liest's vor dem Eval, gibt's
per collect_variables(prev_near_states=...) in die Closure, schreibt
nach dem Eval die neuen Werte zurueck — UNABHAENGIG ob gefeuert
wurde, sonst greift die Uebergangs-Erkennung nicht.
Background _tick:
- Aufteilung in Watcher-Pass (mit prev_near_states pro Trigger) und
Timer-Pass (ohne State, gemeinsame vars). Bisher war collect_variables
einmal pro Tick — jetzt einmal pro Watcher. Disk-Stats sind teuer
aber unter 30 Watchern unkritisch; bei mehr koennen wir cachen.
ARIA-Tool-Description erweitert (trigger_watcher): erklaert die drei
Modi mit Use-Cases und empfohlenen Throttle-Werten (kurz fuer entered/
left, lang fuer near).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan ist mehrmals an einem 300m-near()-Watcher (DRK Kreyenbrueck)
vorbeigefahren, kein Fire. Ursache: Background-Loop tickte alle 30s,
Auto-Durchfahrt durch 600m-Durchmesser-Radius dauert bei 50-120 km/h
nur 18-43 Sekunden — der Tick konnte komplett dazwischen liegen.
Drei Fixes (A + B aus Stefans Vorschlag):
A1. Background-Loop-Frequenz: TICK_SEC 30 → 8.
Garantiert mind. 2 Checks auch bei 120 km/h durch 300m. Loop ist
billig (paar Dateilesungen + AST-Eval), Brain merkt das nicht.
A2. near() bekommt Age-Schutz (watcher.py NEAR_MAX_AGE_SEC=300):
Wenn location_age_sec > 5 min, gilt die Position als unbekannt
und near() liefert False. Verhindert Phantom-Fires wenn Tracking
aus ist oder Mobilfunk weg war — vorher haette der letzte
bekannte Wert weiter ausgewertet werden koennen.
B. Event-getriebener Tick:
- background.py: tick_now()-Funktion + Module-Slot fuer
agent_factory damit man von ausserhalb des Lifespan-Pfads
einen Tick triggern kann
- main.py: POST /triggers/check-now Endpoint ruft tick_now()
- bridge: _persist_location feuert nach jedem Save ein fire-and-
forget POST /triggers/check-now (run_in_executor, timeout 8s,
blockt nichts wenn Brain stockt)
Damit fires near() sofort wenn die App ein location_update schickt —
Polling ist nur noch der Fallback fuer Watcher OHNE GPS-Bezug
(disk_free, hour_of_day etc.) und als Sicherheits-Tick falls
location_update mal ausfaellt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Heute Tag-2 nach dem Memory-Editor-Hauptbau:
issue.md: neuer Block "App-Memory-Editor + Crash-Reporting" mit 8
Punkten (Bubble-Header dynamic, Tap-Modal, Inbox, Settings-Editor,
RVS-Brain-Proxy, App-Crash-Reporting, memory_search+update Tools,
Bugfixes-Cluster).
README.md:
- App-Features um Notizen-Inbox + Memory-Editor + Bubble-Header
dynamic + App-Crash-Reporting ergaenzt
- Roadmap um "Memory-Editor in der App" und "App-Crash-Reporting via
RVS" als eigene Bullets — beide sitzen unter dem letzten
Memory-Anhaenge-Eintrag und schliessen damit den App-UX-Loop:
ARIA hat jetzt im Diagnostic UND in der App vollwertiges Memory-
CRUD inkl. Anhaenge, plus Crashes sind ohne ADB diagnostizierbar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: Memory-Liste in Settings → Gedaechtnis-Sektion laesst sich
nicht scrollen. Klassisches FlatList-in-ScrollView-Problem auf
Android: die aeussere ScrollView (Settings-Screen-Container) faengt
alle Gesten ab, die innere FlatList (MemoryBrowser) bleibt regungslos.
Fix:
- MemoryBrowser FlatList bekommt nestedScrollEnabled={true}
- SettingsScreen-aeussere-ScrollView ebenfalls nestedScrollEnabled
- Plus keyboardShouldPersistTaps="handled" damit Taps auf Filter-
Buttons nicht von der Tastatur weggefangen werden
In der Inbox-Modal-Nutzung ist's egal — dort hat MemoryBrowser
flex:1 und der Container ist kein ScrollView.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: nach App-Cache-Leeren in Settings tippt er auf eine Datei im
Chat oder im memorySaved-Anhang → "Oeffnen fehlgeschlagen" Toast,
aber kein Re-Download. Datei hing als Geister-uri im State (RNFS-Cache
weg, State-attachment.uri zeigt noch auf den entfernten Pfad).
Fix in beiden Tap-Handlern (item.attachments im normalen Chat +
memorySaved.attachments via localUri):
1. RNFS.exists(localPath) pruefen
2. Wenn ja → openFileWithIntent
3. Wenn nein:
- State bereinigen (uri/localUri auf undefined setzen, UI zeigt
dann "tippen zum Laden")
- Toast "Cache leer — lade nach..."
- file_request via RVS triggern
- autoOpenPaths-Marker setzen, sodass file_response → Datei
speichern + automatisch oeffnen
Bilder-Branch hatte schon onError-Handler (Image-Komponente meldet
self) — der ist unveraendert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inbox-Crash gefunden via App-Crash-Reporter (commit 21a315c):
"URLSearchParams.set is not implemented"
at MemoryBrowser → brainApi.listMemories
React Native's Hermes-Polyfill kennt zwar new URLSearchParams() aber
nicht die .set()-Methode darauf. Pickup-Bug — auf iOS / aelteren
Versionen geht's, Stefan's Android-Build crasht.
Fix: kleine _qs()-Helper im brainApi.ts der einen Query-String aus
einem flachen Object baut, ohne URLSearchParams:
_qs({q:'cessna', k:5, type:'fact'}) → "?q=cessna&k=5&type=fact"
Plus: undefined/null/empty Werte werden ausgelassen — saubererer als
URLSearchParams.set wo man manuell prefilten muss.
ErrorBoundary aus 21a315c hat den Crash sauber abgefangen, statt der
App-Tot war ne Error-Box im Inbox-Modal mit der vollen Stack-Trace.
Stefan konnte den Log via tools/fetch-app-logs.sh holen ohne ADB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan ist unterwegs, ADB nicht moeglich. Dieses Script ist die andere
Haelfte des Crash-Reportings (commit 21a315c hat die App-Seite + Bridge-
Endpoint gebaut):
Nutzung:
tools/fetch-app-logs.sh # 200 letzte Eintraege
tools/fetch-app-logs.sh --limit 50
tools/fetch-app-logs.sh --watch # alle 5s pollen + Diff ausgeben
tools/fetch-app-logs.sh --clear # Log auf VM nach Abholen leeren
Liest $ARIA_DIAG_URL aus .claude/aria-vm.env, ruft GET /api/app-log.
Speichert komplette JSON-Response in .aria-debug/app-log-<ts>.json
(gitignored). Stdout zeigt kompakt: Uhrzeit, Level, Scope, Message,
erste 8 Stack-Frames pro Eintrag.
.gitignore: .aria-debug/ ist komplett ausgeschlossen (Crashes
koennen private Daten enthalten).
tools/README.md: kurze Doku des Workflows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan ist unterwegs, ADB-Zugriff nicht moeglich. Loesung: die App
loggt ihre eigenen Crashes via RVS, Bridge sammelt sie in
/shared/logs/app.log, Diagnostic-Server liefert sie als JSON.
Damit braucht's keinen ADB mehr — Crashes sind sofort vom Browser
(oder Claude per curl) lesbar.
Komponenten:
1. App components/ErrorBoundary.tsx
- React-ErrorBoundary fuer kritische Sections
- componentDidCatch → reportAppError (RVS-Send)
- UI zeigt Error-Box statt White-Screen + Reset-Button
2. App services/logger.ts
- reportAppError(scope, message, stack) → rvs.send('app_log', ...)
- installGlobalCrashReporter() haengt sich an ErrorUtils.setGlobalHandler
UND HermesInternal.enablePromiseRejectionTracker — fangt sowohl
ungefangene Errors als auch unhandled Promise-Rejections
- Konsole bleibt parallel aktiv (damit ADB im Dev-Build weiter
was sieht)
3. App App.tsx: installGlobalCrashReporter() im useEffect zusammen
mit initLogger.
4. App ChatScreen.tsx:
- Inbox-Modal mit ErrorBoundary umschlossen (scope: InboxModal,
onReset schliesst Modal)
- MemoryDetailModal mit ErrorBoundary umschlossen
- DetailModal wird nur noch konditional gerendert (memoryDetailId
!= null) statt immer visible-toggle — vermeidet potentielles
Modal-Stacking-Problem
5. RVS server.js: ALLOWED_TYPES += "app_log"
6. Bridge aria_bridge.py:
- elif msg_type == "app_log": haengt eine Zeile an
/shared/logs/app.log (JSONL, jedes Item {ts, platform, level,
scope, message, stack})
- Plus log.info Hinweis fuer das normale Bridge-Log
7. Diagnostic server.js:
- GET /api/app-log[?limit=N] → letzte N Eintraege als JSON
- POST /api/app-log/clear → log-Datei loeschen
Workflow zum Debuggen des Inbox-Crashes:
Stefan rebuilded App → drueckt Inbox → ErrorBoundary fangt den
Crash (oder Global-Handler bei ungefangenem Error) → reportAppError
→ RVS → Bridge schreibt nach /shared/logs/app.log → Stefan
oder Claude rufen GET /api/app-log auf → sehen Stacktrace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: App crasht beim Tap auf Inbox-Button. Zwei Ursachen:
1. Modal-in-Modal-Stacking (Inbox-Modal enthielt MemoryBrowser, der
wiederum ein MemoryDetailModal gerendered hat). Android Modal hat
damit Probleme — der Native-Layer mag nur eine Modal-Instance
gleichzeitig zuverlaessig.
2. MemoryBrowser nutzte Alert.prompt fuer "Neue Memory anlegen" —
das ist iOS-only, Android wirft eine Warnung oder crasht.
Fix:
- MemoryBrowser bekommt optionalen onOpenMemory-Callback. Wenn der
Parent diesen liefert, mounted MemoryBrowser KEIN eigenes
DetailModal mehr. ChatScreen mountet das DetailModal nur einmal
auf seiner Ebene; Inbox-Modal schliesst sich beim Tap und delegiert
die ID an memoryDetailId-State. Damit ist immer maximal ein Modal
aktiv.
- Alert.prompt durch eigenes kleines Dialog-Modal ersetzt: TextInput
fuer Titel, Anlegen/Abbrechen-Buttons. Cross-platform stabil.
SettingsScreen-Nutzung von MemoryBrowser bleibt unveraendert (kein
Callback → eingebautes DetailModal, aber dort kein Modal-Stacking
weil Settings kein Modal ist).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zwei Bugs aus Stefans Screenshot:
1. memorySaved/triggerCreated/skillCreated bleiben permanent unten im
Chat-Verlauf statt mit den anderen Bubbles zu scrollen — sieht aus
wie Werbe-Bumper. Fix: chatVisibleMessages-Filter raus aus
FlatList-Source, diese Bubbles werden im Chat ueberhaupt nicht mehr
gerendert.
Stefans urspruengliche Idee war ja "trigger und gedächtnis bubble
in ein extra modal fenster" — genau das ist die Inbox jetzt.
2. Inbox-Emoji 🗂️ wurde als Literal "🗂️"-Text
gerendert. Letztes Edit hat es ohne JSX-String-Literal-Schutz
eingefuegt. Fix: {'🗂️'} statt direktes Emoji-Token.
Modal-Header analog.
Inbox-Modal erweitert:
- Neue Section "AUS DIESEM CHAT" oben: kompakte Liste der Spezial-
Bubbles aus messages (chronologisch neueste oben). Memory-Eintraege
oeffnen MemoryDetailModal (mit Tap auf den Pfeil). Trigger/Skills
zeigen nur Title+Meta — keine Edit-UI, dafuer gibt's die jeweiligen
Tabs im Diagnostic.
- Darunter wie bisher der volle MemoryBrowser mit allen DB-Memories.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Etappe 4 — 🗂️ Notizen-Inbox-Button neben der Lupe:
- Statusleiste hat jetzt zwei Icons: 🗂️ Inbox + 🔍 Suche
- Tap auf Inbox-Icon oeffnet ein Vollbild-Modal mit MemoryBrowser-
Komponente. User sieht alle Memories aus der DB, kann suchen,
filtern, neu anlegen, und in den Detail/Edit-Modus springen.
Etappe 5 — Memory-Editor in App-Settings:
- SETTINGS_SECTIONS um Eintrag 🧠 "Gedächtnis" erweitert
- Sektion rendert MemoryBrowser (selbe Komponente wie Inbox) in
einer 600px-Box — vom Diagnostic-Gehirn-Tab inspiriert, aber
fuer's Handy optimiert
- Beide Stellen recyclen MemoryBrowser+MemoryDetailModal aus
Etappe 2/3 — kein doppelter Code
MemoryBrowser (neue Komponente components/MemoryBrowser.tsx):
- Lazy-Load aller Memories via brainApi.listMemories
- Client-side Filter: Volltext-Suche (Title+Content+Category+Tags),
Type-Dropdown, Pinned/Cold/Alle-Toggle
- "+ Neu" Knopf mit Alert.prompt fuer Titel, automatisch type=fact,
oeffnet danach den DetailModal zum Editieren des Contents
- Item-Render mit Pinned-Marker, Anhang-Badge 📎N, Type-Label,
Category, 2-Zeilen-Content-Preview
- Tap auf Item oeffnet MemoryDetailModal → CRUD weiter dort
Damit sind alle 5 Etappen aus Stefans Wunsch-Trio durch:
- Bubble-Header dynamic (Etappe 1, committed gestern)
- Tap-Modal mit Detail (Etappe 2)
- Edit + Anhang-Upload im Modal (Etappe 3)
- Notizen-Inbox-Button (Etappe 4)
- Memory-Editor in Settings (Etappe 5)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Etappe 1 von Stefans App-Memory-UX-Wunsch:
Brain agent.py: memory_save Dispatcher pushed jetzt action="created",
memory_update Dispatcher pushed action="updated" mit demselben
memory_saved-Event-Typ. Bridge reicht das action-Feld im Payload mit
durch (in beiden Side-Channel-Pfaden — send_to_core + trigger-fired).
App ChatScreen: ChatMessage.memorySaved.action ('created' | 'updated'
| 'deleted'). Bubble-Header je nach Aktion:
- created → "🧠 ARIA hat etwas gemerkt" (gelb)
- updated → "🧠 ARIA hat eine Notiz geändert" (gelb)
- deleted → "🧠 ARIA hat eine Notiz gelöscht" (rot)
Naechste Etappen folgen (Detail-Modal beim Tap, Edit + Anhang-Upload,
Notizen-Inbox neben Lupe, Memory-Editor in Settings).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug-Report von Stefan: er hat im Diagnostic den Baujahr-Memory von
1972 auf 1974 geaendert, ARIA wusste das nicht und beharrte auf 1972
(weil ihr letzter Conversation-Turn noch '1972' enthielt). Sie konnte
auch nicht nachpruefen, sagte selbst: "Qdrant kann ich nicht aktiv
durchsuchen".
Fix: zwei neue Meta-Tools im agent.py.
memory_search(query, mode='text'|'semantic', k=5):
- Volltext oder semantic via store.search_text / store.search
- Liefert Liste mit Titel, ID, Content, Anhaengen
- Tool-Description sagt explizit: "Memory ist Truth ueber dem
Conversation-Window" — wenn beide unterschiedlich sind, gilt
Memory. Plus Anker-Anwendungsfaelle: 'schau in deinem Gedaechtnis',
'ich hab das aktualisiert', 'pruef ob's schon was zum Thema gibt'
memory_update(id, title?, content?, category?, tags?, pinned?):
- Patch existierender Memory per ID (aus memory_search oder Cold-Memory)
- Content-Change triggert Re-Embedding fuer Search, sonst nur
Payload-Update
- Pushed memory_saved-Event analog zu memory_save (App/Diagnostic
refreshen)
- Tool-Description empfiehlt explizit Update statt neuem Save bei
Korrekturen/Ergaenzungen — vermeidet Fragmentierung
Damit kann Stefan jetzt sagen "schau in deinem Gedaechtnis" und ARIA
findet den aktualisierten Eintrag. Plus bei spaeteren Korrekturen
("ach nee, 1974") nutzt ARIA memory_update statt memory_save +
hinterlaesst einen sauberen Eintrag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
issue.md: neuer Block "Memory-Anhaenge mit Vision (Stufe A-E +
attach_paths)" mit den 7 Punkten (Storage-Layer, Backend-Endpoints,
Diagnostic-UI, App-UI, System-Prompt-Integration, Vision via Read-
Tool, attach_paths fuer einarmigen memory_save+attach-Workflow).
README.md: Diagnostic-Gehirn-Tab-Beschreibung um 📎-Anhaenge erweitert,
plus neuer Roadmap-Eintrag "Memory-Anhaenge mit Vision-Pipeline" der
das End-to-End-Erlebnis erklaert (User-Foto → ARIA liest via Read →
extrahiert Kennzeichen/Marken/Texte → speichert als Memory mit Foto-
Anhang → spaetere Detail-Fragen lassen ARIA das Bild nochmal lesen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Letzter Baustein vor Stefan's End-to-End-Test:
memory_attachments.attach_from_path(memory_id, src_path):
- Kopiert eine bestehende Datei aus /shared/uploads/ oder
/shared/memory-attachments/ in das Anhang-Verzeichnis der Memory
- Pfadschutz: nur ALLOWED_SOURCE_PREFIXES (/shared/uploads/,
/shared/memory-attachments/) — kein Zugriff auf Root-FS oder
SSH-Keys
- Groessen-Limit wie save_attachment (20 MB Default)
agent.py memory_save:
- Neuer optionaler Parameter `attach_paths: List[str]`
- Nach dem upsert: pro Pfad attach_from_path → Payload update mit
neuen Anhang-Metadaten
- Fehler beim Anhang sind nicht fatal (Memory bleibt gespeichert,
Hinweis in der Tool-Response)
- Tool-Description deutlich erweitert: expliziter Workflow-Hinweis
bei Bildern → erst `Read <pfad>` aufrufen (Claude Code Read ist
multi-modal), Texte/Kennungen/Marken in den content extrahieren,
dann erst memory_save mit attach_paths. Beispiel-Workflow als
Pseudocode mit Cessna 172 / Kennung D-EAAA.
End-to-End-Workflow ist jetzt einarmig moeglich:
User: "Ich hab eine Cessna 172" + Bild im Attachment
ARIA: Read /shared/uploads/aria_xy.jpg → sieht "Kennung D-EAAA"
ARIA: memory_save(content="Stefan besitzt eine Cessna 172,
Kennung D-EAAA, weiss/rot lackiert.",
attach_paths=["/shared/uploads/aria_xy.jpg"])
→ 🧠-Bubble mit Anhang in der App
→ Spaetere Frage "welche Kennung hat mein Flieger?" liefert via
Cold-Memory den Eintrag inkl. Kennung aus dem content
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wir mussten den Proxy nicht patchen. Claude Code's eingebautes
Read-Tool ist multi-modal-faehig — uebergibt man eine Bilddatei,
geht die durch das gleiche Vision-Modell wie via Anthropic-Vision-API.
ARIA hat eh "Tool-Freigaben — Vollzugriff" pinned (inkl. Read), also
muss sie nur wissen dass sie das nutzen darf.
prompts._attachments_line erweitert: bei image/* im Anhang haengen
wir den Hinweis an "Bilder kannst du via `Read <pfad>` direkt ansehen".
ARIA ruft dann selbststaendig Read mit dem Memory-Anhang-Pfad, sieht
das Bild und kann antworten was drauf ist.
Heisst: Stefan sagt "schau dir mein Cessna-Foto an" → ARIA findet
Memory via Cold-Search → sieht die Read-Anweisung → ruft Read auf →
Vision-Modell beschreibt das Bild → ARIA antwortet im Chat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stufe C — App:
- ChatMessage.memorySaved.attachments [{name, mime, size, path, localUri}]
- memory_saved-Listener uebernimmt payload.attachments
- renderMessage memorySaved-Bubble zeigt Anhaenge als Tap-Reihen
(Icon 🖼/📄 + Filename + Hint). Tap → file_request via Bridge,
beim ersten Mal "(tippen zum Laden)" → nach file_response cached
+ bei Bildern setFullscreenImage, bei anderen openFileWithIntent
- file_response-Handler updated zusaetzlich memorySaved.attachments
per serverPath-Match
- Styles fuer memoryAttachmentRow/Icon/Name/Meta
Stufe D — System-Prompt:
- prompts._attachments_line: pro Memory eine Zeile
"📎 Anhaenge: foo.jpg (image/jpeg, 109 KB) — Pfad: /shared/memory-attachments/<id>/"
- Wird in build_hot_memory_section + build_cold_memory_section
nach dem Content angehangen
- ARIA "weiss" damit dass Anhaenge da sind und kann via Bash darauf
zugreifen (file, head, base64 …). Echt sehen kann sie sie erst mit
Multi-Modal-Pipeline (Stufe E)
- memory_save Dispatcher: attachments-Liste auch im memory_saved-Event
(vermutlich [] beim Save, aber konsistent fuer spaeteres
Speichern-mit-Anhaengen-Pattern)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostic-Gehirn-Tab kann jetzt Bilder/Dateien an Memory-Eintraege
haengen — drag+drop ueber den File-Input im Memory-Modal.
Memory-Modal (Edit-Modus):
- Neuer Block "📎 Anhaenge" unter Pinned-Checkbox, nur sichtbar wenn
Memory eine ID hat (Edit). Bei "Neue Memory" stattdessen Hinweis
"Anhaenge nach Speichern hinzufuegbar".
- "⬆ Datei waehlen" oeffnet File-Picker (multiple), Upload via
multipart/form-data POST an /memory/{id}/attachments/upload.
- Liste zeigt pro Anhang: Thumbnail (Bilder) oder 📄-Icon,
Filename, Mime + Groesse, 🗑 Loeschen-Button.
- Bild-Thumbnails sind klickbar → openLightbox.
- Status-Zeile zeigt Upload-Progress + Erfolgsmeldung.
Memory-Liste:
- 📎N-Badge erscheint hinter dem Titel wenn N > 0 Anhaenge da sind.
Diagnostic-Server:
- Brain-Reverse-Proxy-Timeout dynamisch: 120s fuer /attachments-Routen
(Upload), 60s sonst (vorher pauschal 30s — zu wenig fuer chat/distill).
- multipart-Body wird ueber req.pipe(proxyReq) durchgereicht (FastAPI
liest File via UploadFile, Content-Type-Header bleibt erhalten).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan's Test scheiterte: ein normales Handy-Foto als Base64 in der
curl-d-Argumentliste sprengt Bash's ARG_MAX (typisch 128KB-2MB). Plus:
Browser-FormData und curl -F sind eh der Standard fuer File-Uploads.
Fix: zusaetzlicher Endpoint
POST /memory/{id}/attachments/upload (multipart/form-data, field: file)
Beispiel auf der VM:
curl -F file=@/pfad/zu/foto.jpg \
"$ARIA_BRAIN_URL/memory/<id>/attachments/upload" | jq
Base64-Endpoint (/memory/{id}/attachments) bleibt fuer kleine
Uploads + interne JSON-Tools. Beide rufen am Ende den gleichen
_commit_attachment_meta-Helper, der das Memory-Payload um den
neuen Anhang updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pro Memory koennen jetzt Dateien (Bilder, PDFs, Sound, ...) angehaengt
werden. Use-Case: Stefan sagt "ich hab eine Cessna 172" und pinnt
gleich ein Foto dran — ARIA sieht spaeter neben dem Memory auch die
visuelle Referenz (Stufe E = Multi-Modal-Pipeline).
Stufe A baut nur den Backend-Layer; UI kommt in Stufe B (Diagnostic)
und C (App). Anhaenge werden in Stufe A nur via HTTP-API gepflegt
(curl), ARIA selbst kann sie noch nicht hochladen — sinnvoll erst
wenn die Vision-Pipeline (Stufe E) steht.
Komponenten:
- memory_attachments.py: neuer Storage-Helper. Layout
/shared/memory-attachments/<memory-id>/<safe-filename>.
Filename-Sanitization (kein Path-Traversal), Limit 20 MB
konfigurierbar, save/list/delete/read_bytes + delete_all fuer
Cleanup beim Memory-Delete.
- vector_store.py: MemoryPoint.attachments (List[dict]) — Metadaten
{name, mime, size, path} im Qdrant-Payload damit Suche/Anzeige
sie ohne Filesystem-Lookup kennt.
- main.py:
- MemoryIn akzeptiert attachments-Liste (fuer Restore-Faelle)
- MemoryOut liefert attachments
- GET /memory/{id}/attachments → Liste vom FS
- POST /memory/{id}/attachments → Base64-Upload,
schreibt FS + updated Payload-Liste
- DELETE /memory/{id}/attachments/{filename} → FS + Payload-Eintrag weg
- GET /memory/{id}/attachments/{filename} → Bytes mit MIME serve
- /memory/delete cleanup: ruft attachments.delete_all damit kein
Verzeichnis verwaist
Smoke-Test nach Brain-Rebuild (Stefan auf VM):
# Memory-ID rauspicken
ID=$(curl -s "$ARIA_BRAIN_URL/memory/list?type=fact" | python3 -c "import sys,json;print(json.load(sys.stdin)[0]['id'])")
# Bild als Base64 hochladen
B64=$(base64 -w0 /pfad/zu/foto.jpg)
curl -s -X POST "$ARIA_BRAIN_URL/memory/$ID/attachments" \
-H 'Content-Type: application/json' \
-d "{\"name\":\"foto.jpg\",\"data_base64\":\"$B64\"}" | jq
# Liste anzeigen
curl -s "$ARIA_BRAIN_URL/memory/$ID/attachments" | jq
# Datei wieder laden
curl -s "$ARIA_BRAIN_URL/memory/$ID/attachments/foto.jpg" -o /tmp/back.jpg
Stufe B (Diagnostic-UI) folgt sobald A getestet ist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Was alles seit dem letzten Doc-Update dazukam:
issue.md (Bugfixes):
- Cold Memory Crosstalk durch Score-Threshold
- Pinned-/Type-Filter bei aktiver Suche
- Memory-Liste refresh nach Delete
- Thinking-Indikator im RVS-Chat wieder sichtbar
- Memory-Suche filtert Rauschen (score_threshold am Endpoint)
- Cessna-Phantom-Wissen aus System-Prompt raus
- Claude-Code-Auto-Memory abgeklemmt (tmpfs)
issue.md (Features):
- Neuer Block "Memory-System (Phase B Punkt 5+ Bonus)" mit
memory_save Tool, Volltext-Suche, Advanced Search, Muelltonne,
Druckansicht, klappbare Kategorien
- Neuer Block "DB als Single Source of Truth" mit brain-import als
Drop-Folder, DB-Cleanup 60→31, .claude/aria-vm.env Setup
README.md:
- aria-data/brain-import Tabelle-Beschreibung aktualisiert
- .claude/aria-vm.env als neue Zeile in der Konfig-Tabelle
- Diagnostic Gehirn-Tab Beschreibung ausgebaut (Wortlich/Semantisch,
Advanced Search, klappbare Kategorien, Druckansicht)
- App-Features: Muelltonne pro Bubble erklaert
- Roadmap-Eintrag "Single Source of Truth — Qdrant" als zentrales
Abschluss-Item nach Tool-Use-Patch
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: Agent.chat() rief store.search() OHNE score_threshold — die
Top-5 wurden ungefiltert in den 'Moeglicherweise relevant'-Block
des System-Prompts gepackt. Bei kleiner DB hatte das absurde Folgen:
Stefan fragte 'hab ich ein flugzeug?', Cold-Search lieferte Top-1
'Watcher-Latenzproblem' mit Score 0.138 + 'Firmenadresse' mit 0.094,
ARIA wob die Firmenadresse in die Antwort ein ('Die Adresse habe ich
aus meinem Gedaechtnis...') — obwohl der User gar nicht danach gefragt
hat.
Fix: Konstante COLD_SCORE_THRESHOLD=0.30 in Agent eingefuehrt und an
store.search() durchgereicht. Treffer unter 0.30 werden als Rauschen
verworfen, ARIA bekommt nur substantielle Memories ins Cold-Set.
Konsistent mit dem Threshold im /memory/search HTTP-Endpoint und dem
Diagnostic UI.
MiniLM-multilingual gibt fuer unverwandte deutsche Texte gerne 0.10-
0.25 Score — alles darunter ist Embedder-Noise, kein echter Bezug.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 1: Memory loeschen + Liste zeigt geloeschten Eintrag weiter
deleteMemory rief loadBrainMemoryList — die fiel bei aktiver Such-
Ansicht in den Cache-Pfad und renderte den geloeschten Eintrag aus
brainMemoryCache/brainSearchIds wieder. Fix: nach Delete den Cache-
Eintrag + brainSearchIds bereinigen und bei aktiver Suche re-search
ausfuehren (single oder advanced), sonst Vollliste vom Server.
Bug 2: "ARIA denkt..."-Indikator erscheint nicht mehr im Chat-Fenster
Diagnostic-Server hatte fuer RVS-eingehende agent_activity-Events
keinen Relay an die Browser-Clients. Bridge sendet die Events brav,
Diagnostic schluckt sie still. Fix: agent_activity vom RVS an
Browser broadcasten (mit dem gleichen settled-window-Schutz wie
beim alten Gateway-Pfad — Trailing-Events nach chat:final werden
weiter ignoriert).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA hatte beim 'weisst du ob ich ein Flugzeug habe?'-Test richtig
geantwortet ('nein'), aber transparent erklaert dass sie das Wort
'Cessna' aus dem memory_save Tool-Description kennt — wo es als
Beispiel fuer den fact-Type stand. Ein Beispiel-Text der jedes
Chat-Turn im System-Prompt landet ist suboptimal, auch wenn ARIA
ihn korrekt einordnet.
Fix: das konkrete Beispiel durch eine generische Aufzaehlung
ersetzt (Vorlieben/Besitz/Orte/Termine/Personen). Ohne Stefan-
spezifisches Phantom-Wissen. Selber Spirit in der search-text
Docstring im main.py (geht zwar nicht in den Prompt, aber lieber
konsistent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: runBrainSearch und runAdvancedSearch ignorierten den
brain-filter-pinned Dropdown — egal ob "Nur Pinned" oder "Nur Cold"
gewaehlt war, kam immer alles was die Such-Kriterien erfuellte.
Plus: Dropdown-onchange rief loadBrainMemoryList und brach damit
die Suche ab statt sie mit dem neuen Filter neu auszufuehren.
Fix:
- Neue Helfer brainSearchActive() (erkennt single/advanced/none) und
applyPinnedFilter() (client-side Filter nach 'all'/'pinned'/'cold').
- runBrainSearch + runAdvancedSearch wenden applyPinnedFilter nach
dem Backend-Hit an. Info-Box zeigt zusaetzlich an wenn
Pinned-Filter aktiv war ("... · 📌 nur pinned"), bei 0 Treffern
auch der unfiltered Count fuer Debug ("X Treffer ohne Pinned-Filter").
- Type+Pinned-Dropdowns onchange → onBrainFiltersChanged: bei
aktiver Suche re-search, sonst loadBrainMemoryList.
Backend bleibt unveraendert (include_pinned all-or-none reicht —
Feinheit "nur pinned" macht der Client).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ARIA hatte bisher KEIN Tool um eigene Notizen sauber zu persistieren —
sie ist deshalb aufs Claude-Code-File-Memory ausgewichen (das wir mit
dem letzten Commit per tmpfs abgeklemmt haben). Jetzt schliesst sich
der Loop: ein echtes memory_save-Tool gegen die Qdrant-DB.
Brain:
- agent.py: memory_save als Meta-Tool mit Schema (title, content,
type, optional category/tags/pinned). Tool-Description erklaert
die Type-Wahl (identity/rule/preference/tool/skill = pinned,
fact/conversation/reminder = cold) und sagt explizit: "Du hast
KEIN File-Memory mehr, schreibe nicht in ~/.claude/projects/..."
- Dispatcher: validiert type-enum, ruft self.embedder.embed +
self.store.upsert, pushed memory_saved als _pending_events damit
Bridge eine Bubble broadcasten kann.
Side-Channel-Pipeline (gleich wie skill_created/trigger_created):
- Bridge send_to_core + _handle_trigger_fired: forwarden
memory_saved als RVS-Event
- rvs/server.js: ALLOWED_TYPES += memory_saved
- diagnostic/server.js: relayed memory_saved von RVS an Browser
- diagnostic UI: addMemorySavedBubble (gelber Border) + Auto-Refresh
des Gehirn-Tabs wenn aktiv
- android: ChatMessage.memorySaved-Feld, Listener fuer memory_saved,
renderMessage-Spezialbubble, History-Replace-Schutz (lokal-only)
Damit ist die Architektur konsistent:
"merk dir X" → ARIA ruft memory_save → Eintrag in Qdrant →
Diagnostic-Gehirn-Tab zeigt's sofort → bei naechstem Turn liefert
Cold Memory (Semantic Search) das Wissen wieder rein.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude Code CLI (im Proxy-Container) hat ein eingebautes Auto-Memory-
Feature das Markdown-Files in ~/.claude/projects/<project>/memory/
schreibt. Weil das CLI als ARIAs LLM laeuft, hat sie da ueber Wochen
ihre eigene Schatten-Wissensbasis aufgebaut (cessna, persoenlichkeit,
projects) — komplett parallel zu unserer Qdrant-DB. Genau die doppelte
Truth-Source die wir vermeiden wollten.
Fix: tmpfs ueber das projects/-Verzeichnis im Proxy-Container.
Effekt:
- Claude Code sieht beim Spawn ein leeres projects/ — keine Auto-
Memory-Files werden geladen
- Schreibt sie was rein, landet's nur im Container-RAM
- Beim Container-Recreate ist alles weg
- Stefans persoenlicher ~/.claude/projects/ auf der VM bleibt
unangetastet (Volume ist immer noch gemountet, nur das Subdir
wird ueberlagert)
Migration auf der VM (Stefan einmalig):
rm -rf ~/.claude/projects/-/memory/
docker compose up -d --force-recreate proxy
Auto-Memory ist damit deaktiviert. Naechster Schritt (5): ARIA bekommt
einen eigenen memory_save Tool damit sie Sachen sauber in Qdrant
ablegen kann statt aufs File-Memory auszuweichen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Statt fest 3 Felder gibt's jetzt eine erweiterbare Reihen-Liste:
- "+ Feld"-Button fuegt eine Reihe hinzu (UND/ODER + Eingabe)
- ✕-Button pro Reihe (ausser der ersten) entfernt sie
- Erste Reihe ist immer "Start" ohne Operator
- syncAdvancedRowsFromDOM rettet Eingaben vor jedem Re-Render
- runAdvancedSearch iteriert ueber alle Reihen mit Inhalt, leere
werden ignoriert
Damit ist die Boolean-Suche so lang wie noetig — Stefan kann auch
5-6 Begriffe verknuepfen ohne UI-Hack. Min. 1 Feld bleibt immer
(clearAdvancedSearch reseted auf eine leere Start-Reihe).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>