Compare commits

...
3 Commits
Author SHA1 Message Date
duffyduckandClaude Opus 4.7 950a9d009c feat(diagnostic): Info-Buttons mit Modal-Erklaerungen im Gehirn-Tab
Reusable Info-Modal-System: kleines (ℹ)-Button neben Ueberschriften, beim
Klick oeffnet ein Modal mit ausfuehrlicher Erklaerung. Fuer die 4 wichtigsten
Brain-Konzepte sind die Texte vor-definiert (INFO_TEXTS dict).

  - Gehirn — Status     online/offline, N Memories, Qdrant-Endpoint
  - Konversation        Rolling Window 50, Schwelle 60, Destillat-Logik,
                        Hinweis warum chat_backup ≠ conversation.jsonl
  - Memories            Hot vs. Cold, alle 8 Typen erklaert, semantische Suche
  - Bootstrap           Die drei Wege (Migration / Snapshot / Komplett-Gehirn)

Plus inline-ℹ-Button neben der "Konversation: N Turns"-Zeile in der
Status-Card, damit man dort wo's relevant ist sofort die Erklaerung findet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 23:03:47 +02:00
duffyduckandClaude Opus 4.7 693542ef19 fix(diagnostic): Brain-Card live + Runtime-Konfig-Text + Konversation-Reset macht beides
Drei zusammenhaengende Fixes — alle aus der OpenClaw-Umstellung uebrig.

Brain-Card im Main-Tab
  - updateState() schrieb "aria-core entfernt — Brain-Loop in Arbeit" in
    die Card, weil state.gateway noch den alten OpenClaw-Status-Text
    transportiert hat. Brain ist online, sah aber auf der Diagnostic
    aus als waere alles tot.
  - Fix: updateState laesst die Brain-Card jetzt in Ruhe. Daten kommen
    exklusiv von loadBrainStatus() (fetch /api/brain/health).
  - WS-onopen ruft jetzt loadBrainStatus(), plus 15s-Interval fuer
    Live-Refresh.

Runtime-Konfiguration
  - Hinweis-Text erwaehnte noch aria.env + "Bridge-Container neu starten".
    aria.env gibt's nicht mehr, und Brain liest die Config auch.
  - Neu: ".env / Bridge UND Brain lesen sie beim Start — nach Aenderung
    den jeweiligen Container neu starten (Reparatur-Section oben)."

Konversation-Reset
  - Vorher zwei verschiedene Konversations-Stores ohne klare UI:
    chat_backup.jsonl (Diagnostic-Anzeige) und conversation.jsonl
    (Brain Rolling Window). "Konversation leeren" leerte nur Brain,
    die UI zeigte alle Turns weiter → verwirrend.
  - Neu: ein Button "🧹 Konversation komplett zurücksetzen" leert
    beides parallel (Promise.all auf /api/brain/conversation/reset
    + /api/chat-history-clear) plus die lokale Chat-View.
  - Erklaerungstext darunter macht klar was passiert + was bleibt
    (destillierte Facts + Memories in der Vector-DB).
  - Neuer Endpoint /api/chat-history-clear loescht chat_backup.jsonl
    und broadcastet leere chat_history an alle Browser-Clients.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:59:35 +02:00
duffyduckandClaude Opus 4.7 d12f356ebe fix(diagnostic): JS-Crash beim Laden durch Ghost-IDs aus OpenClaw-Zeit
Die Diagnostic-Seite lud nicht mehr richtig: bei jedem state-Update vom
Server crashte updateState() auf null.className weil 'gw-dot' nicht mehr
existiert (Gateway-Card wurde durch Brain-Card ersetzt). Mehrere weitere
Stellen waren ebenfalls auf nicht mehr existierende Elemente geleitet.

Bereinigt:
  - updateState() nutzt jetzt brain-dot/short/error (null-safe)
  - openCoreTerminal entfernt (aria-core ist raus)
  - closeTermModal null-safe fuer btn-core-term
  - 'core_auth' WS-Event entfernt
  - 'session_restarted' WS-Event entfernt (perms-status-Element war auch raus)
  - 'openclaw_config' WS-Event entfernt
  - rc-compact-after read/write aus loadRuntimeConfig/saveRuntimeConfig raus
    (Compact-After-Messages-Setting wurde mit aria-core entfernt)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:53:30 +02:00
2 changed files with 159 additions and 60 deletions
+142 -60
View File
@@ -120,6 +120,16 @@
/* Settings */
.settings-section { margin-bottom:20px; }
.settings-section h2 { margin-bottom:12px; }
/* Info-Button: kleines (i) neben Ueberschriften */
.info-btn { background:transparent; border:1px solid #0096FF; color:#0096FF; width:20px; height:20px;
border-radius:50%; padding:0; font-size:11px; font-weight:bold; cursor:pointer; margin-left:6px;
line-height:18px; text-align:center; vertical-align:middle; font-family:serif; }
.info-btn:hover { background:#0096FF; color:#fff; }
.info-btn-small { background:transparent; border:1px solid #0096FF44; color:#0096FF; width:14px; height:14px;
border-radius:50%; padding:0; font-size:9px; font-weight:bold; cursor:pointer; margin-left:4px;
line-height:11px; text-align:center; vertical-align:middle; font-family:serif; }
.info-btn-small:hover { background:#0096FF; color:#fff; }
.toggle { position:relative; width:40px; height:22px; flex-shrink:0; margin-left:8px; }
.toggle input { opacity:0; width:0; height:0; }
.toggle .slider { position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0;
@@ -592,14 +602,14 @@
</div>
</div>
<!-- Runtime-Konfiguration (migriert von .env) -->
<!-- Runtime-Konfiguration -->
<div class="settings-section">
<h2>Runtime-Konfiguration</h2>
<div style="font-size:11px;color:#8888AA;margin-bottom:8px;">
Werte werden in <code>/shared/config/runtime.json</code> persistiert und
ueberschreiben die ENV-Variablen aus <code>aria.env</code>. Bridge liest
sie beim naechsten Start — nach Aenderung <b>Bridge-Container neu starten</b>
(Diagnostic-Container bleibt auf ENV).
ueberschreiben die ENV-Variablen aus der <code>.env</code>. Bridge und Brain
lesen sie beim Start — nach Aenderung den jeweiligen Container neu starten
(Reparatur-Section oben).
</div>
<div class="card" style="max-width:600px;">
<div style="display:grid;grid-template-columns:140px 1fr;gap:8px 10px;align-items:center;font-size:13px;">
@@ -689,20 +699,22 @@
<div id="tab-brain" class="main-tab">
<div class="settings-section">
<h2>Gehirn — Status</h2>
<h2>Gehirn — Status <button class="info-btn" onclick="showInfo('brain-status')" title="Was bedeutet was?"></button></h2>
<div class="card">
<div id="brain-status" style="font-size:12px;color:#8888AA;margin-bottom:8px;">(Lade...)</div>
<div id="conversation-status" style="font-size:12px;color:#8888AA;margin-bottom:8px;"></div>
<div id="conversation-status" style="font-size:12px;color:#8888AA;margin-bottom:8px;">
<button class="info-btn-small" onclick="showInfo('conversation')" title="Konversation — wie funktioniert das?"></button>
</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<button class="btn secondary" onclick="loadBrainStatus()" style="padding:4px 12px;font-size:11px;">Aktualisieren</button>
<button class="btn secondary" onclick="distillNow()" style="padding:4px 12px;font-size:11px;color:#FFD60A;border-color:#FFD60A;" title="Destilliert die aeltesten Turns sofort zu fact-Memories">⚗ Jetzt destillieren</button>
<button class="btn secondary" onclick="resetConversation()" style="padding:4px 12px;font-size:11px;color:#FF6B6B;border-color:#FF6B6B;" title="Rolling-Window leeren — destillierte Facts bleiben in der DB">🧹 Konversation leeren</button>
<button class="btn secondary" onclick="resetConversation()" style="padding:4px 12px;font-size:11px;color:#FF6B6B;border-color:#FF6B6B;" title="Leert ARIAs Rolling-Window (Brain) + die Chat-Anzeige (chat_backup). Memories bleiben in der Vector-DB.">🧹 Konversation komplett zurücksetzen</button>
</div>
</div>
</div>
<div class="settings-section">
<h2>Bootstrap & Migration</h2>
<h2>Bootstrap & Migration <button class="info-btn" onclick="showInfo('bootstrap')" title="Was sind die drei Wege?"></button></h2>
<div class="card" style="line-height:1.6;">
<p style="color:#8888AA;font-size:12px;margin:0 0 12px;">
Drei Wege ARIA mit "Grundregeln" zu füttern — von leichtgewichtig bis Voll-Wiederherstellung.
@@ -754,7 +766,7 @@
<div class="settings-section">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
<h2 style="margin:0;">Memories</h2>
<h2 style="margin:0;">Memories <button class="info-btn" onclick="showInfo('memories')" title="Hot vs. Cold — wie funktioniert das Gedaechtnis?"></button></h2>
<div>
<button class="btn secondary" onclick="resetBrainFilters();loadBrainMemoryList()" style="padding:4px 10px;font-size:11px;">Aktualisieren</button>
<button class="btn" onclick="openMemoryModal()" style="padding:4px 10px;font-size:11px;">+ Neu</button>
@@ -845,6 +857,17 @@
</div>
</div><!-- /tab-skills -->
<!-- Generisches Info-Modal — wird via openInfoModal(title, html) gefuellt -->
<div class="modal-overlay" id="info-modal">
<div class="modal-box" style="max-width:640px;">
<div class="modal-header">
<h3 id="info-modal-title">Info</h3>
<button class="modal-close" onclick="closeInfoModal()">&times;</button>
</div>
<div class="modal-body" id="info-modal-body" style="padding:16px;font-size:13px;color:#E0E0F0;line-height:1.6;"></div>
</div>
</div>
<!-- Memory-Modal (Neu + Editieren) -->
<div class="modal-overlay" id="memory-modal">
<div class="modal-box" style="max-width:640px;">
@@ -993,7 +1016,16 @@
ws.onopen = () => {
addLog('info', 'browser', 'Verbunden mit Diagnostic Server');
send({ action: 'load_chat_history' });
// Brain-Card initial laden (sonst zeigt sie "Lade...")
try { loadBrainStatus(); } catch {}
};
// Brain-Status periodisch refreshen damit die Card live bleibt
if (!window.__brainStatusInterval) {
window.__brainStatusInterval = setInterval(() => {
try { loadBrainStatus(); } catch {}
}, 15000);
}
ws.onclose = () => {
addLog('warn', 'browser', 'Verbindung zum Diagnostic Server verloren — Reconnect in 2s');
setTimeout(connectWS, 2000);
@@ -1230,12 +1262,7 @@
el.textContent = msg.error ? `Fehler: ${msg.error}` : msg.info;
return;
}
if (msg.type === 'core_auth') {
const el = document.getElementById('core-auth');
el.style.display = 'block';
el.textContent = msg.error ? `Fehler: ${msg.error}` : msg.info;
return;
}
// core_auth WS-Event entfernt — aria-core ist raus.
// Live SSH + Desktop
if (msg.type?.startsWith('live_ssh_')) { handleLiveSSH(msg); return; }
if (msg.type === 'desktop_status') { handleDesktop(msg); return; }
@@ -1310,32 +1337,15 @@
return;
}
// Settings (permissions_list/permissions_saved entfernt — Alles-oder-Nichts via --dangerously-skip-permissions)
if (msg.type === 'session_restarted') {
const s = document.getElementById('perms-status');
s.style.display = 'block';
if (msg.status === 'restarting') {
s.style.color = '#FFD60A';
s.textContent = 'aria-core wird neu gestartet...';
} else if (msg.status === 'ok') {
s.style.color = '#34C759';
s.textContent = msg.info || 'Session neu gestartet!';
} else {
s.style.color = '#FF6B6B';
s.textContent = 'Restart fehlgeschlagen: ' + (msg.error || '?');
}
return;
}
// session_restarted / openclaw_config WS-Events entfernt — aria-core ist raus.
if (msg.type === 'model_info') {
const el = document.getElementById('setting-model');
const st = document.getElementById('model-status');
if (msg.model) el.value = msg.model;
st.textContent = msg.info || '';
st.style.color = msg.error ? '#FF6B6B' : '#34C759';
return;
}
if (msg.type === 'openclaw_config') {
document.getElementById('openclaw-config').textContent = msg.config || msg.error || '(leer)';
if (el && msg.model) el.value = msg.model;
if (st) {
st.textContent = msg.info || msg.error || '';
st.style.color = msg.error ? '#FF6B6B' : '#34C759';
}
return;
}
if (msg.type === 'response') { return; }
@@ -1422,15 +1432,12 @@
openTermModal('Claude Login Terminal (aria-proxy)', { action: 'proxy_login' });
}
function openCoreTerminal() {
document.getElementById('btn-core-term').disabled = true;
openTermModal('aria-core Shell', { action: 'core_terminal' });
}
// openCoreTerminal entfernt — aria-core ist raus.
function closeTermModal() {
document.getElementById('term-modal').classList.remove('open');
document.getElementById('btn-proxy-login').disabled = false;
document.getElementById('btn-core-term').disabled = false;
const proxyBtn = document.getElementById('btn-proxy-login');
if (proxyBtn) proxyBtn.disabled = false;
// Terminal aufraeumen
if (term) { term.dispose(); term = null; }
}
@@ -1496,12 +1503,8 @@
}
function updateState(state) {
// Gateway
const gw = state.gateway || {};
document.getElementById('gw-dot').className = `dot ${gw.status || 'disconnected'}`;
document.getElementById('gw-status').textContent =
(STATUS_LABELS[gw.status] || gw.status) + (gw.handshakeOk ? ' (Handshake OK)' : '');
document.getElementById('gw-error').textContent = gw.lastError || '';
// Brain-Card holt ihre Daten via loadBrainStatus() (fetch /api/brain/health).
// state.gateway ist Reststruktur aus OpenClaw-Zeit — wir ignorieren das hier.
// RVS
const rvs = state.rvs || {};
@@ -2280,7 +2283,6 @@
document.getElementById('rc-rvs-tls').value = String(cfg.RVS_TLS) === 'false' ? 'false' : 'true';
document.getElementById('rc-rvs-token').value = cfg.RVS_TOKEN || '';
document.getElementById('rc-auth-token').value = cfg.ARIA_AUTH_TOKEN || '';
document.getElementById('rc-compact-after').value = cfg.compactAfterMessages != null ? cfg.compactAfterMessages : 140;
statusEl.textContent = 'Geladen.';
statusEl.style.color = '#34C759';
loadOnboardingQR(); // QR bei Config-Wechsel neu generieren
@@ -2293,14 +2295,12 @@
async function saveRuntimeConfig() {
const statusEl = document.getElementById('rc-status');
statusEl.textContent = 'Speichere...';
const compactRaw = document.getElementById('rc-compact-after').value.trim();
const patch = {
RVS_HOST: document.getElementById('rc-rvs-host').value.trim(),
RVS_PORT: document.getElementById('rc-rvs-port').value.trim(),
RVS_TLS: document.getElementById('rc-rvs-tls').value,
RVS_TOKEN: document.getElementById('rc-rvs-token').value.trim(),
ARIA_AUTH_TOKEN: document.getElementById('rc-auth-token').value.trim(),
compactAfterMessages: compactRaw === '' ? 140 : Math.max(0, parseInt(compactRaw, 10) || 0),
};
try {
const resp = await fetch('/api/runtime-config', {
@@ -2895,14 +2895,15 @@
// Conversation-Stats (separater Endpoint)
const conv = document.getElementById('conversation-status');
if (!conv) return;
const infoBtn = `<button class="info-btn-small" onclick="showInfo('conversation')" title="Konversation — wie funktioniert das?"></button>`;
try {
const r2 = await fetch('/api/brain/conversation/stats');
if (!r2.ok) throw new Error('HTTP ' + r2.status);
const d2 = await r2.json();
const distillIcon = d2.needs_distill ? ' ⚠ Destillat bald fällig' : '';
conv.innerHTML = `Konversation: <strong>${d2.turns}</strong> Turns · Window: ${d2.max_window} · Schwelle: ${d2.distill_threshold}${distillIcon}`;
conv.innerHTML = `Konversation: <strong>${d2.turns}</strong> Turns · Window: ${d2.max_window} · Schwelle: ${d2.distill_threshold}${distillIcon} ${infoBtn}`;
} catch (e) {
conv.innerHTML = `Konversation: <span style="color:#555570;">${e.message}</span>`;
conv.innerHTML = `Konversation: <span style="color:#555570;">${e.message}</span> ${infoBtn}`;
}
}
@@ -2920,14 +2921,23 @@
}
async function resetConversation() {
if (!confirm('Konversation leeren?\n\nDer Rolling-Window-Verlauf wird komplett verworfen. Destillierte Facts bleiben in der DB.')) return;
if (!confirm('Konversation komplett zurücksetzen?\n\n• ARIAs Rolling-Window (Brain) wird geleert — sie "vergisst" die letzten Turns\n• Chat-Anzeige in der Diagnostic wird geleert\n\nDestillierte Facts + andere Memories bleiben in der Vector-DB.')) return;
try {
const r = await fetch('/api/brain/conversation/reset', { method: 'POST' });
const d = await r.json();
if (d.ok) {
// Beides parallel — Brain Window + Diagnostic chat_backup
const [brainR, histR] = await Promise.all([
fetch('/api/brain/conversation/reset', { method: 'POST' }),
fetch('/api/chat-history-clear', { method: 'POST' }),
]);
const brainOk = brainR.ok;
const histOk = histR.ok;
if (brainOk && histOk) {
// Chat-View leeren (Server broadcasted das eh, aber sicherheitshalber)
if (chatBox) chatBox.innerHTML = '';
const fsBox = document.getElementById('chat-box-fs');
if (fsBox) fsBox.innerHTML = '';
loadBrainStatus();
} else {
alert('Reset fehlgeschlagen');
alert(`Reset teilweise fehlgeschlagen — Brain: ${brainOk ? 'OK' : 'fail'}, History: ${histOk ? 'OK' : 'fail'}`);
}
} catch (e) {
alert('Reset fehlgeschlagen: ' + e.message);
@@ -3161,6 +3171,78 @@
return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
// ── Generisches Info-Modal — Aufruf: openInfoModal('Titel', '<p>HTML...</p>') ──
function openInfoModal(title, html) {
const t = document.getElementById('info-modal-title');
const b = document.getElementById('info-modal-body');
const m = document.getElementById('info-modal');
if (!t || !b || !m) return;
t.textContent = title;
b.innerHTML = html;
m.classList.add('open');
}
function closeInfoModal() {
const m = document.getElementById('info-modal');
if (m) m.classList.remove('open');
}
// Vor-definierte Info-Blocks
const INFO_TEXTS = {
'brain-status': {
title: 'Gehirn — Status',
html: `
<p><strong>online / offline</strong> — ob der <code>aria-brain</code> Container erreichbar ist (HTTP GET /health).</p>
<p><strong>N Memories</strong> — Anzahl der Punkte in der Vector-DB. Beinhaltet alle Typen: identity, rule, preference, tool, skill, fact, conversation, reminder.</p>
<p><strong>Qdrant: aria-qdrant:6333</strong> — Hostname + Port des Vector-DB-Containers. Der Brain spricht intern dorthin.</p>
`,
},
'conversation': {
title: 'Konversation — wie funktioniert das?',
html: `
<p><strong>Rolling Window:</strong> ARIA "sieht" pro Anfrage nur die letzten N Turns einer einzelnen, durchgehenden Konversation. Kein Sessions, kein Multi-Thread.</p>
<ul>
<li><strong>Turns</strong> — Anzahl aller Nachrichten (User + ARIA) seit dem letzten Destillat oder Reset.</li>
<li><strong>Window: 50</strong> — die letzten 50 Turns wandern in den Prompt. Aelteste fallen raus, sobald die Schwelle ueberschritten ist.</li>
<li><strong>Schwelle: 60</strong> — bei mehr als 60 Turns triggert Brain automatisch das Destillat (die 30 aeltesten werden zu fact-Memories verdichtet, Token-Budget bleibt konstant).</li>
</ul>
<p><strong>⚗ Jetzt destillieren:</strong> manueller Trigger fuer das Destillat (kostet einen Claude-Call). Verdichtet die aeltesten 30 Turns zu Fakten + entfernt sie aus dem Window.</p>
<p><strong>🧹 Konversation komplett zuruecksetzen:</strong> leert beides — ARIAs Rolling-Window (Brain) UND die Chat-Anzeige hier (chat_backup.jsonl). Destillierte Facts + alle anderen Memories in der Vector-DB <em>bleiben</em>.</p>
<p style="margin-top:8px;color:#FFD60A;font-size:12px;">⚠ Falls "Turns: 0" obwohl du oben Chat-Eintraege siehst: chat_backup.jsonl (Anzeige) und conversation.jsonl (Brain-Kontext) sind getrennte Stores. Alte chat_backup-Eintraege koennen aus OpenClaw-Zeit stammen. Reset-Button leert beides.</p>
`,
},
'memories': {
title: 'Memories — Hot vs. Cold',
html: `
<p><strong>Pinned (Hot Memory)</strong> 📌 — landet bei JEDER Anfrage im System-Prompt. Hier gehoeren rein: Identitaet, Sicherheitsregeln, Benutzer-Praeferenzen, Tool-Freigaben, Kern-Skills.</p>
<p><strong>Cold Memory</strong> — semantisch durchsucht. Pro Anfrage werden die 5 aehnlichsten Punkte zur User-Frage in den Prompt eingehaengt.</p>
<p><strong>Typen:</strong></p>
<ul>
<li><strong>identity</strong> — wer ARIA ist (Name, Persoenlichkeit)</li>
<li><strong>rule</strong> — Sicherheits-/Werte-Regeln</li>
<li><strong>preference</strong> — User-Profile</li>
<li><strong>tool</strong> — Tool-Freigaben + Infrastruktur</li>
<li><strong>skill</strong> — Faehigkeiten (verlinkt mit /data/skills/)</li>
<li><strong>fact</strong> — Wissens-Fakten (oft aus Destillaten)</li>
<li><strong>conversation</strong> — destillierte Konversations-Erkenntnisse</li>
<li><strong>reminder</strong> — Termine, Aufgaben</li>
</ul>
<p><strong>Such-Feld:</strong> semantische Suche via Embedder + Qdrant. Findet sinngemaess, nicht nur Stichworte.</p>
`,
},
'bootstrap': {
title: 'Bootstrap & Migration — die drei Wege',
html: `
<p><strong>1. Aus brain-import/ migrieren</strong> 🔵 — Parser fuer die <code>.md</code>-Dateien im Repo (AGENT.md, USER.md, TOOLING.md). Schreibt sie als atomare pinned Memories. Idempotent — Re-Run ersetzt nur die Migration-Punkte, eigene Memories bleiben.</p>
<p><strong>2. Bootstrap-Snapshot</strong> 🟡 — kleines JSON, NUR die pinned Memories. Export = aktueller Stand als Datei. Import = ALLE aktuell pinned werden ersetzt. Cold Memory bleibt unangetastet.</p>
<p><strong>3. Komplettes Gehirn</strong> 🔴 — tar.gz mit allem (Memories + Skills + Qdrant-DB). Backup + Restore. Import ueberschreibt ALLES.</p>
`,
},
};
function showInfo(key) {
const cfg = INFO_TEXTS[key];
if (cfg) openInfoModal(cfg.title, cfg.html);
}
function brainExport() {
// Browser folgt der Download-Header-Antwort automatisch
window.location.href = '/api/brain-export';
+17
View File
@@ -1448,6 +1448,23 @@ const server = http.createServer((req, res) => {
}
});
return;
} else if (req.url === "/api/chat-history-clear" && req.method === "POST") {
// Leert die Diagnostic-Anzeige-History (chat_backup.jsonl).
// Brain's Rolling-Window (conversation.jsonl) ist davon unabhaengig —
// der Caller sollte zusaetzlich /api/brain/conversation/reset triggern.
log("warn", "server", "HTTP /api/chat-history-clear");
try {
const file = "/shared/config/chat_backup.jsonl";
if (fs.existsSync(file)) fs.unlinkSync(file);
// Broadcast: alle Browser-Clients sollen ihre lokale Chat-View leeren
broadcast({ type: "chat_history", messages: [] });
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
} catch (err) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: false, error: err.message }));
}
return;
} else if (req.url === "/api/wipe-all" && req.method === "POST") {
// Komplett-Reset — Gedaechtnis, Stimmen, Config alle weg. SSH-Keys
// und .env bleiben, RVS-Anbindung bleibt. Brain + Qdrant werden