[WebChat-Aktualisierung verliert interne System-/Heartbeat-Einträge] - WebChat Refresh Leaks Internal System/Heartbeat Entries
Nach dem Aktualisieren von WebChat gelangen interne Nachrichten (Systemnachrichten, Heartbeat-Antworten, Tool-Ketten-Artefakte) aufgrund inkonsistenter Verlaufsfilterung in buildChatItems in die sichtbare Unterhaltung.
🔍 Symptome
Visuelle Manifestationen
Nach einer WebChat-Seitenaktualisierung beobachten Benutzer die folgenden Artefakte in der Gesprächszeitlinie:
- Systemnachrichtenblöcke — Interne Systemereignisse, die als Chat-Elemente gerendert werden (z.B. „Refresh filter repro")
- Heartbeat-Bestätigungstext — Antwort-Nutzdaten wie
HEARTBEAT_OK, die für Endbenutzer sichtbar sind - Interne Werkzeug-/Status-Kettenelemente — Werkzeug-Ausführungsartefakte und Statusmeldungen, die in der Gesprächsansicht offengelegt werden
CLI-Reproduktionsbefehle
So lösen Sie interne Ereignisse zum Testen aus:
openclaw system event --mode now --text "Refresh filter repro"
Erwartetes vs. tatsächliches Verhalten
| Zustand | Erwartet | Aktuell (Fehler) |
|---|---|---|
| Vor der Aktualisierung | Sauberes Benutzer-/Assistenten-Gespräch | Sauberes Benutzer-/Assistenten-Gespräch |
| Nach der Aktualisierung | Sauberes Benutzer-/Assistenten-Gespräch | System-/Heartbeat-Artefakte sichtbar |
Umgebungsdetails
- Version: OpenClaw 2026.2.26
- OS: macOS 26.1 (Darwin 25.1.0, arm64)
- Installation: pnpm global binary (
/Users/bell/Library/pnpm/openclaw) - Feature-Flags: WebChat aktiviert, Heartbeat aktiviert
🧠 Ursache
Architekturanalyse
Das WebChat-Aktualisierungsleck resultiert aus einer Inkonsistenz in der zweiphasigen Architektur der Chat-Verlaufs-Rendering-Pipeline:
Phase 1: Verlauf neu laden (ui/src/ui/controllers/chat.ts)
Wenn WebChat initialisiert oder aktualisiert wird, lädt der Chat-Controller das vollständige chat.history-Array:
// ui/src/ui/controllers/chat.ts (Zeile ~N)
const history = chat.history; // Lädt den VOLLSTÄNDIGEN Verlauf einschließlich interner Einträge
Dies umfasst alle Nachrichtentypen, unabhängig von ihrer Klassifizierung als intern oder benutzerorientiert.
Phase 2: Unzureichende Filterung (ui/src/ui/views/chat.ts)
Die Funktion buildChatItems wendet Filterlogik an, die nicht ausreichend umfassend ist:
// ui/src/ui/views/chat.ts - buildChatItems-Funktion
function buildChatItems(history) {
return history.filter(item => {
// Aktuelle Filterlogik (unvollständig):
if (!item.thinking && item.type === 'toolresult') {
return false; // Unterdrückt toolresult nur, wenn thinking deaktiviert ist
}
// FEHLT: Keine Filterung für system/heartbeat/interne Nachrichtenklassen
return true;
});
}
Fehlerfolge
- Benutzer löst internes Ereignis aus (Heartbeat, Systembefehl)
- Interne Nachricht wird mit Klassifizierungs-Flags in
chat.historyeingefügt (z.B.role: 'system',internal: true,messageClass: 'heartbeat') - Seitenaktualisierung erfolgt
chat.tslädt den vollständigen Verlauf einschließlich interner EinträgebuildChatItemsscheitert beim Filtern dieser Einträge, da die Filterlogik nicht aufmessageClassoderinternal-Flags prüft- Interne Artefakte werden in der sichtbaren WebChat-Zeitlinie gerendert
Lücke in der Nachrichtenklassifizierung
Die aktuelle Implementierung verfügt über keinen systematischen Ansatz zur Nachrichtenklassifizierung. Interne Nachrichten sollten Metadaten-Flags mitführen, die das Rendering-Layer zur Filterung verwenden kann:
// Erwartete Nachrichtenstruktur mit Klassifizierung
{
id: "msg_xxx",
role: "system",
content: "HEARTBEAT_OK",
messageClass: "heartbeat", // FEHLT bei Filterprüfung
internal: true, // FEHLT bei Filterprüfung
visibleInChat: false // FEHLT bei Filterprüfung
}
🛠️ Schritt-für-Schritt-Lösung
Lösungsübersicht
Implementieren Sie einen Dev-Modus-Toggle, der die Nachrichtensichtbarkeit steuert:
- Dev-Modus AUS (Standard): Nur benutzerorientierte Konversationselemente anzeigen
- Dev-Modus AN: Volle Internals zu Debugging-Zwecken anzeigen
Phase 1: Nachrichtenklassifizierungs-Flags hinzufügen
Datei: packages/core/src/types/chat.ts
// Zur Nachrichtenschnittstelle hinzufügen
export interface ChatMessage {
id: string;
role: 'user' | 'assistant' | 'system' | 'tool';
content: string;
// ... vorhandene Felder
// NEU: Klassifizierung für UI-Filterung
internal?: boolean;
messageClass?: 'user' | 'assistant' | 'system' | 'heartbeat' | 'tool' | 'status';
visibleInChat?: boolean; // Explizite Überschreibung
}
Phase 2: buildChatItems-Filterung aktualisieren
Datei: ui/src/ui/views/chat.ts
// VORHER (unvollständige Filterung)
function buildChatItems(history, devMode = false) {
return history.filter(item => {
if (!item.thinking && item.type === 'toolresult') {
return false;
}
return true;
});
}
// NACHHER (umfassende Filterung)
function buildChatItems(history, devMode = false) {
return history.filter(item => {
// Explizite Sichtbarkeitsüberschreibung
if (item.visibleInChat === false && !devMode) {
return false;
}
// Interne Nachrichten im Produktionsmodus ausgeblendet
if (item.internal && !devMode) {
return false;
}
// Nachrichtenklassen-basierte Filterung
const hiddenClasses = ['heartbeat', 'system', 'status'];
if (hiddenClasses.includes(item.messageClass) && !devMode) {
return false;
}
// Legacy-Toolresult-Handhabung (wenn thinking deaktiviert ist)
if (!item.thinking && item.type === 'toolresult') {
return false;
}
return true;
});
}
Phase 3: Dev-Modus-Toggle implementieren
Datei: ui/src/ui/components/DevModeToggle.tsx
import { useState, useEffect } from 'react';
import { loadSetting, saveSetting } from '../utils/settings';
const DEV_MODE_KEY = 'openclaw_dev_mode';
export function DevModeToggle() {
const [devMode, setDevMode] = useState(() =>
loadSetting(DEV_MODE_KEY, false)
);
useEffect(() => {
saveSetting(DEV_MODE_KEY, devMode);
}, [devMode]);
return (
<div className="dev-mode-toggle">
<label>
<input
type="checkbox"
checked={devMode}
onChange={(e) => setDevMode(e.target.checked)}
/>
Dev Mode
</label>
<span className="dev-mode-indicator">
{devMode ? '🔧 Debugging' : '🚀 Production'}
</span>
</div>
);
}
Datei: ui/src/ui/utils/settings.ts
const SETTINGS_KEY = 'openclaw_ui_settings';
export function loadSetting(key: string, defaultValue: any): any {
try {
const settings = JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}');
return settings[key] ?? defaultValue;
} catch {
return defaultValue;
}
}
export function saveSetting(key: string, value: any): void {
try {
const settings = JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}');
settings[key] = value;
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
} catch (e) {
console.error('Failed to persist setting:', key, e);
}
}
Phase 4: Dev-Modus mit Chat-Controller verbinden
Datei: ui/src/ui/controllers/chat.ts
// VORHER
const history = chat.history;
// NACHHER
import { loadSetting } from '../utils/settings';
const DEV_MODE = loadSetting('openclaw_dev_mode', false);
const history = buildChatItems(chat.history, DEV_MODE);
Phase 5: Interne Nachrichten beim Einfügen markieren
Datei: packages/heartbeat/src/handler.ts (oder relevantes Heartbeat-Modul)
// Beim Einfügen der Heartbeat-Antwort
chat.history.push({
id: generateId(),
role: 'system',
content: 'HEARTBEAT_OK',
messageClass: 'heartbeat',
internal: true,
visibleInChat: false, // Explizite Unterdrückung
timestamp: Date.now()
});
🧪 Verifizierung
Testfall 1: Saubere Ansicht nach Aktualisierung verifizieren (Dev-Modus AUS)
# 1. WebChat mit aktiviertem Heartbeat starten
openclaw start --webchat --heartbeat
# 2. Internes Systemereignis auslösen
openclaw system event --mode now --text "Refresh filter test"
# 3. WebChat-Seite aktualisieren (Strg+Umschalt+R / Cmd+Umschalt+R)
# 4. Keine internen Artefakte im Gespräch verifizieren
# Erwartet: Nur Benutzernachrichten und Assistentenantworten sichtbar
# Auf Abwesenheit prüfen von: HEARTBEAT_OK, Systemnachrichtenblöcke, Werkzeugketten
Erwartete Ausgabe: Das Gespräch enthält nur Benutzer-/Assistenten-Austausche.
Testfall 2: Dev-Modus zeigt Internals
# 1. Dev-Modus-Toggle in WebChat-UI aktivieren
# 2. Seite aktualisieren
# 3. Interne Artefakte jetzt sichtbar verifizieren
# Erwartet: Systemnachrichten, Heartbeat-ACKs, Werkzeugketten sichtbar
Erwartete Ausgabe: Vollständige interne Nachrichtenkette mit Debug-Indikatoren sichtbar.
Testfall 3: Dev-Modus bleibt über Sitzungen hinweg erhalten
# 1. Dev-Modus aktivieren
# 2. WebChat-Tab schließen
# 3. WebChat erneut öffnen
# 4. Dev-Modus-Zustand bleibt erhalten verifizieren
# localStorage prüfen
window.localStorage.getItem('openclaw_ui_settings')
# Erwartet: {"openclaw_dev_mode":true}
Testfall 4: CLI-Verifizierung der Nachrichtenklassifizierung
# Verifizieren, dass Nachrichten korrekte Klassifizierung haben
openclaw chat history --format json | jq '.[] | select(.messageClass == "heartbeat")'
# Erwartet: Gibt Heartbeat-Einträge zurück (bestätigt Klassifizierung ist gesetzt)
Testfall 5: Regressionstest für Werkzeugresultat-Filterung
# 1. Thinking-Modus deaktivieren
openclaw config set thinking false
# 2. Einen Werkzeugaufruf ausführen (z.B. Dateilesen)
openclaw tool run read-file --path /tmp/test.txt
# 3. Seite aktualisieren
# 4. Verifizieren, dass Werkzeugresultate im AUS-Modus ausgeblendet, im AN-Modus sichtbar sind
Erwarteter Exit-Code: Alle Tests bestehen mit Exit-Code 0.
⚠️ Häufige Fehler
1. Race-Bedingung beim Verlaufsladen
Problem: Chat-Controller lädt möglicherweise den Verlauf, bevor die Dev-Modus-Einstellung aus localStorage gelesen wird.
Gegenmaßnahme: Dev-Modus synchron aus localStorage beim Modulladezeitpunkt initialisieren, nicht verzögert.
// KORREKT: Synchronische Initialisierung
const DEV_MODE = (() => {
try {
return JSON.parse(localStorage.getItem('openclaw_ui_settings') || '{}').openclaw_dev_mode ?? false;
} catch {
return false;
}
})();
// INCORRECT: Verzögerte Initialisierung (verursacht Race)
const getDevMode = async () => loadSetting(...); // NICHT SO MACHEN
2. Inkonsistenz bei der Nachrichtenklassifizierung
Problem: Einige Nachrichtenproduzenten (Heartbeat, Systemereignisse, Plugins) setzen möglicherweise keine messageClass- oder internal-Flags.
Gegenmaßnahme: Schema-Validator im Entwicklungsmodus implementieren:
// Zu buildChatItems hinzufügen
if (process.env.NODE_ENV === 'development') {
history.forEach(item => {
if (!item.messageClass && item.role === 'system') {
console.warn('[Dev] Systemnachricht mit fehlender messageClass:', item.id);
}
});
}
3. Docker/Container-Umgebung localStorage
Problem: WebChat in Docker-Containern kann isoliertes localStorage-Verhalten aufweisen.
Workaround: Dev-Modus-Präferenz zusätzlich zu localStorage über einen Backend-API-Aufruf persistieren:
// Fallback zu serverseitigen Präferenzen
async function getDevMode() {
const local = loadSetting('openclaw_dev_mode', false);
const server = await fetch('/api/user/preferences/dev-mode').catch(() => null);
return server ? await server.json() : local;
}
4. Verlaufsgröße und Speicher
Problem: Vollständigen Verlauf mit allen internen Nachrichten laden kann bei langen Gesprächen zu Speicherproblemen führen.
Gegenmaßnahme: History-Pruning für interne Nachrichten beim Speichern implementieren:
// Beim Persistieren des Chat-Zustands
function pruneInternalMessages(chat) {
return {
...chat,
history: chat.history.filter(item =>
item.internal ? false : true // Internals nicht persistieren
)
};
}
5. Multi-Tab-Synchronisation
Problem: Dev-Modus-Toggle in einem Tab wird möglicherweise nicht mit anderen offenen WebChat-Tabs synchronisiert.
Workaround: Auf Storage-Events hören:
window.addEventListener('storage', (e) => {
if (e.key === 'openclaw_ui_settings') {
// Mit aktualisierten Einstellungen neu rendern
forceUpdate();
}
});
6. Browser-Erweiterung-Konflikte
Problem: Erweiterungen, die localStorage modifizieren oder Skripte injizieren, können Einstellungen beschädigen.
Erkennung: Integritätsprüfung hinzufügen:
function validateSettings() {
try {
const settings = JSON.parse(localStorage.getItem('openclaw_ui_settings'));
return typeof settings.openclaw_dev_mode === 'boolean';
} catch {
return false;
}
}
🔗 Zugehörige Fehler
Logisch verbundene Probleme
- #26461 — Verlaufszustandskorruption nach schnellen Aktualisierungszyklen (verwandt: Zustandsverwaltung während des Neuladens)
- #21032 — Systemnachrichten erscheinen im Gesprächsexport (verwandt: Verlaufsfilterung an Exportgrenze)
- #12186 — Werkzeugkettensichtbarkeit respektiert UI-Präferenzen nicht (geschlossen/veraltet, aber ähnlicher Filterumfang)
Ähnliche Fehlermuster
| Fehlercode | Beschreibung | Verbindung |
|---|---|---|
E_HEARTBEAT_LEAK | Heartbeat-Antworten in benutzerorientierten Ansichten sichtbar | Direktes Symptom dieses Fehlers |
E_SYSTEM_MSG_LEAK | Systemnachrichten im normalen Chat-Modus offengelegt | Gleiche Ursache wie Heartbeat-Leck |
E_TOOLCHAIN_VISIBLE | Werkzeugausführungskette in Produktions-UI sichtbar | Verwandtes Filterungsproblem in buildChatItems |
E_HISTORY_RELOAD_ARTIFACTS | Veraltete interne Einträge erscheinen nach Seitenreload | Verlaufsneulade-Timing-Problem |
Architektonische Abhängigkeiten
ui/src/ui/controllers/chat.ts— Verlaufslade-Logikui/src/ui/views/chat.ts—buildChatItems-Filterfunktionpackages/heartbeat/src/handler.ts— Heartbeat-Nachrichteneinfügungpackages/core/src/types/chat.ts— Nachrichtentypdefinitionen