fix(chat): User-Bubble →failed bei langsamen ARIA-Antworten

Symptom: ARIA bearbeitet die Nachricht (im Gedanken-Stream sichtbar),
aber unter der User-Bubble bleibt die Sanduhr stehen und nach ~90 s
springt sie auf ⚠ failed. ARIA-Antwort kommt trotzdem irgendwann durch
— die Bubble war also nie weg, nur visuell schief.

Wurzel: chat_ack vom Bridge kam offenbar in manchen Faellen nicht
verlaesslich an. ACK-Timer (30 s × 3 Retries) lief durch → 'failed'.

Fix: agent_activity = thinking/tool/assistant ist impliziter Beweis,
dass das Brain die Nachricht bekommen und angefangen hat zu arbeiten.
Beim ersten non-idle Event:
- alle laufenden ACK-Timer cancelen
- alle 'sending'-User-Bubbles auf 'sent' (✓) setzen

ARIA-Reply markiert dann wie gehabt 'delivered' (✓✓). Damit kann keine
Bubble mehr auf failed gehen waehrend Brain noch laeuft.

Plus: ACK_TIMEOUT_MS 30 → 60 s als Backup-Reserve fuer den Fall dass
weder ACK noch agent_activity ankommt (sehr unwahrscheinlich, aber
billig).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 11:43:04 +02:00
parent a476a4b734
commit 5133f0bc2d
+22 -1
View File
@@ -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.
@@ -1026,6 +1031,22 @@ 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);
}
setMessages(prev => 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,