Commit Graph

593 Commits

Author SHA1 Message Date
duffyduck 7a22474efd feat(chat): Jump-down-Button + Sprung-an-Text-Anfang + Vision-Issue raus
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>
2026-05-14 22:10:26 +02:00
duffyduck f2cf4e0d58 release: bump version to 0.1.4.0 v0.1.4.0 2026-05-14 18:39:58 +02:00
duffyduck db4bebfa57 docs: README + issue — drei GPS-Trigger-Modi + Tick-Frequenz-Fix
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>
2026-05-14 18:33:04 +02:00
duffyduck 435b77e1df feat(trigger): entered_near + left_near — drei Modi fuer near()-Watcher
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>
2026-05-14 18:29:39 +02:00
duffyduck 6f80e442cf fix(trigger): near() fired bei Auto-Vorbeifahrten verpasst — Loop schneller + event-getrieben
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>
2026-05-14 18:16:53 +02:00
duffyduck 0fcbf5e3ed docs: README + issue — Memory-Editor-App + Crash-Reporting + Bugfixes
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>
2026-05-14 17:30:15 +02:00
duffyduck 3cf6308b79 release: bump version to 0.1.3.9 v0.1.3.9 2026-05-14 17:17:44 +02:00
duffyduck 7e5a4da659 fix(app): Memory-Liste in Settings scrollt jetzt (nestedScrollEnabled)
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>
2026-05-14 17:14:21 +02:00
duffyduck d27fcaf342 fix(chat): Cache leer + Datei-Tap → Auto-Re-Download statt Fehlertoast
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>
2026-05-14 16:14:56 +02:00
duffyduck 5b28a065c0 release: bump version to 0.1.3.7 v0.1.3.7 2026-05-14 16:08:30 +02:00
duffyduck e74e1eaf70 fix(app): URLSearchParams crasht in Hermes — durch Mini-Query-Builder ersetzt
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>
2026-05-14 16:06:21 +02:00
duffyduck ff7c6333bb release: bump version to 0.1.3.6 v0.1.3.6 2026-05-14 16:00:42 +02:00
duffyduck 2c85df3499 chore: tools/fetch-app-logs.sh — App-Crash-Logs von der VM holen
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>
2026-05-14 15:59:06 +02:00
duffyduck 6f11f28448 release: bump version to 0.1.3.5 v0.1.3.5 2026-05-14 15:45:36 +02:00
duffyduck 21a315ca71 feat(debug): App-Crash-Reporting via RVS — Logs in der Diagnostic-UI
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>
2026-05-14 15:42:55 +02:00
duffyduck d8b05082d6 release: bump version to 0.1.3.4 v0.1.3.4 2026-05-14 14:58:47 +02:00
duffyduck de91073b2e release: bump version to 0.1.3.3 v0.1.3.3 2026-05-14 14:08:03 +02:00
duffyduck e88b5f57bf fix(memory): Inbox-Crash auf Android — Modal-Stacking + Alert.prompt
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>
2026-05-14 14:05:42 +02:00
duffyduck 64a17c8c19 release: bump version to 0.1.3.2 v0.1.3.2 2026-05-14 13:59:09 +02:00
duffyduck ebeacba8b5 fix(chat): Spezial-Bubbles raus aus Chat → Inbox + Emoji-Bug behoben
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>
2026-05-14 13:56:39 +02:00
duffyduck 58251b26a2 release: bump version to 0.1.3.1 v0.1.3.1 2026-05-14 13:31:59 +02:00
duffyduck 5c10990cbc feat(memory): Notizen-Inbox + Settings-Editor (Etappen 4+5)
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>
2026-05-14 13:29:43 +02:00
duffyduck f71936da86 feat(memory): Tap auf Memory-Bubble oeffnet Detail+Edit-Modal in der App (Etappen 2+3)
Stefans naechste Wunsch-Etappe — komplettes Edit eines Memory-Eintrags
aus der App heraus, inkl. Anhang-Upload, ohne Diagnostic-Browser
auszuklappen.

Backend-Fundament (Phase A):
- Brain bekommt GET /memory/get/{id} fuer Einzel-Lookup mit allen Feldern
- RVS ALLOWED_TYPES um brain_request + brain_response erweitert
- Bridge implementiert generischen RVS-Brain-Proxy:
  payload {requestId, method, path, body|bodyBase64, contentType}
  → ruft Brain-HTTP-API → broadcastet brain_response {requestId,
  status, json|text|base64+contentType}. Damit kann die App
  beliebige Brain-Endpoints ueber RVS adressieren — nicht nur Memory.

App-Service (Phase B):
- services/brainApi.ts: Promise-basierter Client. _send() schickt
  brain_request mit requestId, _ensureListener() filtert die passende
  brain_response. Methoden: getMemory, listMemories, searchText,
  searchSemantic, saveMemory, updateMemory, deleteMemory,
  uploadAttachment (Base64), deleteAttachment, getAttachmentBytes.

App-UI (Phasen C+D):
- components/MemoryDetailModal.tsx: Modal mit zwei Modi.
  - Read: Titel, Type, Category, Tags, voller Content, Anhang-Liste
    (Tap = Bild im Vollbild oder Datei-Info), Stift-Icon → Edit.
  - Edit: Titel/Content/Category/Tags/Pinned editierbar, Save via
    brainApi.updateMemory.
  - DocumentPicker + RNFS.readFile(base64) → uploadAttachment(...).
  - Anhang loeschen, kompletter Memory loeschen (mit Alert-confirm).
- ChatScreen: TouchableOpacity-Wrapper um die memorySaved-Bubble,
  Tap setzt memoryDetailId → Modal oeffnet. Hint im Footer
  "tippen für Details" wenn die Bubble eine ID hat.

Etappen 4 (Notizen-Inbox neben Lupe) + 5 (Memory-Editor in App-
Settings) folgen — diese nutzen die gleiche MemoryDetailModal-
Komponente, sind also schnell aufgesetzt sobald 2+3 verifiziert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:26:02 +02:00
duffyduck 62f394b2aa release: bump version to 0.1.3.0 v0.1.3.0 2026-05-14 13:18:55 +02:00
duffyduck 6239037fa7 feat(memory): Bubble-Header zeigt jetzt Aktion (angelegt/geaendert/geloescht)
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>
2026-05-14 12:44:05 +02:00
duffyduck 4b3f8cded2 release: bump version to 0.1.2.9 v0.1.2.9 2026-05-13 08:55:28 +02:00
duffyduck 16ebaa652f feat(brain): memory_search + memory_update Tools — ARIA findet Updates aktiv
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>
2026-05-13 03:23:08 +02:00
duffyduck 27c04a2874 docs: README + issue — Memory-Anhaenge mit Vision-Pipeline (Stufen A-E + attach_paths)
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>
2026-05-13 03:16:20 +02:00
duffyduck 31a1370050 feat(brain): memory_save mit attach_paths — ARIA haengt Bilder selbst an
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>
2026-05-13 02:57:02 +02:00
duffyduck 933dd50367 feat(brain): Stufe E — ARIA sieht Bilder ueber Claude Codes Read-Tool
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>
2026-05-13 02:49:09 +02:00
duffyduck d5531521fa feat(memory): Anhaenge in App-Bubble + System-Prompt (Stufe C + D)
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>
2026-05-13 02:45:51 +02:00
duffyduck de9b7b46f9 feat(diag): Memory-Anhaenge in der UI (Stufe B)
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>
2026-05-13 02:36:51 +02:00
duffyduck da4e970a31 feat(brain): Memory-Anhaenge — multipart/form-data Endpoint daneben Base64
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>
2026-05-13 02:32:24 +02:00
duffyduck c677cfed24 feat(brain): Anhaenge an Memory-Eintraege (Stufe A — Backend)
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>
2026-05-13 02:27:02 +02:00
duffyduck 331c1437be docs: README + issue — DB-Truth-Block + memory_save + Search-Modi + Muelltonne dokumentiert
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>
2026-05-13 02:23:09 +02:00
duffyduck 1e754910ee fix(brain): Cold Memory mit Score-Threshold — kein Crosstalk mehr
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>
2026-05-13 02:13:32 +02:00
duffyduck 351c58e88e fix(diag): zwei Bugs — Memory-Liste nach Delete + Thinking-Indikator im RVS-Chat
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>
2026-05-13 01:55:08 +02:00
duffyduck df60bb6d74 fix(brain): Cessna-Beispiel aus System-Prompt raus — keine Phantom-Wissens-Hinweise
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>
2026-05-13 01:39:54 +02:00
duffyduck 24cf40293a fix(diag): Pinned-/Type-Filter wirkt jetzt auch bei aktiver Suche
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>
2026-05-13 01:31:17 +02:00
duffyduck 5f96ace469 feat(brain): memory_save Tool — ARIA schreibt selber in die Qdrant-DB
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>
2026-05-13 01:27:20 +02:00
duffyduck 9dd95709b9 fix(proxy): Claude-Code-Auto-Memory abklemmen — tmpfs ueber ~/.claude/projects
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>
2026-05-13 01:17:21 +02:00
duffyduck a2dee3164a feat(diag): Advanced Search — dynamisch Felder hinzufuegen mit + Button
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>
2026-05-13 01:13:15 +02:00
duffyduck 01f0ad3a40 feat(diag): Advanced Search mit AND/OR + mehrere Begriffe
Klappbares Panel unter dem Suchbalken — Stefan kann bis zu 3 Begriffe
eingeben und mit AND/OR verknuepfen, links nach rechts ausgewertet.
Backend bleibt simpel: pro Begriff einmal /memory/search-text aufgerufen,
die Treffer-Set-IDs werden client-seitig per AND (intersect) oder OR
(union) kombiniert.

UI:
- "⌃ Erweitert" Button rechts neben ✕ klappt das Panel auf
- 3 Eingabefelder mit 2 Operator-Dropdowns dazwischen (UND/ODER)
- "Suchen"-Button im Panel
- "Felder leeren" reseted
- Leere Felder werden ignoriert — sind nur 2 belegt, gibt's nur 1 Operator
- Typ-Filter aus dem Hauptbalken wird mit angewandt
- Info-Banner zeigt die kombinierte Suchformel zurueck

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:32:55 +02:00
duffyduck 6549fcbce8 feat(brain): Volltext-Suche zusaetzlich zu Semantic — Default ist jetzt Wortlich
Stefan wollte ne richtige Suche statt nur "klingt aehnlich". Beide
Modi sind jetzt verfuegbar, Default ist Volltext:

- 📝 Wortlich (Substring, case-insensitive ueber Title + Content +
  Category + Tags) — neuer Endpoint /memory/search-text. Full-Scan
  via Qdrant scroll, k=50. Findet "cessna" exakt im Content. Bei
  kleiner DB (<1000 Eintraege) unkritisch performant.

- 🧠 Semantisch (Embedder + score_threshold 0.30) — bestehender
  /memory/search Endpoint. Findet konzeptuell verwandte Eintraege.

Diagnostic UI: Dropdown neben dem Suchfeld zum Modus-Wechsel.
Info-Banner zeigt klar welcher Modus aktiv ist.

Warum Wortlich Default: bei kleiner DB liefert Semantic gern False
Positives mit Score 0.30-0.45 fuer komplett unverwandte Begriffe
(z.B. "cessna" matched "Tageslog fuehren" mit 0.43). Wortlich ist
deterministisch und vermeidet das Rauschen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:59:31 +02:00
duffyduck 3c41f11997 release: bump version to 0.1.2.8 v0.1.2.8 2026-05-12 16:45:29 +02:00
duffyduck 3f2499b528 feat(chat): Muelltonne pro Bubble — gezielt eine Nachricht loeschen
Stefan kann jetzt einzelne Chat-Bubbles loeschen (mit Rueckfrage).
Die Bubble verschwindet aus chat_backup.jsonl (Bridge), Brain-
Conversation (rolling window + jsonl) und allen Clients (App +
Diagnostic). Genauso wichtig fuer ARIA: der gloeschte Turn ist im
naechsten Chat-Prompt nicht mehr im Window.

Pipeline:
  UI 🗑 + confirm
  → RVS delete_message_request {ts}
  → Bridge._delete_chat_message:
      - chat_backup.jsonl Zeile mit ts entfernen (atomar via tmp+rename)
      - Brain POST /conversation/delete-turn (role+content match)
      - RVS broadcast chat_message_deleted {ts}
  → App + Diagnostic entfernen Bubble lokal per ts-Match

Backend-Aenderungen:
- aria-brain/conversation.py: remove_by_match(role, content, ts_hint)
  + _rewrite_file (atomar). Match nahester Turn bei mehrfach gleichem
  content.
- aria-brain/main.py: POST /conversation/delete-turn (POST statt DELETE
  weil FastAPI keine Bodys auf DELETE erlaubt)
- bridge/aria_bridge.py: HTTP-Listener /internal/delete-chat-message
  + RVS-Handler delete_message_request. _append_chat_backup gibt jetzt
  ts zurueck, _process_core_response packt backupTs ins chat-Event.
- rvs/server.js: ALLOWED_TYPES um delete_message_request +
  chat_message_deleted erweitert.
- diagnostic/server.js: delete_chat_message-Action + chat_message_deleted
  Relay zum Browser.

Frontend-Aenderungen:
- diagnostic/index.html: 🗑 erscheint on-hover in Bubbles mit data-ts,
  confirm()-Dialog, addChat + chat_history setzen data-ts. WS-Listener
  fuer chat_message_deleted entfernt Bubble per data-ts.
- android/ChatScreen.tsx: backupTs in ChatMessage, Muelltonne-Button
  unten rechts in jeder Bubble, Alert-confirm, RVS-Listener fuer
  chat_message_deleted entfernt aus messages-State.

Live-User-Bubbles (sofort gerendert vom eigenen Send) haben noch
keinen backupTs bis der Bridge-Roundtrip durch ist — die Muelltonne
erscheint dort erst nach kurzer Verzoegerung / Reload. Folgekommit
kann das polieren wenn noetig.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:42:20 +02:00
duffyduck daf0d44dd7 fix(brain): Memory-Suche filtert jetzt Rauschen — score_threshold + kleineres k
Bug: bei kleiner DB (31 Eintraege) lieferte die Suche fuer JEDES Wort
fast alles als Treffer zurueck — k=20 Top-N ohne Threshold sorgte
dafuer dass auch "banane" zehn vermeintliche Treffer mit Scores
0.09-0.22 (= Rauschen) zurueckgab.

Fix:
- vector_store.search() bekommt optional score_threshold (an Qdrant
  durchgereicht, das nimmt's nativ)
- /memory/search endpoint hat score_threshold-Query-Param (default 0.30)
- Diagnostic schickt k=10 + score_threshold=0.30 statt k=20 ohne Threshold
- "Keine Treffer"-Info-Box wenn alle Treffer < Threshold

MiniLM-multilingual liefert typischerweise:
  >0.50 → starker Treffer
  0.30-0.50 → relevant
  0.20-0.30 → grenzwertig
  <0.20 → Rauschen

Mit score_threshold=0 (oder None) bleibt die alte Top-N-Semantik
fuer Aufrufer die Rauschen explizit wollen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:59:05 +02:00
duffyduck 051d629cb3 chore: brain-import/ wird komplett ignoriert (Drop-Folder)
Stefan wirft MDs rein wenn er was migrieren will, klickt im
Diagnostic-Gehirn-Tab auf "Migration aus brain-import/", fertig.
Was nicht migriert ist, liegt halt rum — gehoert aber nicht ins Repo
(private Daten, ephemerer Kram).

.gitignore-Pattern:
  aria-data/brain-import/*
  !.gitkeep
  !README.md

Alte spezifische USER.md-Zeile durch das catch-all ersetzt — wir
mussten USER.md.example und Co. eh nicht mehr im Repo halten.

README in dem Verzeichnis entsprechend angepasst (Drop-Folder, nicht
"leerer Restposten").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:49:03 +02:00
duffyduck 1a19b362d7 chore: brain-import/-MDs raus — DB ist Truth, README + .gitkeep statt Saatgut
AGENT.md/BOOTSTRAP.md enthielten Duplikate, OpenClaw-Referenzen und
fast-Memory-Hinweise auf das alte file-basierte System. Nach dem
DB-Cleanup (60 → 31 Eintraege) sind die alten MDs nicht mehr nuetzlich
— Stefan kuratiert direkt im Diagnostic-Gehirn-Tab, Backup laeuft via
Bootstrap-Snapshot (JSON) oder Komplett-tar.gz.

TOOLING.md.example + USER.md.example mit raus (auch obsolet).
.gitkeep haelt das Verzeichnis im Repo, README dokumentiert wofuer
es mal war und wann man es wieder braucht (Disaster-Recovery ohne
Snapshot, neues ARIA von Null).

Migration-Code (aria-brain/migration.py) bleibt — falls jemand mal
frische MDs reinpackt um sie zu parsen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:45:50 +02:00
duffyduck 6ebee21bf0 chore(claude): .claude/*.env gitignored — .example als Vorlage commited
Damit kann die Dev-Maschine (wo Claude Code laeuft) die aria-wohnung-VM
ueber Diagnostic Port 3001 erreichen, ohne die interne IP im Git zu
haben. Pro Maschine wird .claude/aria-vm.env aus dem .example kopiert
und mit der lokalen Routing-Info gefuellt.

Nutzung:
  source .claude/aria-vm.env
  curl -s "$ARIA_BRAIN_URL/memory/stats"

Im docker-compose-Netz aria-net leben die Hostnamen (aria-brain etc.)
weiterhin direkt — das brauchst nur Hosts AUSSERHALB der VM.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:31:32 +02:00