added brain and session viewer
This commit is contained in:
parent
6a04d861bd
commit
706005d7f5
|
|
@ -61,6 +61,41 @@ ARIA ist Stefan gegenueber wie Claude gegenueber Stefan: direkt, ehrlich, auf Au
|
|||
| **Ramona** (weiblich) | `de_DE-ramona-low` | Alltag, Antworten, Gespraeche (Standard) |
|
||||
| **Thorsten** (maennlich, tief) | `de_DE-thorsten-high` | Epische Momente, Alarme, besondere Ereignisse |
|
||||
|
||||
## Gedaechtnis (Memory)
|
||||
|
||||
ARIA hat ein persistentes Gedaechtnis im Verzeichnis `memory/`. Erinnerungen ueberleben Session-Neustarts und Container-Restarts.
|
||||
|
||||
### Wann speichern?
|
||||
|
||||
- **Stefan sagt "merk dir das"** — sofort speichern
|
||||
- **Neue Info ueber Stefan** — Rolle, Vorlieben, Arbeitsweise (Typ: user)
|
||||
- **Korrektur oder Feedback** — "mach das nicht so, sondern so" (Typ: feedback)
|
||||
- **Projekt-Kontext** — Deadlines, wer macht was, warum (Typ: project)
|
||||
- **Externe Referenzen** — wo was zu finden ist (Typ: reference)
|
||||
|
||||
### Wie speichern?
|
||||
|
||||
Erstelle eine Datei in `memory/` mit Frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Kurzer Name
|
||||
description: Einzeiler — woran erkennst du spaeter ob das relevant ist?
|
||||
type: user|feedback|project|reference
|
||||
---
|
||||
|
||||
Inhalt der Erinnerung
|
||||
```
|
||||
|
||||
Danach den Eintrag in `memory/MEMORY.md` (Index) verlinken.
|
||||
|
||||
### Was NICHT speichern?
|
||||
|
||||
- Code-Strukturen (die siehst du im Code)
|
||||
- Git-History (die steht in git log)
|
||||
- Dinge die in dieser Datei schon stehen
|
||||
- Temporaere Sachen die nur in der aktuellen Session relevant sind
|
||||
|
||||
## Infrastruktur
|
||||
|
||||
### Container (aria-core) — Dein Gehirn
|
||||
|
|
|
|||
|
|
@ -160,6 +160,43 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Session + Brain Viewer -->
|
||||
<div class="grid" style="grid-template-columns: 1fr 1fr;">
|
||||
<div class="card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
|
||||
<h2>Sessions</h2>
|
||||
<button class="btn secondary" onclick="loadSessions()" style="padding:4px 10px;font-size:11px;">Laden</button>
|
||||
</div>
|
||||
<div id="sessions-list" style="max-height:300px;overflow-y:auto;font-size:12px;"></div>
|
||||
<div id="session-detail" style="display:none;margin-top:8px;background:#080810;border:1px solid #1E1E2E;border-radius:4px;padding:8px;max-height:300px;overflow-y:auto;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
|
||||
<span style="font-size:11px;color:#0096FF;font-weight:bold;" id="session-detail-title"></span>
|
||||
<button class="btn secondary" onclick="closeSessionDetail()" style="padding:2px 8px;font-size:10px;">Schliessen</button>
|
||||
</div>
|
||||
<div id="session-detail-content" style="font-size:11px;line-height:1.5;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
|
||||
<h2>Brain / Memory</h2>
|
||||
<button class="btn secondary" onclick="loadBrain()" style="padding:4px 10px;font-size:11px;">Laden</button>
|
||||
</div>
|
||||
<div id="brain-list" style="max-height:200px;overflow-y:auto;font-size:12px;"></div>
|
||||
<div id="brain-content" style="display:none;margin-top:8px;background:#080810;border:1px solid #1E1E2E;border-radius:4px;padding:8px;max-height:300px;overflow-y:auto;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
|
||||
<span style="font-size:11px;color:#0096FF;font-weight:bold;" id="brain-content-title"></span>
|
||||
<button class="btn secondary" onclick="closeBrainContent()" style="padding:2px 8px;font-size:10px;">Schliessen</button>
|
||||
</div>
|
||||
<pre id="brain-content-text" style="font-size:11px;line-height:1.5;white-space:pre-wrap;color:#E0E0F0;margin:0;"></pre>
|
||||
</div>
|
||||
<div id="brain-empty" style="display:none;text-align:center;padding:20px;color:#555570;">
|
||||
<div style="font-size:24px;margin-bottom:8px;">🧠</div>
|
||||
<div style="font-size:12px;">Gehirn ist leer</div>
|
||||
<div style="font-size:10px;margin-top:4px;">ARIA speichert Erinnerungen wenn sie etwas Wichtiges lernt</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs mit Tabs -->
|
||||
<div class="card" style="margin-top:12px; padding: 8px 0 0 0;">
|
||||
<div style="padding: 0 12px;">
|
||||
|
|
@ -420,6 +457,16 @@
|
|||
showDockerLogs(msg);
|
||||
return;
|
||||
}
|
||||
// Session + Brain Viewer
|
||||
if (msg.type === 'sessions_list') { renderSessions(msg); return; }
|
||||
if (msg.type === 'session_detail') { renderSessionDetail(msg); return; }
|
||||
if (msg.type === 'session_deleted') {
|
||||
if (msg.ok) loadSessions(); // Liste neu laden
|
||||
else alert('Loeschen fehlgeschlagen: ' + (msg.error || '?'));
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'brain_list') { renderBrainList(msg); return; }
|
||||
if (msg.type === 'brain_content') { renderBrainContent(msg); return; }
|
||||
if (msg.type === 'response') { return; }
|
||||
};
|
||||
}
|
||||
|
|
@ -794,6 +841,171 @@
|
|||
}
|
||||
}
|
||||
|
||||
// ── Session Viewer ────────────────────────────────────────
|
||||
|
||||
function loadSessions() {
|
||||
document.getElementById('sessions-list').innerHTML = '<div style="color:#8888AA;padding:8px;">Lade...</div>';
|
||||
send({ action: 'list_sessions' });
|
||||
}
|
||||
|
||||
function renderSessions(data) {
|
||||
const container = document.getElementById('sessions-list');
|
||||
if (data.error) {
|
||||
container.innerHTML = `<div style="color:#FF6B6B;padding:8px;">Fehler: ${escapeHtml(data.error)}</div>`;
|
||||
return;
|
||||
}
|
||||
if (!data.sessions || data.sessions.length === 0) {
|
||||
container.innerHTML = data.raw
|
||||
? `<pre style="color:#555570;font-size:10px;white-space:pre-wrap;padding:8px;">${escapeHtml(data.raw)}</pre>`
|
||||
: '<div style="color:#555570;padding:8px;text-align:center;">Keine Sessions gefunden</div>';
|
||||
return;
|
||||
}
|
||||
let html = '<table style="width:100%;border-collapse:collapse;">';
|
||||
html += '<tr style="color:#8888AA;font-size:10px;text-align:left;border-bottom:1px solid #1E1E2E;">'
|
||||
+ '<th style="padding:4px 6px;">Session</th>'
|
||||
+ '<th style="padding:4px 6px;">Zeilen</th>'
|
||||
+ '<th style="padding:4px 6px;">Groesse</th>'
|
||||
+ '<th style="padding:4px 6px;">Zuletzt</th>'
|
||||
+ '<th style="padding:4px 6px;"></th></tr>';
|
||||
for (const s of data.sessions) {
|
||||
const date = s.modified ? new Date(s.modified * 1000).toLocaleString('de-DE') : '?';
|
||||
const key = escapeHtml(s.sessionKey || s.path.split('/').pop());
|
||||
html += `<tr style="border-bottom:1px solid #0D0D1A;cursor:pointer;" onmouseover="this.style.background='#1E1E2E'" onmouseout="this.style.background=''">`
|
||||
+ `<td style="padding:4px 6px;color:#E0E0F0;" onclick="viewSession('${escapeHtml(s.path)}')">${key}</td>`
|
||||
+ `<td style="padding:4px 6px;color:#8888AA;">${s.lines}</td>`
|
||||
+ `<td style="padding:4px 6px;color:#8888AA;">${escapeHtml(s.size)}</td>`
|
||||
+ `<td style="padding:4px 6px;color:#8888AA;font-size:10px;">${date}</td>`
|
||||
+ `<td style="padding:4px 6px;"><button class="btn secondary" onclick="event.stopPropagation();deleteSession('${escapeHtml(s.path)}')" style="padding:2px 6px;font-size:10px;color:#FF6B6B;">X</button></td>`
|
||||
+ '</tr>';
|
||||
}
|
||||
html += '</table>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function viewSession(path) {
|
||||
const detail = document.getElementById('session-detail');
|
||||
const title = document.getElementById('session-detail-title');
|
||||
const content = document.getElementById('session-detail-content');
|
||||
detail.style.display = 'block';
|
||||
title.textContent = path.split('/').pop();
|
||||
content.innerHTML = '<div style="color:#8888AA;">Lade...</div>';
|
||||
send({ action: 'read_session', sessionPath: path });
|
||||
}
|
||||
|
||||
function renderSessionDetail(data) {
|
||||
const content = document.getElementById('session-detail-content');
|
||||
if (data.error) {
|
||||
content.innerHTML = `<div style="color:#FF6B6B;">${escapeHtml(data.error)}</div>`;
|
||||
return;
|
||||
}
|
||||
if (data.raw) {
|
||||
content.innerHTML = `<pre style="color:#555570;font-size:10px;white-space:pre-wrap;">${escapeHtml(data.raw)}</pre>`;
|
||||
return;
|
||||
}
|
||||
if (!data.messages || data.messages.length === 0) {
|
||||
content.innerHTML = '<div style="color:#555570;">Keine Nachrichten</div>';
|
||||
return;
|
||||
}
|
||||
let html = '';
|
||||
for (const msg of data.messages) {
|
||||
const role = msg.role || msg.type || '?';
|
||||
const text = extractMessageText(msg);
|
||||
const roleColor = role === 'user' ? '#0096FF' : role === 'assistant' ? '#34C759' : '#8888AA';
|
||||
html += `<div style="margin-bottom:4px;padding:4px 6px;border-left:2px solid ${roleColor};background:#0D0D1A;border-radius:0 4px 4px 0;">`
|
||||
+ `<span style="color:${roleColor};font-size:10px;font-weight:bold;">${escapeHtml(role)}</span> `
|
||||
+ `<span style="color:#E0E0F0;">${escapeHtml(text.slice(0, 500))}${text.length > 500 ? '...' : ''}</span>`
|
||||
+ '</div>';
|
||||
}
|
||||
content.innerHTML = html;
|
||||
}
|
||||
|
||||
function extractMessageText(msg) {
|
||||
if (typeof msg.content === 'string') return msg.content;
|
||||
if (Array.isArray(msg.content)) {
|
||||
return msg.content.filter(c => c.type === 'text').map(c => c.text || '').join('');
|
||||
}
|
||||
if (msg.text) return msg.text;
|
||||
if (msg.message) return typeof msg.message === 'string' ? msg.message : JSON.stringify(msg.message);
|
||||
return JSON.stringify(msg).slice(0, 200);
|
||||
}
|
||||
|
||||
function closeSessionDetail() {
|
||||
document.getElementById('session-detail').style.display = 'none';
|
||||
}
|
||||
|
||||
function deleteSession(path) {
|
||||
const name = path.split('/').pop();
|
||||
if (!confirm(`Session "${name}" wirklich loeschen?`)) return;
|
||||
send({ action: 'delete_session', sessionPath: path });
|
||||
}
|
||||
|
||||
// ── Brain Viewer ────────────────────────────────────────
|
||||
|
||||
function loadBrain() {
|
||||
document.getElementById('brain-list').innerHTML = '<div style="color:#8888AA;padding:8px;">Lade...</div>';
|
||||
document.getElementById('brain-empty').style.display = 'none';
|
||||
send({ action: 'list_brain' });
|
||||
}
|
||||
|
||||
function renderBrainList(data) {
|
||||
const container = document.getElementById('brain-list');
|
||||
const emptyEl = document.getElementById('brain-empty');
|
||||
|
||||
if (data.error) {
|
||||
container.innerHTML = `<div style="color:#FF6B6B;padding:8px;">Fehler: ${escapeHtml(data.error)}</div>`;
|
||||
emptyEl.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
if (!data.files || data.files.length === 0) {
|
||||
container.innerHTML = '';
|
||||
emptyEl.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
emptyEl.style.display = 'none';
|
||||
|
||||
const TYPE_COLORS = { user: '#0096FF', feedback: '#FFD60A', project: '#34C759', reference: '#FF9500' };
|
||||
let html = '';
|
||||
for (const f of data.files) {
|
||||
if (f.name === '.gitkeep') continue;
|
||||
const color = TYPE_COLORS[f.memType] || '#8888AA';
|
||||
const date = f.modified ? new Date(f.modified * 1000).toLocaleString('de-DE') : '?';
|
||||
html += `<div style="padding:6px 8px;border-bottom:1px solid #0D0D1A;cursor:pointer;display:flex;align-items:center;gap:8px;" onmouseover="this.style.background='#1E1E2E'" onmouseout="this.style.background=''" onclick="viewBrainFile('${escapeHtml(f.name)}')">`
|
||||
+ `<span style="background:${color};width:8px;height:8px;border-radius:50%;flex-shrink:0;" title="${escapeHtml(f.memType || 'unbekannt')}"></span>`
|
||||
+ `<div style="flex:1;min-width:0;">`
|
||||
+ `<div style="color:#E0E0F0;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(f.name)}</div>`
|
||||
+ (f.description ? `<div style="color:#8888AA;font-size:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(f.description)}</div>` : '')
|
||||
+ `</div>`
|
||||
+ `<div style="color:#555570;font-size:10px;white-space:nowrap;">${escapeHtml(f.size)}</div>`
|
||||
+ '</div>';
|
||||
}
|
||||
container.innerHTML = html || '<div style="color:#555570;padding:8px;text-align:center;">Nur .gitkeep gefunden</div>';
|
||||
}
|
||||
|
||||
function viewBrainFile(name) {
|
||||
const panel = document.getElementById('brain-content');
|
||||
const title = document.getElementById('brain-content-title');
|
||||
const text = document.getElementById('brain-content-text');
|
||||
panel.style.display = 'block';
|
||||
title.textContent = name;
|
||||
text.textContent = 'Lade...';
|
||||
send({ action: 'read_brain_file', filename: name });
|
||||
}
|
||||
|
||||
function renderBrainContent(data) {
|
||||
const text = document.getElementById('brain-content-text');
|
||||
if (data.error) {
|
||||
text.textContent = `Fehler: ${data.error}`;
|
||||
text.style.color = '#FF6B6B';
|
||||
return;
|
||||
}
|
||||
text.style.color = '#E0E0F0';
|
||||
text.textContent = data.content || '(leer)';
|
||||
}
|
||||
|
||||
function closeBrainContent() {
|
||||
document.getElementById('brain-content').style.display = 'none';
|
||||
}
|
||||
|
||||
connectWS();
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -970,6 +970,16 @@ wss.on("connection", (ws) => {
|
|||
if (ws._sshSock) { ws._sshSock.end(); ws._sshSock = null; }
|
||||
} else if (msg.action === "check_desktop") {
|
||||
checkDesktopAvailable(ws);
|
||||
} else if (msg.action === "list_sessions") {
|
||||
handleListSessions(ws);
|
||||
} else if (msg.action === "read_session") {
|
||||
handleReadSession(ws, msg.sessionPath);
|
||||
} else if (msg.action === "delete_session") {
|
||||
handleDeleteSession(ws, msg.sessionPath);
|
||||
} else if (msg.action === "list_brain") {
|
||||
handleListBrain(ws);
|
||||
} else if (msg.action === "read_brain_file") {
|
||||
handleReadBrainFile(ws, msg.filename);
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
|
|
@ -1094,6 +1104,187 @@ function checkDesktopAvailable(clientWs) {
|
|||
});
|
||||
}
|
||||
|
||||
// ── Session Viewer ──────────────────────────────────────
|
||||
|
||||
async function handleListSessions(clientWs) {
|
||||
try {
|
||||
log("info", "server", "Lade Sessions aus aria-core...");
|
||||
// OpenClaw/Claude Code speichert Sessions in projects/<hash>/sessions/
|
||||
const raw = await dockerExec("aria-core", `
|
||||
echo '=== SESSIONS ===' &&
|
||||
find /home/node/.openclaw/projects -name '*.jsonl' -type f 2>/dev/null | sort &&
|
||||
echo '=== DETAILS ===' &&
|
||||
for f in $(find /home/node/.openclaw/projects -name '*.jsonl' -type f 2>/dev/null | sort); do
|
||||
lines=$(wc -l < "$f" 2>/dev/null || echo 0)
|
||||
size=$(du -h "$f" 2>/dev/null | cut -f1)
|
||||
modified=$(stat -c '%Y' "$f" 2>/dev/null || echo 0)
|
||||
# Erste Zeile fuer Session-Info
|
||||
first=$(head -1 "$f" 2>/dev/null | head -c 500)
|
||||
echo "FILE:$f|LINES:$lines|SIZE:$size|MODIFIED:$modified|FIRST:$first"
|
||||
done
|
||||
`.trim());
|
||||
|
||||
// Parse Output
|
||||
const sessions = [];
|
||||
const lines = raw.split("\n");
|
||||
for (const line of lines) {
|
||||
if (!line.startsWith("FILE:")) continue;
|
||||
const parts = {};
|
||||
for (const seg of line.split("|")) {
|
||||
const idx = seg.indexOf(":");
|
||||
if (idx > 0) parts[seg.slice(0, idx)] = seg.slice(idx + 1);
|
||||
}
|
||||
if (!parts.FILE) continue;
|
||||
|
||||
// Session-Key aus Pfad oder erster Zeile extrahieren
|
||||
let sessionKey = "";
|
||||
try {
|
||||
const firstObj = JSON.parse(parts.FIRST || "{}");
|
||||
sessionKey = firstObj.sessionKey || firstObj.key || "";
|
||||
} catch {}
|
||||
if (!sessionKey) {
|
||||
// Dateiname als Fallback
|
||||
sessionKey = parts.FILE.split("/").pop().replace(".jsonl", "");
|
||||
}
|
||||
|
||||
sessions.push({
|
||||
path: parts.FILE,
|
||||
sessionKey,
|
||||
lines: parseInt(parts.LINES) || 0,
|
||||
size: parts.SIZE || "?",
|
||||
modified: parseInt(parts.MODIFIED) || 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Nach Aenderungsdatum sortieren (neueste zuerst)
|
||||
sessions.sort((a, b) => b.modified - a.modified);
|
||||
|
||||
clientWs.send(JSON.stringify({ type: "sessions_list", sessions, raw: sessions.length === 0 ? raw : undefined }));
|
||||
log("info", "server", `${sessions.length} Session(s) gefunden`);
|
||||
} catch (err) {
|
||||
log("error", "server", `Sessions laden fehlgeschlagen: ${err.message}`);
|
||||
clientWs.send(JSON.stringify({ type: "sessions_list", sessions: [], error: err.message }));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReadSession(clientWs, sessionPath) {
|
||||
if (!sessionPath || sessionPath.includes("..")) {
|
||||
clientWs.send(JSON.stringify({ type: "session_detail", error: "Ungueltiger Pfad" }));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Letzte 100 Zeilen der Session (JSONL)
|
||||
const raw = await dockerExec("aria-core", `tail -100 '${sessionPath.replace(/'/g, "")}'`);
|
||||
const messages = [];
|
||||
for (const line of raw.split("\n")) {
|
||||
if (!line.trim()) continue;
|
||||
try {
|
||||
const obj = JSON.parse(line);
|
||||
messages.push(obj);
|
||||
} catch {}
|
||||
}
|
||||
clientWs.send(JSON.stringify({ type: "session_detail", path: sessionPath, messages, raw: messages.length === 0 ? raw : undefined }));
|
||||
} catch (err) {
|
||||
clientWs.send(JSON.stringify({ type: "session_detail", error: err.message }));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteSession(clientWs, sessionPath) {
|
||||
if (!sessionPath || sessionPath.includes("..") || !sessionPath.startsWith("/home/node/.openclaw/")) {
|
||||
clientWs.send(JSON.stringify({ type: "session_deleted", ok: false, error: "Ungueltiger Pfad" }));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log("warn", "server", `Loesche Session: ${sessionPath}`);
|
||||
await dockerExec("aria-core", `rm '${sessionPath.replace(/'/g, "")}'`);
|
||||
clientWs.send(JSON.stringify({ type: "session_deleted", ok: true, path: sessionPath }));
|
||||
log("info", "server", "Session geloescht");
|
||||
} catch (err) {
|
||||
clientWs.send(JSON.stringify({ type: "session_deleted", ok: false, error: err.message }));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Brain Viewer ────────────────────────────────────────
|
||||
|
||||
async function handleListBrain(clientWs) {
|
||||
try {
|
||||
log("info", "server", "Lade Brain-Dateien...");
|
||||
const raw = await dockerExec("aria-core", `
|
||||
for f in /home/node/.openclaw/workspace/memory/*; do
|
||||
[ -f "$f" ] || continue
|
||||
name=$(basename "$f")
|
||||
size=$(du -h "$f" 2>/dev/null | cut -f1)
|
||||
lines=$(wc -l < "$f" 2>/dev/null || echo 0)
|
||||
modified=$(stat -c '%Y' "$f" 2>/dev/null || echo 0)
|
||||
# Frontmatter extrahieren (erste 10 Zeilen)
|
||||
head10=$(head -10 "$f" 2>/dev/null | tr '\\n' '|')
|
||||
echo "FILE:$name|SIZE:$size|LINES:$lines|MODIFIED:$modified|HEAD:$head10"
|
||||
done
|
||||
`.trim());
|
||||
|
||||
const files = [];
|
||||
for (const line of raw.split("\n")) {
|
||||
if (!line.startsWith("FILE:")) continue;
|
||||
const parts = {};
|
||||
for (const seg of line.split("|")) {
|
||||
const idx = seg.indexOf(":");
|
||||
if (idx > 0) {
|
||||
const key = seg.slice(0, idx);
|
||||
const val = seg.slice(idx + 1);
|
||||
// HEAD hat mehrere |, also nur die bekannten Keys parsen
|
||||
if (["FILE", "SIZE", "LINES", "MODIFIED"].includes(key)) {
|
||||
parts[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!parts.FILE || parts.FILE === "*") continue;
|
||||
|
||||
// Frontmatter-Info aus HEAD extrahieren
|
||||
let description = "";
|
||||
let memType = "";
|
||||
const headPart = line.slice(line.indexOf("|HEAD:") + 6);
|
||||
if (headPart) {
|
||||
const headLines = headPart.split("|");
|
||||
for (const hl of headLines) {
|
||||
if (hl.startsWith("description:")) description = hl.replace("description:", "").trim();
|
||||
if (hl.startsWith("type:")) memType = hl.replace("type:", "").trim();
|
||||
}
|
||||
}
|
||||
|
||||
files.push({
|
||||
name: parts.FILE,
|
||||
size: parts.SIZE || "?",
|
||||
lines: parseInt(parts.LINES) || 0,
|
||||
modified: parseInt(parts.MODIFIED) || 0,
|
||||
description,
|
||||
memType,
|
||||
});
|
||||
}
|
||||
|
||||
files.sort((a, b) => b.modified - a.modified);
|
||||
clientWs.send(JSON.stringify({ type: "brain_list", files }));
|
||||
log("info", "server", `${files.length} Brain-Datei(en) gefunden`);
|
||||
} catch (err) {
|
||||
log("error", "server", `Brain laden fehlgeschlagen: ${err.message}`);
|
||||
clientWs.send(JSON.stringify({ type: "brain_list", files: [], error: err.message }));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReadBrainFile(clientWs, filename) {
|
||||
// Path Traversal verhindern
|
||||
if (!filename || filename.includes("..") || filename.includes("/")) {
|
||||
clientWs.send(JSON.stringify({ type: "brain_content", error: "Ungueltiger Dateiname" }));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const content = await dockerExec("aria-core",
|
||||
`cat '/home/node/.openclaw/workspace/memory/${filename.replace(/'/g, "")}'`);
|
||||
clientWs.send(JSON.stringify({ type: "brain_content", filename, content }));
|
||||
} catch (err) {
|
||||
clientWs.send(JSON.stringify({ type: "brain_content", filename, error: err.message }));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Start ───────────────────────────────────────────────
|
||||
|
||||
server.listen(HTTP_PORT, "0.0.0.0", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue