Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 22adc91c1e | |||
| 61cf8e3bcc | |||
| 3e38f1dad3 | |||
| 635944299e | |||
| b2ac013765 | |||
| 93db6a3156 | |||
| 579a466402 | |||
| 5133f0bc2d | |||
| a476a4b734 | |||
| 11b205ddaf |
@@ -219,11 +219,15 @@ Der Proxy-Container (`node:22-alpine`) installiert bei jedem Start:
|
||||
Danach wird der Proxy gepatcht:
|
||||
1. **Host-Binding** (sed): Server hoert auf `0.0.0.0` statt localhost
|
||||
2. **Tool-Permissions** (sed): `--dangerously-skip-permissions` Flag injizieren
|
||||
3. **Tool-Use-Adapter** (Datei-Overwrite aus [`proxy-patches/`](proxy-patches/)):
|
||||
3. **CLI-Timeout** (sed): `DEFAULT_TIMEOUT 300000 → 1200000` (5 → 20 Min) im subprocess-manager. Multi-Tool-Workflows mit echtem Bash + curl + DB-Inserts brauchen oft 8–15 Min; 5 Min war chronisch zu kurz
|
||||
4. **Tool-Use-Adapter** (Datei-Overwrite aus [`proxy-patches/`](proxy-patches/)):
|
||||
- `openai-to-cli.js` injiziert das OpenAI-`tools`-Feld als `<system>`-Block mit Schema-Beschreibungen + Anweisung `<tool_call name="X">{json}</tool_call>` als Antwortformat. `role=tool`-Messages werden als `<tool_result>`-Bloecke eingewoben. Multimodal-Content (Array von Parts) bleibt String-kompatibel.
|
||||
- `cli-to-openai.js` parsed `<tool_call>`-Bloecke aus Claudes Antwort und liefert sie als echte OpenAI `tool_calls` mit `finish_reason="tool_calls"`. Pre-Tool-Text bleibt im `content`. Mehrere parallele Calls werden korrekt aufgeteilt. Model-Name null-safe.
|
||||
- `routes.js` hookt die `assistant`-Events des Subprozesses und feuert pro `tool_use`-Block (Bash, Read, Edit, Grep, …) einen HTTP-POST an die Bridge (`/internal/agent-activity`). Bridge spiegelt das als RVS `agent_activity` an App+Diagnostic → der Gedanken-Stream zeigt live mit was ARIA gerade tut. Fire-and-forget, fail-open — Brain-Call bricht nicht ab wenn die Bridge mal nicht da ist.
|
||||
|
||||
**Warum?** Die npm-Version des Proxys ignoriert das `tools`-Feld komplett und reicht nur einen Prompt-String an die CLI weiter. Claude Code nutzt dann ihre internen Tools (Bash, Read, …) und „simuliert" Aktionen — z.B. `sleep 120` statt `trigger_timer`. Mit den eigenen Adaptern landen ARIA-Tools wieder auf der Linie und Side-Effects (Trigger anlegen, Skills aufrufen, GPS-Tracking schalten) funktionieren.
|
||||
**Warum?** Die npm-Version des Proxys ignoriert das `tools`-Feld komplett und reicht nur einen Prompt-String an die CLI weiter. Claude Code nutzt dann ihre internen Tools (Bash, Read, …) und „simuliert" Aktionen — z.B. `sleep 120` statt `trigger_timer`. Mit den eigenen Adaptern landen ARIA-Tools wieder auf der Linie und Side-Effects (Trigger anlegen, Skills aufrufen, GPS-Tracking schalten) funktionieren. Der Tool-Hook im `routes.js` macht zusaetzlich das interne Claude-Code-Werkzeug-Geschehen fuer den User sichtbar.
|
||||
|
||||
**Brain ↔ Bridge ist async**: `_handle_rvs_message` ruft `send_to_core` als `asyncio.create_task` statt `await` — sonst blockierte der WS-recv-Loop bis zu 20 Min und der RVS-Server (mobil.hacker-net.de) droppte die Bridge nach ~4 Min Idle-Timeout. Brain laeuft jetzt im Hintergrund-Task, RVS-Verbindung bleibt waehrend ARIA arbeitet aktiv.
|
||||
|
||||
**Wichtige Umgebungsvariablen im Proxy:**
|
||||
- `HOST=0.0.0.0` — API von aussen erreichbar (Docker-Netz)
|
||||
@@ -316,7 +320,7 @@ Erreichbar unter `http://<VM-IP>:3001`. Teilt das Netzwerk mit der Bridge.
|
||||
|
||||
### Tabs
|
||||
|
||||
- **Main**: Brain/RVS/Proxy-Status, Chat-Test, "ARIA denkt..."-Indikator, End-to-End-Trace, Container-Logs
|
||||
- **Main**: Brain/RVS/Proxy-Status, Chat-Test, "ARIA denkt..."-Indikator, **💭 Gedanken-Stream** (zentrales Modal, zeigt live alle Tool-Calls + Phasen mit Zeitstempel und Trennlinien bei langen Pausen), End-to-End-Trace, Container-Logs
|
||||
- **Gehirn**: Memory-Browser (Vector-DB), Suche mit zwei Modi (**📝 Wortlich** = Substring-Match Default + **🧠 Semantisch** mit Score-Threshold), **Advanced Search** (aufklappbares Panel, beliebig viele AND/OR-verknuepfte Felder, + Button fuer mehr Zeilen), Type+Pinned-Filter (greifen auch in der Suche), klappbare Type-Kategorien (Default eingeklappt), Add/Edit/Delete mit Category-Autosuggest, **📎 Anhaenge** pro Memory (Bilder/PDFs/...): Upload + Thumbnail-Vorschau + Lightbox + Lösch-Button, 📎N-Badge in der Liste, automatischer Cleanup beim Memory-Delete. ℹ-Info-Modal das erklaert welche Types FEST in den Prompt vs. Cold Memory wandern. **📄 Druckansicht** (Strg+P → PDF). Konversation-Status mit Destillat-Trigger, **Token/Call-Metrics mit Subscription-Quota-Tracking**, Bootstrap & Migration (3 Wiederherstellungs-Wege), Gehirn-Export/Import (tar.gz)
|
||||
- **Skills**: Liste aller Skills mit Logs pro Run, Activate/Deactivate, Export/Import als tar.gz, "von ARIA"-Badge fuer selbst gebaute
|
||||
- **Trigger**: passive Aufweck-Quellen. **Timer** (einmalig, ISO-Timestamp oder via `in_seconds` als Server-Berechnung) + **Watcher** (recurring, mit Condition + Throttle). Liste aktiver Trigger + Logs pro Feuer-Event. Modal mit Type-Dropdown, Live-Anzeige aller verfuegbaren Condition-Variablen (`disk_free_gb`, `hour_of_day`, `current_lat/lon`, `last_user_message_ago_sec`, …). **Drei GPS-Funktionen** mit unterschiedlicher Semantik:
|
||||
@@ -366,6 +370,7 @@ Erreichbar unter `http://<VM-IP>:3001`. Teilt das Netzwerk mit der Bridge.
|
||||
- **Jump-to-Bottom-Button**: erscheint rechts unten sobald man weg von der neuesten Nachricht scrollt, ein Tap fuehrt zurueck
|
||||
- **Delivery-Status pro User-Bubble** (WhatsApp-Style): `⏱` (queued, wartet auf Verbindung) → `⏳` (sending) → `✓` (Bridge hat ACK gesendet) → `✓✓` (ARIA hat verarbeitet). Bei Netzausfall werden Nachrichten lokal als queued gehalten und beim Reconnect automatisch geflusht. Bei drei ACK-Timeouts → `⚠ tippen f. Retry`. Idempotenz auf der Bridge (LRU ueber `clientMsgId`) verhindert Doppelte beim Retry
|
||||
- **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
|
||||
- **💭 Gedanken-Stream**: chronologisches Log was ARIA intern macht — gefuettert aus `agent_activity`-Events (denkt / 🔧 Tool-Name / schreibt / ✓ fertig). Live-Update waehrend Brain arbeitet: pro Tool-Call (Bash, Read, Edit, Grep, …) erscheint sofort ein Eintrag, durchgereicht vom claude-max-api-proxy via `proxy-patches/routes.js`-Hook. Lange Pausen zwischen Denk-Phasen werden als Trennlinie mit Minuten-Hint sichtbar. App: Icon in der Statusleiste oeffnet ein Bottom-Sheet, persistiert in AsyncStorage (capped 500). Diagnostic: identische Funktion als zentrales Modal im Chat-Test-Header
|
||||
- **🗂️ 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
|
||||
|
||||
@@ -79,8 +79,8 @@ android {
|
||||
applicationId "com.ariacockpit"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 10407
|
||||
versionName "0.1.4.7"
|
||||
versionCode 10501
|
||||
versionName "0.1.5.1"
|
||||
// Fallback fuer Libraries mit Product Flavors
|
||||
missingDimensionStrategy 'react-native-camera', 'general'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "aria-cockpit",
|
||||
"version": "0.1.4.7",
|
||||
"version": "0.1.5.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -324,7 +324,12 @@ const ChatScreen: React.FC = () => {
|
||||
|
||||
// Wie lange wir auf das ACK warten bevor wir retryen. Bridge sollte
|
||||
// unmittelbar zurueckmelden — 30s ist grosszuegig fuer schlechte Netze.
|
||||
const ACK_TIMEOUT_MS = 30_000;
|
||||
// 60s — grosszuegiger als 30s, weil langsame Brain-Calls (Multi-Tool) sonst
|
||||
// 90s × 3 Retries lang die User-Bubble auf ⏳ stehen lassen wuerden. Der
|
||||
// wichtige Pfad ist sowieso: agent_activity = thinking → markiert die
|
||||
// Bubble sofort als 'sent' (siehe handler). Das hier ist Fallback wenn
|
||||
// weder ACK noch agent_activity ankommt.
|
||||
const ACK_TIMEOUT_MS = 60_000;
|
||||
// Wie oft re-tryen wir bevor wir "failed" anzeigen.
|
||||
const MAX_SEND_ATTEMPTS = 3;
|
||||
// Pending ACK-Timer pro clientMsgId — fuer cancel beim ACK.
|
||||
@@ -686,23 +691,26 @@ const ChatScreen: React.FC = () => {
|
||||
// gesetzt UND text leer/Placeholder)
|
||||
// - User-Bubbles deren clientMsgId der Server noch nicht kennt:
|
||||
// z.B. waehrend Reconnect-Race oder solange flushQueuedMessages
|
||||
// noch laeuft. ABER: wenn der Server eine textgleiche Bubble
|
||||
// im gleichen 5-Min-Fenster hat (Alter Backup-Eintrag ohne
|
||||
// clientMsgId, vor dem Bridge-Patch geschrieben), werten wir
|
||||
// das als Treffer und verwerfen die lokale Kopie — sonst
|
||||
// Doppelpost: einmal als Server-Bubble (delivered) und einmal
|
||||
// als lokale failed/queued mit Retry-Knopf.
|
||||
const FIVE_MIN = 5 * 60 * 1000;
|
||||
// noch laeuft. ABER: wenn der Server eine textgleiche User-
|
||||
// Bubble hat (egal mit welcher cmid oder ohne — z.B. wenn die
|
||||
// Bubble aus einer Bridge-Version vor dem clientMsgId-Patch
|
||||
// stammt oder wenn die ts kaputt sind), werten wir das als
|
||||
// Treffer und verwerfen die lokale Kopie. Sonst Doppelpost:
|
||||
// einmal als Server-Bubble (delivered) und einmal als lokale
|
||||
// failed/queued mit Retry-Knopf.
|
||||
const serverUserTexts = new Set(
|
||||
fromServer.filter(s => s.sender === 'user').map(s => s.text || '')
|
||||
);
|
||||
const localOnly = prev.filter(m => {
|
||||
if (m.skillCreated || m.triggerCreated || m.memorySaved) return true;
|
||||
if (m.audioRequestId && (!m.text || m.text === '🎙 Aufnahme...' || m.text === 'Aufnahme...')) return true;
|
||||
if (m.sender === 'user' && m.clientMsgId && !serverCmids.has(m.clientMsgId)) {
|
||||
const serverHasIt = fromServer.some(s =>
|
||||
s.sender === 'user' &&
|
||||
s.text === m.text &&
|
||||
Math.abs((s.timestamp || 0) - (m.timestamp || 0)) < FIVE_MIN,
|
||||
);
|
||||
if (serverHasIt) return false;
|
||||
// Text-Match-Fallback: wenn der Server irgendwo eine textgleiche
|
||||
// User-Bubble hat, ist es dieselbe Nachricht (vor cmid-Aera, ts
|
||||
// kaputt etc.) — wir verwerfen die lokale Kopie. Leerer Text
|
||||
// (z.B. nur Anhang) faellt nicht in den Vergleich.
|
||||
const text = m.text || '';
|
||||
if (text && serverUserTexts.has(text)) return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1023,6 +1031,31 @@ const ChatScreen: React.FC = () => {
|
||||
const activity = (message.payload.activity as string) || 'idle';
|
||||
const tool = (message.payload.tool as string) || '';
|
||||
setAgentActivity({ activity, tool });
|
||||
// Implizite ACK-Bestaetigung: Brain hat angefangen zu arbeiten →
|
||||
// unsere Nachricht ist offensichtlich angekommen, auch wenn das
|
||||
// chat_ack aus irgendeinem Grund nicht durchkam. Alle laufenden
|
||||
// ACK-Timer canceln + sending-Bubbles auf 'sent' setzen.
|
||||
// Vermeidet das Symptom "Sanduhr bleibt + Timeout" bei langsamen
|
||||
// Brain-Antworten (>90 s, also nach 3 ACK-Retries auf failed).
|
||||
if (activity !== 'idle' && ackTimers.current.size > 0) {
|
||||
for (const cmid of Array.from(ackTimers.current.keys())) {
|
||||
clearAckTimer(cmid);
|
||||
}
|
||||
// Reference-stable: wenn keine Bubble zu aendern ist, geben wir
|
||||
// prev unveraendert zurueck. Sonst triggert .map() ein neues
|
||||
// Array + Re-Render, was waehrend einer aktiven Such-Scroll-
|
||||
// Sequenz die FlatList-Layouts invalidiert → permanenter
|
||||
// onScrollToIndexFailed-Loop.
|
||||
setMessages(prev => {
|
||||
const needs = prev.some(m => m.sender === 'user' && m.deliveryStatus === 'sending');
|
||||
if (!needs) return prev;
|
||||
return prev.map(m =>
|
||||
m.sender === 'user' && m.deliveryStatus === 'sending'
|
||||
? { ...m, deliveryStatus: 'sent' }
|
||||
: m,
|
||||
);
|
||||
});
|
||||
}
|
||||
// In den Gedanken-Stream einfuegen. Dedup gegen identische Folge-
|
||||
// Events (z.B. zwei mal 'thinking' direkt hintereinander). Tool-
|
||||
// Events NIE dedupen — wenn ARIA dreimal Bash hintereinander ruft,
|
||||
@@ -1370,11 +1403,18 @@ const ChatScreen: React.FC = () => {
|
||||
// ein neuer Search-Hit kommt, damit alte Retries nicht den neuen
|
||||
// Scroll-Versuch durcheinanderbringen ("permanent springen"-Bug).
|
||||
const pendingScrollRetry = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
// Zaehler fuer fehlgeschlagene Scroll-Retries. Hartes Limit gegen
|
||||
// Endlos-Loops wenn das Item-Layout aus irgendwelchen Gruenden nie
|
||||
// verfuegbar wird (z.B. weil setMessages mitten in der Sequenz die
|
||||
// FlatList re-rendert).
|
||||
const scrollRetryCount = useRef<number>(0);
|
||||
const MAX_SCROLL_RETRIES = 3;
|
||||
const clearPendingScrollRetry = () => {
|
||||
if (pendingScrollRetry.current) {
|
||||
clearTimeout(pendingScrollRetry.current);
|
||||
pendingScrollRetry.current = null;
|
||||
}
|
||||
scrollRetryCount.current = 0;
|
||||
};
|
||||
|
||||
// Bei Search-Index-Wechsel zur entsprechenden Bubble scrollen.
|
||||
@@ -1385,6 +1425,11 @@ const ChatScreen: React.FC = () => {
|
||||
// Den aktuellen Snapshot von invertedMessages holen wir via Ref.
|
||||
const invertedMessagesRef = useRef(invertedMessages);
|
||||
invertedMessagesRef.current = invertedMessages;
|
||||
// Schaetzwert fuer durchschnittliche Bubble-Hoehe. Wird fuer den
|
||||
// ersten Pre-Scroll genutzt wenn FlatList noch keine Layouts hat.
|
||||
// 150 px ist Mittel zwischen kurzen Voice-Bubbles (~70) und langen
|
||||
// ARIA-Antworten (~250).
|
||||
const AVG_BUBBLE_HEIGHT = 150;
|
||||
useEffect(() => {
|
||||
if (!searchMatchIds.length) {
|
||||
lastSearchScrollKey.current = '';
|
||||
@@ -1402,12 +1447,27 @@ const ChatScreen: React.FC = () => {
|
||||
clearPendingScrollRetry();
|
||||
const idx = invertedMessagesRef.current.findIndex(m => m.id === id);
|
||||
if (idx < 0 || !flatListRef.current) return;
|
||||
// Pre-Scroll: erst grob mit konstanter Hoehe in die Naehe springen.
|
||||
// Damit hat FlatList ueberhaupt die Chance die Layouts der Bubbles in
|
||||
// der Naehe zu rendern — sonst basiert info.averageItemLength im
|
||||
// Failed-Handler nur auf den ersten 10 Items (Default initialNumToRender)
|
||||
// und liefert beim ersten Such-Versuch nach App-Start einen voellig
|
||||
// falschen Sprung.
|
||||
try {
|
||||
flatListRef.current?.scrollToOffset({
|
||||
offset: idx * AVG_BUBBLE_HEIGHT,
|
||||
animated: false,
|
||||
});
|
||||
} catch {}
|
||||
// Nach kurzer Render-Pause praezise nachsetzen
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
flatListRef.current?.scrollToIndex({ index: idx, animated: true, viewPosition: 0 });
|
||||
} catch {
|
||||
// onScrollToIndexFailed-Handler uebernimmt den Fallback
|
||||
}
|
||||
setTimeout(() => {
|
||||
try {
|
||||
flatListRef.current?.scrollToIndex({ index: idx, animated: true, viewPosition: 0 });
|
||||
} catch {
|
||||
// onScrollToIndexFailed-Handler uebernimmt den Fallback
|
||||
}
|
||||
}, 80);
|
||||
});
|
||||
}, [searchIndex, searchMatchIds]);
|
||||
|
||||
@@ -2119,6 +2179,13 @@ const ChatScreen: React.FC = () => {
|
||||
ref={flatListRef}
|
||||
inverted
|
||||
data={invertedMessages}
|
||||
// Mehr Items beim Mount messen → bessere averageItemLength fuer
|
||||
// Such-Sprung gleich nach App-Start. Default sind 10 Items, das
|
||||
// ist bei 300+ Bubbles im Backup viel zu wenig.
|
||||
initialNumToRender={30}
|
||||
// Mehr Items im Speicher halten (Default 21 = 10 oben + 10 unten).
|
||||
// Macht scroll-to-far-away weniger anfaellig fuer Layout-Holes.
|
||||
windowSize={41}
|
||||
onScroll={(e) => {
|
||||
// Bei inverted FlatList: contentOffset.y > 0 = weg von "unten"
|
||||
// (= aelter scrollen). Wir zeigen den Jump-Down-Button ab ~250px.
|
||||
@@ -2128,13 +2195,24 @@ const ChatScreen: React.FC = () => {
|
||||
scrollEventThrottle={120}
|
||||
onScrollToIndexFailed={(info) => {
|
||||
// FlatList kennt das Item-Layout noch nicht. Wir scrollen grob in
|
||||
// die Naehe (Average-Item-Hoehe-Schaetzung) und versuchen EINMAL
|
||||
// nach 300ms praezise nachzusetzen. Mehr Retries → Endlos-Cascade
|
||||
// (jeder failed Retry triggert wieder den Handler → 3, 9, 27 ...
|
||||
// Scrolls in der Pipeline = der "permanent springen"-Bug).
|
||||
// die Naehe (Average-Item-Hoehe-Schaetzung) und versuchen bis zu
|
||||
// MAX_SCROLL_RETRIES mal praezise nachzusetzen. Danach geben wir
|
||||
// auf — User sieht die Bubble in der ungefaehren Naehe und kann
|
||||
// selber finetunen. Frueher: jeder failed Retry triggerte einen
|
||||
// neuen Retry ohne Limit → "permanent springen"-Bug, vor allem
|
||||
// wenn waehrenddessen setMessages die Layouts invalidierte.
|
||||
const offset = info.averageItemLength * info.index;
|
||||
try { flatListRef.current?.scrollToOffset({ offset, animated: false }); } catch {}
|
||||
clearPendingScrollRetry();
|
||||
if (pendingScrollRetry.current) {
|
||||
clearTimeout(pendingScrollRetry.current);
|
||||
pendingScrollRetry.current = null;
|
||||
}
|
||||
if (scrollRetryCount.current >= MAX_SCROLL_RETRIES) {
|
||||
// Aufgeben — Item ist offenbar nicht stabil renderbar
|
||||
scrollRetryCount.current = 0;
|
||||
return;
|
||||
}
|
||||
scrollRetryCount.current += 1;
|
||||
pendingScrollRetry.current = setTimeout(() => {
|
||||
pendingScrollRetry.current = null;
|
||||
try { flatListRef.current?.scrollToIndex({ index: info.index, animated: true, viewPosition: 0 }); } catch {}
|
||||
@@ -2314,7 +2392,15 @@ const ChatScreen: React.FC = () => {
|
||||
activeOpacity={1}
|
||||
onPress={() => setThoughtsVisible(false)}
|
||||
>
|
||||
<TouchableOpacity activeOpacity={1} style={{height:'60%', backgroundColor:'#0D0D1A', borderTopLeftRadius:16, borderTopRightRadius:16}}>
|
||||
{/* View statt TouchableOpacity, sonst konsumiert das die Touch-
|
||||
Events und die FlatList laesst sich nicht scrollen.
|
||||
onStartShouldSetResponder={true} blockt aber die Propagation
|
||||
an das aeussere TouchableOpacity (close-on-tap-outside). */}
|
||||
<View
|
||||
style={{height:'60%', backgroundColor:'#0D0D1A', borderTopLeftRadius:16, borderTopRightRadius:16}}
|
||||
onStartShouldSetResponder={() => true}
|
||||
onResponderTerminationRequest={() => false}
|
||||
>
|
||||
{/* Drag-Indicator */}
|
||||
<View style={{alignItems:'center', paddingTop:8, paddingBottom:4}}>
|
||||
<View style={{width:40, height:4, borderRadius:2, backgroundColor:'#2A2A3E'}} />
|
||||
@@ -2393,7 +2479,7 @@ const ChatScreen: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
|
||||
|
||||
@@ -997,8 +997,13 @@ class ARIABridge:
|
||||
"""Schreibt eine Zeile in /shared/config/chat_backup.jsonl.
|
||||
Wird von Diagnostic + App als History-Quelle gelesen.
|
||||
entry braucht mindestens {role, text}; ts wird ergaenzt.
|
||||
Returns den ts (auch fuer Bubble-Loeschen-Tracking)."""
|
||||
ts = int(asyncio.get_event_loop().time() * 1000)
|
||||
Returns den ts (auch fuer Bubble-Loeschen-Tracking).
|
||||
|
||||
WICHTIG: ts ist UNIX-ms (time.time()*1000), NICHT loop-time.
|
||||
Loop-time ist Container-monotonic — bei jedem Restart wieder 0.
|
||||
Das brach die App-History-Sortierung weil App-side Date.now()
|
||||
(echtes UNIX-ms) mit Bridge-Container-Uptime gemischt wurde."""
|
||||
ts = int(time.time() * 1000)
|
||||
try:
|
||||
line = {"ts": ts}
|
||||
line.update(entry)
|
||||
|
||||
@@ -350,6 +350,20 @@ Skills mit Tool-Use.
|
||||
- [x] **Delivery-Handshake (WhatsApp-Style)**: pro User-Bubble ein lokaler `clientMsgId` + `deliveryStatus` (queued/sending/sent/delivered/failed). Bridge sendet `chat_ack` zurueck (✓ sent) und schreibt die ID ins `chat_backup.jsonl`. ARIA-Reply markiert alle vorigen User-Bubbles als delivered (✓✓). LRU-Idempotenz auf der Bridge (200 cmids) verhindert Doppelte beim Retry. Offline-Queue: Nachrichten im Flugmodus bleiben lokal als ⏱-queued, beim Reconnect feuert `flushQueuedMessages`. ACK-Timeout 30 s, bis zu 3 Retries, danach ⚠ + Tap-fuer-Retry
|
||||
- [x] **Offline-Bubble verschwand nach Reconnect (Race)**: parallel laufen `chat_history_request` und `flushQueuedMessages` beim Reconnect; die History-Antwort kam an bevor die Bridge die Bubble persistiert hatte → Merge ersetzte den lokalen Stand → Bubble weg (war aber in Diagnostic drin). Fix: Bridge spiegelt `clientMsgId` im `chat_backup.jsonl`, App-Merge dedupt per cmid und behaelt lokale Bubbles deren ID der Server noch nicht kennt
|
||||
- [x] **Doppel-Bubble nach Retry**: Backup-Eintraege von vor dem cmid-Patch hatten keine `clientMsgId` — Server-Bubble (ohne cmid) und lokale failed-Bubble (mit cmid) standen beide im Merge. Plus ACK-Timer lief gelegentlich weiter obwohl die Bubble schon `delivered` war → Retry pushte den Status zurueck auf `sending`. Fix: Merge faellt zusaetzlich auf `text+timestamp`-Heuristik im 5-Min-Fenster zurueck; `dispatchWithAck` prueft per Ref ob die Bubble inzwischen `delivered` ist und cancelt dann; bei ARIA-Reply werden alle laufenden ACK-Timer gecleart
|
||||
- [x] **chat_backup ts war Container-Uptime statt UNIX-ms**: `_append_chat_backup` nutzte `asyncio.get_event_loop().time()` (Monotonic, bei jedem Restart wieder 0) statt `time.time()`. Folge: Server-Bubbles mit ts wie 394M (6 min Uptime) wurden in der App-History neben App-side Bubbles mit Date.now() (1.778e12) sortiert — Hello-Kitty-Konversation von gestern landete chronologisch nach heutigen Karten-Routen, neue Nachrichten verschwanden unter dem 500er-Cap. Plus: Doppelpost-Schutz griff nicht weil das 5-Min-ts-Fenster bei 1.7 Bio ms Diff nie zutraf. Fix: Bridge schreibt jetzt UNIX-ms, Migration-Script `tools/migrate_chat_backup_ts.py` repariert vorhandene jsonl (284/299 ts umgeschrieben auf der VM, Datei-Reihenfolge bleibt). App-Merge dedupt zusaetzlich per blossem Text-Match (ohne ts-Diff) — schuetzt auch gegen vorhandene lokale Duplikate
|
||||
- [x] **User-Bubble ⏳→failed bei langsamen ARIA-Antworten**: ACK-Timer (30 s × 3 Retries) lief durch obwohl Brain laengst arbeitete — wenn `chat_ack` aus irgendwelchen Gruenden nicht durchkam (RVS-Frame verloren etc.), wurde die Bubble nach 90 s auf failed gesetzt obwohl die Antwort gleich danach kam. Fix: jedes `agent_activity != idle`-Event ist impliziter ACK — Brain wuerde nicht arbeiten wenn es die Nachricht nicht haette. Beim ersten non-idle Event werden alle laufenden ACK-Timer gecanceled und sending-Bubbles auf 'sent' gesetzt. ACK_TIMEOUT_MS zusaetzlich von 30 s auf 60 s als Backup
|
||||
- [x] **Gedanken-Stream Modal scrollte nicht**: innerer `TouchableOpacity` (eigentlich nur fuer close-on-tap-outside-Schutz) hat alle Touch-Events konsumiert. Fix: durch `View` mit `onStartShouldSetResponder={true}` + `onResponderTerminationRequest={false}` ersetzt — blockt Tap-Propagation ohne Scrolls der Children zu verschlucken
|
||||
|
||||
### Brain-Hang: Multi-Tool-Timeouts + RVS-Block + Skill-Aggressivitaet
|
||||
|
||||
- [x] **Skill-Erstellung aggressiver als gewollt**: Prompt sagte „Harte Regel — IMMER Skill anlegen wenn pip-Library noetig". ARIA hat das wortwoertlich genommen und bei einer simplen pdf-extract-Frage sofort `skill_create` aufgerufen → Brain 12 Min blockiert (venv 2 min + pip install 10 min Timeout in `skills.py`). App zeigt „ARIA denkt", Bridge emitted nach 5 Min Timeout idle, User ohne Antwort. Fix in `prompts.py`: „Goldene Regel: NIE ungefragt Skills anlegen" + nur bei expliziter Anfrage („mach daraus einen Skill") und auch dann nur wenn die 4 Kriterien (wiederkehrend / nicht-trivial / parametrisierbar / wiederverwendbar) zutreffen. Greift auf der VM nach `docker compose restart aria-brain` ohne Re-Build
|
||||
- [x] **Brain-Timeouts 5 Min → 20 Min**: drei verkettete 5-Min-Timeouts (Bridge `urlopen`, Brain `proxy_client`, Proxy `DEFAULT_TIMEOUT` im claude-max-api-proxy npm-Modul) feuerten exakt gleichzeitig. Live in den Logs nachvollzogen: ein Proxy-Call brauchte 4m51s und wurde von der Bridge auf den Sekundenbruchteil genau gekappt. Aufgabenstellungen wie Karten-Rekonstruktion mit 10+ curl-Calls oder PDF-Verarbeitung brauchen aber locker 8–15 Min. Fix: alle drei Timeouts auf 1200 s, plus dritter sed-Patch im docker-compose proxy-Service (`DEFAULT_TIMEOUT = 300000 → 1200000`). App-Stuck-Watchdog auf 1260 s (21 Min, knapp drueber)
|
||||
- [x] **RVS-Block waehrend Brain-Call** (mobil.hacker-net.de:444 droppt nach 4 Min idle): `async for raw_message in ws: await _handle_rvs_message(...)` — das await blockierte den recv-Loop solange `send_to_core` lief. Die websockets-Lib beantwortete Pings im Hintergrund, aber der RVS-Server zaehlt nur echte App-Frames und droppt sonst die Verbindung. Symptom: App+Diagnostic zeigten „abgebrochen" obwohl Brain noch arbeitete. Fix: `send_to_core` als `asyncio.create_task` statt `await` — RVS-recv-Loop bleibt frei, neue Messages werden weiter verarbeitet, Verbindung bleibt lebendig
|
||||
|
||||
### Gedanken-Stream + Live-Tool-Events
|
||||
|
||||
- [x] **Gedanken-Stream in App + Diagnostic**: chronologisches Log was ARIA intern macht, gefuettert aus `agent_activity`-Events (thinking/tool/assistant/idle). Bleibt zwischen Denk-Phasen stehen, lange Pausen sichtbar als Trennlinie mit Minuten-Hint. App: 💭-Icon in der Statusleiste oeffnet Bottom-Sheet mit chronologischer Liste, 🗑-Confirm zum Leeren. Diagnostic: 💭 Gedanken-Button im Chat-Test-Header oeffnet zentrales Modal, Live-Update wenn neue Eintraege kommen (autoscroll ans Ende). Persistierung in AsyncStorage / localStorage, capped auf 500 Eintraege
|
||||
- [x] **Live-Tool-Events vom Proxy**: dritter Proxy-Patch (`proxy-patches/routes.js`) 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 RVS-Clients. Vorher kam pro Brain-Call nur EIN „💭 denkt" am Anfang und EIN „✓ fertig" am Ende — jetzt sieht man **live** in beiden UIs wie ARIA durch die Tools haengt. Hook ist fire-and-forget (ARIA_TOOL_HOOK_URL Env-Variable, default http://aria-bridge:8090/internal/agent-activity)
|
||||
|
||||
## Offen
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Migration: chat_backup.jsonl ts-Werte von Container-Uptime-ms auf UNIX-ms umstellen.
|
||||
|
||||
Hintergrund: vor dem Fix nutzte _append_chat_backup() `asyncio.get_event_loop().time()`,
|
||||
was Container-Monotonic ist (bei Restart wieder 0). Mischte sich mit App-side
|
||||
`Date.now()` (echtes UNIX-ms) → falsche Sortierung in der App-History.
|
||||
|
||||
Strategie: ts < 1e12 (keine UNIX-ms) werden umgeschrieben. Anker = file-mtime,
|
||||
decay 60 Sekunden pro Eintrag rueckwaerts. Datei-Reihenfolge bleibt erhalten
|
||||
(append-only war chronologisch korrekt, nur ts-Werte waren Unsinn).
|
||||
|
||||
Vorhandene UNIX-ms-Eintraege (file_deleted-Marker, neue Eintraege ab Bridge-Fix)
|
||||
werden unveraendert gelassen.
|
||||
|
||||
Idempotent: zweimal laufen lassen ist sicher — beim zweiten Mal sind alle ts
|
||||
schon UNIX-ms und werden nicht angefasst.
|
||||
|
||||
Backup: schreibt erst chat_backup.jsonl.bak, dann atomar replace.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
UNIX_MS_THRESHOLD = 10 ** 12 # < 1e12 ms = vor 2001 = unrealistisch fuer UNIX
|
||||
GAP_SECONDS = 60 # 1 Eintrag pro Minute rueckwaerts ab mtime
|
||||
|
||||
|
||||
def migrate(path: Path) -> None:
|
||||
if not path.exists():
|
||||
print(f"Datei nicht da: {path}")
|
||||
sys.exit(1)
|
||||
|
||||
raw = path.read_text(encoding="utf-8").splitlines()
|
||||
entries = []
|
||||
for raw_line in raw:
|
||||
s = raw_line.strip()
|
||||
if not s:
|
||||
continue
|
||||
try:
|
||||
entries.append(json.loads(s))
|
||||
except Exception as e:
|
||||
print(f" ueberspringe kaputte Zeile: {e}")
|
||||
continue
|
||||
|
||||
if not entries:
|
||||
print("Datei leer")
|
||||
return
|
||||
|
||||
file_mtime_ms = int(os.path.getmtime(path) * 1000)
|
||||
n = len(entries)
|
||||
fixed = 0
|
||||
|
||||
# Wir bauen einen Ersatz-ts (file_mtime - gap*minutes_back) nur fuer
|
||||
# Eintraege deren ts < UNIX_MS_THRESHOLD. file_deleted etc. mit echtem
|
||||
# UNIX-ms bleiben unangetastet.
|
||||
for i, entry in enumerate(entries):
|
||||
ts = entry.get("ts", 0)
|
||||
if not isinstance(ts, (int, float)) or ts < UNIX_MS_THRESHOLD:
|
||||
# Synth-ts vergeben: aelteste = mtime - n*gap, neueste = mtime
|
||||
new_ts = file_mtime_ms - (n - 1 - i) * GAP_SECONDS * 1000
|
||||
entry["ts"] = new_ts
|
||||
fixed += 1
|
||||
|
||||
if fixed == 0:
|
||||
print(f"Nichts zu migrieren ({n} Eintraege, alle ts schon UNIX-ms)")
|
||||
return
|
||||
|
||||
# Backup
|
||||
bak = path.with_suffix(path.suffix + ".bak")
|
||||
shutil.copy2(path, bak)
|
||||
print(f"Backup: {bak}")
|
||||
|
||||
# Atomic rewrite
|
||||
tmp = path.with_suffix(path.suffix + ".tmp")
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
for entry in entries:
|
||||
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
||||
tmp.replace(path)
|
||||
|
||||
print(f"Migration fertig: {fixed}/{n} ts umgeschrieben")
|
||||
print(f" aelteste neu : {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(entries[0]['ts'] / 1000))}")
|
||||
print(f" neueste neu : {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(entries[-1]['ts'] / 1000))}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
default = Path("/var/lib/docker/volumes/aria-agent_aria-shared/_data/config/chat_backup.jsonl")
|
||||
path = Path(sys.argv[1]) if len(sys.argv) > 1 else default
|
||||
migrate(path)
|
||||
Reference in New Issue
Block a user