Compare commits

..
8 Commits
Author SHA1 Message Date
duffyduckandClaude Opus 4.7 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 2026-05-14 17:17:44 +02:00
duffyduckandClaude Opus 4.7 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
duffyduckandClaude Opus 4.7 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 2026-05-14 16:08:30 +02:00
duffyduckandClaude Opus 4.7 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 2026-05-14 16:00:42 +02:00
duffyduckandClaude Opus 4.7 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
11 changed files with 240 additions and 33 deletions
+4
View File
@@ -25,6 +25,10 @@ aria-data/brain-import/*
!aria-data/brain-import/.gitkeep
!aria-data/brain-import/README.md
# .aria-debug/ — App-Crash-Logs die tools/fetch-app-logs.sh hier ablegt.
# Komplett lokal, enthaelt potentiell private Stacktraces / Daten.
.aria-debug/
# ── ARIAs Gedächtnis (Vector-DB, Skills, Models) ──
# Backup via Diagnostic → Gehirn-Export (tar.gz), nicht via Git.
aria-data/brain/data/
+5
View File
@@ -357,6 +357,9 @@ Erreichbar unter `http://<VM-IP>:3001`. Teilt das Netzwerk mit der Bridge.
- **Play-Button**: Jede ARIA-Nachricht kann nochmal vorgelesen werden (aus Cache wenn vorhanden, sonst neu rendern)
- **Chat-Suche**: Lupe in der Statusleiste filtert Nachrichten live
- **Mülltonne pro Bubble** (mit Confirm): gezielt eine Nachricht loeschen — geht nicht nur aus der UI weg, sondern auch aus `chat_backup.jsonl`, Brain-Conversation-Window und allen anderen Clients (RVS-Broadcast). Wichtig damit ARIA den Turn auch beim naechsten Prompt nicht mehr im Kontext hat
- **🗂️ Notizen-Inbox + Memory-Editor**: Neben der Lupe oeffnet `🗂️` ein Vollbild-Modal mit allen Memory/Trigger/Skill-Spezial-Bubbles aus dem Chat plus dem vollen DB-Browser. Tap auf eine Memory oeffnet ein **Detail/Edit-Modal**: Felder editieren, Anhaenge hoch-/runterladen + loeschen, Memory komplett loeschen. Identischer Editor auch in Settings → 🧠 Gedaechtnis. Spezial-Bubbles werden aus dem Chat-Stream gefiltert (keine ewig-unten-haengenden Notiz-Bubbles mehr)
- **Bubble-Header dynamic**: „ARIA hat etwas gemerkt" / „Notiz geaendert" (gelb) / „Notiz geloescht" (rot) — je nach action im memory_saved-Event
- **App-Crash-Reporting**: ungefangene JS-Errors + React-Render-Fehler landen automatisch in `/shared/logs/app.log` via RVS — kein ADB noetig, Logs holen via `tools/fetch-app-logs.sh` oder Diagnostic GET `/api/app-log`. ErrorBoundary verhindert White-Screen, zeigt stattdessen Error-Box im Modal mit Stack-Trace + Schliessen-Button
- **Mehrere Anhaenge**: Bilder + Dateien sammeln, Text hinzufuegen, dann zusammen senden
- **Paste-Support**: Bilder aus Zwischenablage einfuegen (Diagnostic)
- **Anhaenge**: Bridge speichert in Shared Volume, ARIA kann darauf zugreifen, Re-Download ueber RVS
@@ -871,6 +874,8 @@ docker exec aria-brain curl localhost:8080/memory/stats
- [x] **Proxy Tool-Use durchreichen**: claude-max-api-proxy patcht via eigene Adapter (`proxy-patches/`) den `tools`/`tool_calls`-Roundtrip — Claude Code rief vorher ihre internen Tools (Bash, sleep) statt der ARIA-Brain-Tools (trigger_timer, skill_*, ...). Jetzt funktioniert Tool-Use End-to-End.
- [x] **Single Source of Truth — Qdrant**: `memory_save`-Tool fuer ARIA, Claude-Code-Auto-Memory abgeklemmt (tmpfs ueber `~/.claude/projects` im Proxy-Container), `brain-import/` zum reinen Drop-Folder degradiert, Cold-Memory mit Score-Threshold (0.30) gegen Embedder-Noise/Crosstalk, Diagnostic-Gehirn-UI mit Wortlich-/Semantisch-Suche, Advanced Search (AND/OR mit + Button), Memory-Druckansicht, Muelltonne pro Chat-Bubble. DB ist jetzt durchgaengig die einzige Wissensquelle, kein paralleles File-Memory mehr.
- [x] **Memory-Anhaenge mit Vision-Pipeline**: Pro Memory koennen Bilder/PDFs/beliebige Dateien angehaengt werden (unter `/shared/memory-attachments/<id>/`, max 20 MB). Diagnostic-UI mit Thumbnail-Vorschau + Lightbox, App `memory_saved`-Bubble mit Tap-to-Load via RVS, System-Prompt zeigt Anhang-Pfade. **ARIA sieht Bilder echt** via Claude Code's eingebautes multi-modales `Read`-Tool — kein Proxy-Patch noetig. `memory_save` hat `attach_paths`-Parameter sodass ARIA ein User-Foto im selben Tool-Call lesen, Infos extrahieren (Kennzeichen, Marken, Texte) und als Memory + Anhang persistieren kann. Bilder bleiben am Memory haengen — bei spaeteren Detail-Fragen liest ARIA das Bild einfach nochmal.
- [x] **Memory-Editor in der App** (5 Etappen): Notizen-Inbox-Button neben der Lupe oeffnet ein Modal mit allen Spezial-Bubbles aus dem aktuellen Chat plus dem vollen DB-Browser. Tap auf eine Memory → Detail-Modal mit Anhang-Vorschau, Stift-Icon wechselt in Edit-Mode (Felder editieren + Anhaenge hoch-/runterladen + loeschen). Identischer Editor unter Settings → 🧠 Gedaechtnis. Bubble-Header dynamic je nach Aktion (created/updated/deleted). RVS-Brain-Proxy als Fundament (`brain_request`/`brain_response`) damit die App beliebige Brain-HTTP-Endpoints adressieren kann. `memory_search` + `memory_update` als ARIA-Tools damit sie aktiv die DB pruefen und Eintraege patchen kann statt zu fragmentieren.
- [x] **App-Crash-Reporting via RVS**: ErrorBoundary + global JS-Error-Handler + Promise-Rejection-Tracker schicken Crashes als `app_log`-Event durch RVS. Bridge sammelt in `/shared/logs/app.log`, Diagnostic GET `/api/app-log`. `tools/fetch-app-logs.sh` holt die Logs auf die Dev-Maschine (gitignored `.aria-debug/`). Damit kann Stefan unterwegs ohne ADB debuggen — der erste Bug (URLSearchParams in Hermes) wurde so in 5 Minuten gefunden.
- [x] Sprachmodell-Setting wieder funktional (brainModel in runtime.json statt aria-core)
- [x] App-Chat-Sync: kompletter Server-Sync bei Reconnect (Server = Source of Truth) + chat_cleared Live-Update. Lokal-only Bubbles (Skill-Notifications, laufende Voice ohne STT) bleiben erhalten.
- [x] App: Chat-Suche mit Next/Prev Navigation statt Filter
+2 -2
View File
@@ -79,8 +79,8 @@ android {
applicationId "com.ariacockpit"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 10305
versionName "0.1.3.5"
versionCode 10309
versionName "0.1.3.9"
// Fallback fuer Libraries mit Product Flavors
missingDimensionStrategy 'react-native-camera', 'general'
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "aria-cockpit",
"version": "0.1.3.5",
"version": "0.1.3.9",
"private": true,
"scripts": {
"android": "react-native run-android",
+6
View File
@@ -169,6 +169,12 @@ export const MemoryBrowser: React.FC<Props> = ({ restrictToIds, title, flatStyle
data={filtered}
keyExtractor={m => m.id}
renderItem={renderItem}
// nestedScrollEnabled: notwendig damit die FlatList auf Android
// scrollt wenn sie in einer aeusseren ScrollView haengt (Settings-
// Screen ist ScrollView). Ohne das frisst der aeussere ScrollView
// alle Gesten und die innere Liste ist tot.
nestedScrollEnabled={true}
keyboardShouldPersistTaps="handled"
ListEmptyComponent={
<Text style={{color:'#555570',textAlign:'center',padding:20,fontStyle:'italic'}}>
{items.length === 0 ? '(keine Memories in der DB)' : '(keine Treffer für diese Filter)'}
+42 -14
View File
@@ -1375,17 +1375,30 @@ const ChatScreen: React.FC = () => {
<TouchableOpacity
key={`${item.id}-att-${idx}`}
style={styles.memoryAttachmentRow}
onPress={() => {
onPress={async () => {
if (!a.path) return;
if (a.localUri) {
if (isImage) setFullscreenImage(a.localUri);
else openFileWithIntent(a.localUri.replace(/^file:\/\//, ''), a.mime || '');
} else {
// Datei via Bridge nachladen — file_response hat den
// memorySaved-Match-Path und cached + zeigt direkt
autoOpenPaths.current.add(a.path);
rvs.send('file_request' as any, { serverPath: a.path, requestId: `memAtt_${item.id}_${idx}` });
const localPath = a.localUri.replace(/^file:\/\//, '');
const exists = await RNFS.exists(localPath).catch(() => false);
if (exists) {
if (isImage) setFullscreenImage(a.localUri);
else openFileWithIntent(localPath, a.mime || '');
return;
}
// Cache weg → localUri leeren + neu laden
setMessages(prev => prev.map(mm => mm.id === item.id && mm.memorySaved
? { ...mm, memorySaved: { ...mm.memorySaved,
attachments: mm.memorySaved.attachments?.map(x =>
x.path === a.path ? { ...x, localUri: undefined } : x) } }
: mm));
if (Platform.OS === 'android') {
ToastAndroid.show('Cache leer — lade nach...', ToastAndroid.SHORT);
}
}
// Datei via Bridge nachladen — file_response hat den
// memorySaved-Match-Path und cached + zeigt direkt
autoOpenPaths.current.add(a.path);
rvs.send('file_request' as any, { serverPath: a.path, requestId: `memAtt_${item.id}_${idx}` });
}}
>
<Text style={styles.memoryAttachmentIcon}>{icon}</Text>
@@ -1489,17 +1502,32 @@ const ChatScreen: React.FC = () => {
) : (
<TouchableOpacity
style={styles.attachmentFile}
onPress={() => {
// Lokal vorhanden \u2192 direkt mit System-Intent oeffnen
onPress={async () => {
// Lokal vorhanden? Cache koennte geleert worden sein \u2014
// Datei-Existenz pruefen bevor wir den Intent feuern.
if (att.uri) {
openFileWithIntent(att.uri.replace(/^file:\/\//, ''), att.mimeType || '');
return;
const localPath = att.uri.replace(/^file:\/\//, '');
const exists = await RNFS.exists(localPath).catch(() => false);
if (exists) {
openFileWithIntent(localPath, att.mimeType || '');
return;
}
// Cache weg \u2192 uri im State leeren damit UI "tippen zum Laden" zeigt
setMessages(prev => prev.map(m => m.id === item.id
? { ...m, attachments: m.attachments?.map(a =>
a.serverPath === att.serverPath ? { ...a, uri: undefined } : a) }
: m));
if (Platform.OS === 'android') {
ToastAndroid.show('Cache leer \u2014 lade nach...', ToastAndroid.SHORT);
}
}
// Sonst: file_request \u2192 bei file_response wird die Datei
// gespeichert UND geoeffnet (autoOpenPaths-Tracking).
// Re-Download via file_request \u2192 bei file_response wird die
// Datei gespeichert UND geoeffnet (autoOpenPaths-Tracking).
if (att.serverPath) {
autoOpenPaths.current.add(att.serverPath);
rvs.send('file_request' as any, { serverPath: att.serverPath, requestId: item.id });
} else if (Platform.OS === 'android') {
ToastAndroid.show('Datei kann nicht nachgeladen werden (kein serverPath)', ToastAndroid.LONG);
}
}}
>
+1 -1
View File
@@ -868,7 +868,7 @@ const SettingsScreen: React.FC = () => {
})()}
</View>
</Modal>
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
<ScrollView style={styles.container} contentContainerStyle={styles.content} nestedScrollEnabled={true}>
{currentSection === null && (
<>
+29 -15
View File
@@ -54,6 +54,18 @@ function _newRequestId(): string {
return `brain_${Date.now().toString(36)}_${_nextId}`;
}
/** Mini-Query-String-Builder ohne URLSearchParams (Hermes-Polyfill kennt
* kein URLSearchParams.set, crasht). Akzeptiert object mit string/number/
* bool-Values; undefined/null/leere Strings werden ausgelassen. */
function _qs(params: Record<string, unknown>): string {
const parts: string[] = [];
for (const [k, v] of Object.entries(params)) {
if (v === undefined || v === null || v === '') continue;
parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
}
return parts.length ? `?${parts.join('&')}` : '';
}
interface SendOpts {
method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
body?: AnyJson;
@@ -119,29 +131,31 @@ export const brainApi = {
/** Liste aller Memories, optional nach Type gefiltert. */
listMemories(opts: { type?: string; limit?: number } = {}): Promise<Memory[]> {
const qs = new URLSearchParams();
if (opts.type) qs.set('type', opts.type);
qs.set('limit', String(opts.limit || 500));
return _send(`/memory/list?${qs.toString()}`);
const qs = _qs({ type: opts.type, limit: opts.limit || 500 });
return _send(`/memory/list${qs}`);
},
/** Volltext-Substring-Suche. */
searchText(q: string, opts: { type?: string; includePinned?: boolean; k?: number } = {}): Promise<Memory[]> {
const qs = new URLSearchParams({ q });
if (opts.type) qs.set('type', opts.type);
qs.set('include_pinned', String(opts.includePinned !== false));
qs.set('k', String(opts.k || 50));
return _send(`/memory/search-text?${qs.toString()}`);
const qs = _qs({
q,
type: opts.type,
include_pinned: opts.includePinned !== false,
k: opts.k || 50,
});
return _send(`/memory/search-text${qs}`);
},
/** Semantische Suche (Embedder). */
searchSemantic(q: string, opts: { type?: string; includePinned?: boolean; k?: number; threshold?: number } = {}): Promise<Memory[]> {
const qs = new URLSearchParams({ q });
if (opts.type) qs.set('type', opts.type);
qs.set('include_pinned', String(opts.includePinned !== false));
qs.set('k', String(opts.k || 10));
qs.set('score_threshold', String(opts.threshold ?? 0.30));
return _send(`/memory/search?${qs.toString()}`);
const qs = _qs({
q,
type: opts.type,
include_pinned: opts.includePinned !== false,
k: opts.k || 10,
score_threshold: opts.threshold ?? 0.30,
});
return _send(`/memory/search${qs}`);
},
/** Memory anlegen. */
+11
View File
@@ -297,6 +297,17 @@ Skills mit Tool-Use.
- [x] **Gehirn-Kategorien standardmaessig eingeklappt**: Beim ersten Aufruf alle Type-Sections collapsed, Stefan klappt gezielt auf was er sehen will. State persistiert in localStorage
- [x] **Klappbare Type-Header + Category-AutoSuggest + Info-Modal**: Type-Header (▼/▶) klappbar, Category-Feld im Neu/Edit-Modal mit `<datalist>`-Vorschlaegen aller existierenden Categories, -Button-Modal erklaert welche Types FEST im System-Prompt vs. Cold Memory sind
### App-Memory-Editor + Crash-Reporting
- [x] **Bubble-Header dynamic** (created/updated/deleted): Die `🧠`-Bubble zeigt jetzt was passiert ist — "ARIA hat etwas gemerkt" / "Notiz geändert" / "Notiz gelöscht" (rot bei delete). Brain-Tools schicken `action`-Feld im memory_saved-Event mit
- [x] **Tap auf Memory-Bubble → Detail-Modal**: Komponente `MemoryDetailModal` zeigt alle Felder (Titel, Type, Category, Tags, voller Content, Anhang-Vorschau mit Thumbnails). Stift-Icon wechselt in Edit-Mode mit Form-Feldern + 📌 Pinned-Toggle. **Anhänge hoch-/runterladen + löschen** im Modal (DocumentPicker, multipart-Upload via RVS-Brain-Proxy). Memory komplett löschen mit Confirm
- [x] **Notizen-Inbox-Button (`🗂️`)** neben der Lupe in der Status-Leiste: Vollbild-Modal mit zwei Sections — „Aus diesem Chat" (kompakte Liste der Spezial-Bubbles aus dem aktuellen Verlauf, klickbar) + „Alle Memories aus der DB" mit dem `MemoryBrowser`. Spezial-Bubbles (memorySaved/triggerCreated/skillCreated) werden im Chat-Stream gefiltert (statt unten zu kleben)
- [x] **Memory-Editor in App-Settings**: neue Sektion 🧠 „Gedächtnis" in den App-Einstellungen. Komplette CRUD-UI mit Wortlich-Suche, Type-Dropdown, Pinned/Cold-Filter, „+ Neu" anlegen. Selbe `MemoryBrowser`-Komponente wie in der Inbox
- [x] **RVS-Brain-Proxy als Fundament**: Bridge implementiert generischen `brain_request` / `brain_response`-Channel — die App kann beliebige Brain-HTTP-Endpoints via RVS adressieren (GET/POST/PATCH/DELETE, JSON+Base64-Body, base64-encoded Binär-Antworten). `services/brainApi.ts` als Promise-basierter Client mit Request-ID-Routing, Timeout, automatischem Listener-Setup
- [x] **App-Crash-Reporting via RVS**: ErrorBoundary-Komponente fängt React-Render-Fehler, `installGlobalCrashReporter` haengt sich an `ErrorUtils.setGlobalHandler` + `HermesInternal.enablePromiseRejectionTracker`. Crashes wandern als `app_log`-Event durch RVS, Bridge schreibt JSONL in `/shared/logs/app.log`. Diagnostic-Server liefert GET `/api/app-log[?limit=N]` + POST `/api/app-log/clear`. **`tools/fetch-app-logs.sh`** holt die Logs auf die Dev-Maschine (über `ARIA_DIAG_URL` aus `.claude/aria-vm.env`), speichert in `.aria-debug/` (gitignored), zeigt Stack-Trace kompakt auf stdout
- [x] **`memory_search` + `memory_update` Tools**: ARIA kann die DB jetzt aktiv durchsuchen (Volltext/Semantic) und existierende Einträge per ID patchen statt fragmentierende neue anzulegen. Tool-Description sagt explizit „Memory ist Truth über Conversation-Window" — wenn der User korrigiert hat, gilt das was im Memory steht. Wichtig nach Diagnostic-Edits damit ARIA die neue Wahrheit sieht statt aus dem Window zu raten
- [x] **App-Bugfixes**: (a) URLSearchParams crasht in Hermes — durch Mini-Query-Builder ersetzt (`brainApi._qs()`). (b) Cache leer + Datei-Tap → Auto-Re-Download via file_request statt Toast-Sackgasse, plus State-Cleanup (uri/localUri auf undefined). (c) Memory-Liste in Settings scrollt jetzt (nestedScrollEnabled auf FlatList + äußere ScrollView). (d) Modal-im-Modal auf Android gefixt — MemoryBrowser nimmt optionalen `onOpenMemory`-Callback, kein verschachteltes DetailModal mehr. (e) Alert.prompt (iOS-only) durch eigenes Text-Input-Modal ersetzt fuer „Neue Memory anlegen"
### Memory-Anhaenge mit Vision (Stufe A-E + attach_paths)
- [x] **Anhaenge an Memory-Eintraege** — Bilder/PDFs/beliebige Dateien koennen an jede Memory gehaengt werden, liegen physisch unter `/shared/memory-attachments/<memory-id>/`. Cleanup beim Memory-Delete automatisch. Limit 20 MB pro Datei
+34
View File
@@ -0,0 +1,34 @@
# tools/
Hilfsskripte für die Dev-Maschine. Brauchen `.claude/aria-vm.env` (aus
`.example` kopieren + lokale VM-IP eintragen).
## fetch-app-logs.sh
Holt App-Crash-Logs von der VM und speichert sie unter `.aria-debug/`
(gitignored). Die App schickt JS-Errors und ungefangene Promise-
Rejections via RVS an die Bridge — Bridge sammelt in
`/shared/logs/app.log`, Diagnostic-Server gibt sie via
`GET /api/app-log` raus.
```bash
tools/fetch-app-logs.sh # 200 neueste Eintraege
tools/fetch-app-logs.sh --limit 50 # weniger
tools/fetch-app-logs.sh --watch # alle 5s pollen, neue rausgeben
tools/fetch-app-logs.sh --clear # nach Abholen Log auf VM leeren
```
Ausgabe enthaelt pro Eintrag: Uhrzeit, Level (error/warn/info), Scope
(z.B. `ChatScreen.InboxModal` oder `global-fatal`), Message, und die
ersten ~8 Stack-Frames. Die kompletten Daten liegen als JSON in
`.aria-debug/app-log-<timestamp>.json`.
Workflow nach einem Crash:
1. App rebuilden mit Crash-Reporting (passiert automatisch ab dem
`21a315c`-Commit)
2. Crash in der App ausloesen
3. `tools/fetch-app-logs.sh` auf der Dev-Maschine
4. Stacktrace lesen / Claude geben
5. Fix bauen
6. `tools/fetch-app-logs.sh --clear` damit der Log wieder sauber ist
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# fetch-app-logs.sh — App-Crash-Logs von der VM holen
#
# Nutzt .claude/aria-vm.env als Quelle fuer $ARIA_DIAG_URL und ruft
# GET /api/app-log?limit=N. Speichert die Roh-Response unter
# .aria-debug/app-log-<timestamp>.json und gibt eine kompakte
# Zusammenfassung auf stdout aus (letzte Eintraege mit Stack-Trace).
#
# Verwendung:
# tools/fetch-app-logs.sh # Default limit=200
# tools/fetch-app-logs.sh --limit 50 # nur 50 holen
# tools/fetch-app-logs.sh --clear # nach Abholen Log loeschen
# tools/fetch-app-logs.sh --watch # alle 5s pollen, neue Eintraege ausgeben
set -euo pipefail
LIMIT=200
CLEAR=0
WATCH=0
while [[ $# -gt 0 ]]; do
case "$1" in
--limit) LIMIT="$2"; shift 2 ;;
--limit=*) LIMIT="${1#*=}"; shift ;;
--clear) CLEAR=1; shift ;;
--watch) WATCH=1; shift ;;
-h|--help)
sed -n '1,/^set/p' "$0" | sed '$d' | sed 's/^# \{0,1\}//'
exit 0 ;;
*) echo "Unbekannte Option: $1" >&2; exit 1 ;;
esac
done
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
ENV_FILE="$ROOT/.claude/aria-vm.env"
OUT_DIR="$ROOT/.aria-debug"
if [[ ! -f "$ENV_FILE" ]]; then
echo "FEHLER: $ENV_FILE nicht vorhanden. Aus .example kopieren und IP anpassen." >&2
exit 1
fi
# shellcheck disable=SC1090
source "$ENV_FILE"
if [[ -z "${ARIA_DIAG_URL:-}" ]]; then
echo "FEHLER: ARIA_DIAG_URL nicht gesetzt in $ENV_FILE" >&2
exit 1
fi
mkdir -p "$OUT_DIR"
fetch_once() {
local ts json file
ts="$(date +%Y%m%d_%H%M%S)"
json="$(curl -s --max-time 10 "${ARIA_DIAG_URL%/}/api/app-log?limit=$LIMIT")" || {
echo "FEHLER: curl gegen $ARIA_DIAG_URL fehlgeschlagen" >&2
return 1
}
file="$OUT_DIR/app-log-$ts.json"
echo "$json" > "$file"
python3 - "$file" <<'PY'
import json, sys
from pathlib import Path
data = json.loads(Path(sys.argv[1]).read_text())
entries = data.get("entries") or []
print(f"=== {len(entries)} Eintrag{'e' if len(entries)!=1 else ''} (gespeichert unter {sys.argv[1]}) ===")
for e in entries[-20:]:
ts = e.get("ts") or 0
from datetime import datetime
when = datetime.fromtimestamp(ts/1000).strftime("%H:%M:%S") if ts else "?"
lvl = e.get("level","?")
scope = e.get("scope","?")
msg = (e.get("message") or "").splitlines()[0][:200]
print(f"\n[{when}] {lvl:5} {scope}: {msg}")
stack = (e.get("stack") or "").strip()
if stack:
for line in stack.splitlines()[:8]:
print(f" {line}")
if len(stack.splitlines()) > 8:
print(f" ... ({len(stack.splitlines())-8} weitere Zeilen — siehe JSON)")
PY
return 0
}
if [[ "$WATCH" == "1" ]]; then
echo "Watching $ARIA_DIAG_URL/api/app-log — Ctrl+C zum Beenden"
SEEN=""
while true; do
cur=$(curl -s --max-time 10 "${ARIA_DIAG_URL%/}/api/app-log?limit=$LIMIT") || cur=""
hash=$(echo "$cur" | md5sum | awk '{print $1}')
if [[ "$hash" != "$SEEN" && -n "$cur" ]]; then
SEEN="$hash"
fetch_once
fi
sleep 5
done
else
fetch_once
fi
if [[ "$CLEAR" == "1" ]]; then
echo
echo "→ Log auf der VM leeren..."
curl -s --max-time 5 -X POST "${ARIA_DIAG_URL%/}/api/app-log/clear" | python3 -m json.tool || true
fi