fix(chat): Offline-Bubble verschwand nach Reconnect — clientMsgId-Dedup
Race-Bug nach Etappe 3: Beim Reconnect schickt die App parallel chat_history_request und (via flushQueuedMessages) die offline gestaute Nachricht. Die history_response kam an bevor die Bridge die Bubble in chat_backup.jsonl geschrieben hatte → Server-Liste ohne unsere Bubble → Merge ersetzte den lokalen Stand → Bubble weg (im Diagnostic war sie gleich danach drin). Bridge: _append_chat_backup nimmt clientMsgId mit auf. send_to_core reicht sie als kwarg durch (chat- und audio-Pfad). App: chat_history_response-Merge dedupt per clientMsgId. Lokale User- Bubbles deren clientMsgId der Server noch nicht kennt bleiben erhalten (localOnly-Filter erweitert). Server-User-Bubbles mit clientMsgId kriegen deliveryStatus='delivered' damit das ✓✓ auch nach Reload sichtbar bleibt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+18
-7
@@ -1319,7 +1319,7 @@ class ARIABridge:
|
||||
await self.send_to_core(text, source="app-file+chat")
|
||||
return True
|
||||
|
||||
async def send_to_core(self, text: str, source: str = "bridge") -> None:
|
||||
async def send_to_core(self, text: str, source: str = "bridge", client_msg_id: Optional[str] = None) -> None:
|
||||
"""Sendet Text an aria-brain (HTTP /chat) und broadcastet die Antwort.
|
||||
|
||||
Nicht-Streaming: wir warten bis Brain fertig ist, dann pushen wir
|
||||
@@ -1333,8 +1333,13 @@ class ARIABridge:
|
||||
logger.info("[brain] chat ← %s '%s'", source, text[:80])
|
||||
|
||||
# User-Nachricht in chat_backup.jsonl loggen — wird beim App-Reconnect
|
||||
# / Diagnostic-Reload als History-Quelle gelesen.
|
||||
self._append_chat_backup({"role": "user", "text": text, "source": source})
|
||||
# / Diagnostic-Reload als History-Quelle gelesen. clientMsgId speichern
|
||||
# damit die App beim chat_history_response ihre lokale Bubble
|
||||
# dedupen kann (sonst verschwindet sie nach Offline→Online-Race).
|
||||
entry: dict = {"role": "user", "text": text, "source": source}
|
||||
if client_msg_id:
|
||||
entry["clientMsgId"] = client_msg_id
|
||||
self._append_chat_backup(entry)
|
||||
|
||||
# agent_activity → thinking. _emit_activity statt direktem _send_to_rvs
|
||||
# damit der State-Cache fuer die spaetere idle-Dedup richtig steht.
|
||||
@@ -1634,7 +1639,9 @@ class ARIABridge:
|
||||
" [BARGE-IN]" if interrupted else "",
|
||||
" [GPS]" if location else "",
|
||||
text[:80])
|
||||
await self.send_to_core(core_text, source="app" + (" [barge-in]" if interrupted else ""))
|
||||
await self.send_to_core(core_text,
|
||||
source="app" + (" [barge-in]" if interrupted else ""),
|
||||
client_msg_id=client_msg_id)
|
||||
return
|
||||
|
||||
if msg_type == "cancel_request":
|
||||
@@ -2234,7 +2241,8 @@ class ARIABridge:
|
||||
" [GPS]" if location else "",
|
||||
f" reqId={audio_request_id[:16]}" if audio_request_id else "")
|
||||
asyncio.create_task(self._process_app_audio(
|
||||
audio_b64, mime_type, interrupted, audio_request_id, location))
|
||||
audio_b64, mime_type, interrupted, audio_request_id, location,
|
||||
client_msg_id=client_msg_id))
|
||||
|
||||
elif msg_type == "stt_response":
|
||||
# Antwort der whisper-bridge auf unseren stt_request
|
||||
@@ -2293,7 +2301,8 @@ class ARIABridge:
|
||||
async def _process_app_audio(self, audio_b64: str, mime_type: str,
|
||||
interrupted: bool = False,
|
||||
audio_request_id: str = "",
|
||||
location: Optional[dict] = None) -> None:
|
||||
location: Optional[dict] = None,
|
||||
client_msg_id: Optional[str] = None) -> None:
|
||||
"""App-Audio → STT → aria-core. Primaer via whisper-bridge (RVS), Fallback lokal.
|
||||
|
||||
interrupted=True wenn der User waehrend ARIA noch sprach/dachte aufgenommen hat
|
||||
@@ -2349,7 +2358,9 @@ class ARIABridge:
|
||||
|
||||
# Dann an Brain — der blockt synchron bis ARIA fertig ist.
|
||||
core_text = self._build_core_text(text, interrupted, location)
|
||||
await self.send_to_core(core_text, source="app-voice" + (" [barge-in]" if interrupted else ""))
|
||||
await self.send_to_core(core_text,
|
||||
source="app-voice" + (" [barge-in]" if interrupted else ""),
|
||||
client_msg_id=client_msg_id)
|
||||
else:
|
||||
logger.info("[rvs] Keine Sprache erkannt — ignoriert")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user