From 019b17ff971c7597393c6d53282aee088ffa71bd Mon Sep 17 00:00:00 2001 From: duffyduck Date: Tue, 14 Jul 2026 23:18:06 +0200 Subject: [PATCH] fix(app): Queue friert nach Verbindungsabbruch nicht mehr ein (Issue 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zwei Ursachen: - pending_queue-Bubbles haben (noch) keine clientMsgId → der Reconnect- History-Sync erkannte sie nicht als lokal-only und verwarf sie, waehrend projectQueues den Eintrag behielt → 'N in Warteschlange' fror ein ohne sichtbare Nachricht. Jetzt bleiben pending_queue-Bubbles beim Sync erhalten. - Watchdog: haengt ein Kontext >15s auf 'running', obwohl der Brain ihn NICHT als busy meldet (Antwort beim Abbruch verloren), schaltet die Queue jetzt selbst weiter (dequeue oder idle) statt fuer immer zu blockieren. Co-Authored-By: Claude Opus 4.8 --- android/src/screens/ChatScreen.tsx | 31 ++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index dca71f7..503a4b0 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -318,6 +318,9 @@ const ChatScreen: React.FC = () => { const [projectQueues, setProjectQueues] = useState>({}); const projectStatesRef = useRef>({}); const projectQueuesRef = useRef>({}); + // Wann ist ein Kontext in 'running' gegangen? Fuer den Watchdog, der eine + // haengende Queue (z.B. Antwort waehrend Verbindungsabbruch verloren) loest. + const ctxRunningSinceRef = useRef>({}); // Pro-Projekt-Textfeld-Entwuerfe (noch nicht gesendeter Feldinhalt). Key = pid. const projectDraftsRef = useRef>({}); const prevFocusedPidRef = useRef(''); @@ -583,8 +586,24 @@ const ChatScreen: React.FC = () => { try { const s = await brainApi.getProjectQueueStatus(); if (cancelled) return; - setQueueStatus(s.contexts || {}); - queueStatusRef.current = s.contexts || {}; + const ctxs = s.contexts || {}; + setQueueStatus(ctxs); + queueStatusRef.current = ctxs; + // Watchdog: haengt ein Kontext seit >15s auf 'running', obwohl der Brain + // ihn NICHT als busy meldet, ist die Antwort verloren gegangen (z.B. + // Verbindungsabbruch) — sonst friert die Queue ein. Dann weiterschalten. + const api = queueApiRef.current; + if (api) { + const now = Date.now(); + for (const [pid, since] of Object.entries(ctxRunningSinceRef.current)) { + if (api.getCtxState(pid) !== 'running') continue; + const busy = !!ctxs[pid || '__main__']?.busy; + if (!busy && now - since > 15000) { + console.log('[Chat] Queue-Watchdog: Kontext %s haengt (running, brain idle) → weiterschalten', pid || '(main)'); + api.advanceQueue(pid); + } + } + } } catch {} }; poll(); @@ -918,6 +937,11 @@ const ChatScreen: React.FC = () => { 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; + // Wartende Queue-Bubbles (noch nicht gesendet → kein clientMsgId, nicht + // auf dem Server) MUESSEN erhalten bleiben — sonst verschwindet die + // Bubble beim Reconnect-Sync, waehrend projectQueues den Eintrag behaelt + // → "N in Warteschlange" friert ein ohne sichtbare Nachricht. + if (m.sender === 'user' && m.deliveryStatus === 'pending_queue') return true; if (m.sender === 'user' && m.clientMsgId && !serverCmids.has(m.clientMsgId)) { // Text-Match-Fallback: wenn der Server irgendwo eine textgleiche // User-Bubble hat, ist es dieselbe Nachricht (vor cmid-Aera, ts @@ -2050,6 +2074,9 @@ const ChatScreen: React.FC = () => { const setCtxState = useCallback((pid: string, s: CtxState) => { projectStatesRef.current = { ...projectStatesRef.current, [pid]: s }; + // Watchdog-Zeitstempel: nur 'running' bekommt einen Start, sonst raus. + if (s === 'running') ctxRunningSinceRef.current[pid] = Date.now(); + else delete ctxRunningSinceRef.current[pid]; setProjectStates(prev => ({ ...prev, [pid]: s })); }, []);