diff --git a/diagnostic/index.html b/diagnostic/index.html
index 06518b1..b161fbb 100644
--- a/diagnostic/index.html
+++ b/diagnostic/index.html
@@ -863,17 +863,19 @@
let html = '
';
html += ''
+ '| Session | '
- + 'Zeilen | '
- + 'Groesse | '
+ + 'Msgs | '
+ 'Zuletzt | '
+ ' |
';
for (const s of data.sessions) {
- const date = s.modified ? new Date(s.modified * 1000).toLocaleString('de-DE') : '?';
+ const date = s.modified ? new Date(s.modified * 1000).toLocaleString('de-DE', {day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}) : '?';
const key = escapeHtml(s.sessionKey || s.path.split('/').pop());
+ const orphanBadge = s.orphan ? ' verwaist' : '';
+ const modelBadge = s.model ? `${escapeHtml(s.model)}
` : '';
+ const keyColor = s.orphan ? '#555570' : '#E0E0F0';
html += ``
- + `| ${key} | `
+ + ``
+ + ` ${key}${orphanBadge} ${modelBadge} | `
+ `${s.lines} | `
- + `${escapeHtml(s.size)} | `
+ `${date} | `
+ ` | `
+ '
';
diff --git a/diagnostic/server.js b/diagnostic/server.js
index 2aaf689..1eadca4 100644
--- a/diagnostic/server.js
+++ b/diagnostic/server.js
@@ -1106,60 +1106,94 @@ function checkDesktopAvailable(clientWs) {
// ── Session Viewer ──────────────────────────────────────
+const SESSIONS_DIR = "/home/node/.openclaw/agents/main/sessions";
+
async function handleListSessions(clientWs) {
try {
log("info", "server", "Lade Sessions aus aria-core...");
- // OpenClaw/Claude Code speichert Sessions in projects//sessions/
+
+ // sessions.json als Index lesen + Datei-Details holen
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
+ cat ${SESSIONS_DIR}/sessions.json 2>/dev/null || echo '{}' &&
+ echo '===FILE_DETAILS===' &&
+ for f in ${SESSIONS_DIR}/*.jsonl; do
+ [ -f "$f" ] || continue
+ name=$(basename "$f")
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"
+ echo "FILE:$name|LINES:$lines|SIZE:$size|MODIFIED:$modified"
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;
+ const parts = raw.split("===FILE_DETAILS===");
+ let sessionsIndex = {};
+ try { sessionsIndex = JSON.parse(parts[0].trim()); } catch {}
- // 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", "");
+ // Datei-Details parsen
+ const fileDetails = {};
+ if (parts[1]) {
+ for (const line of parts[1].trim().split("\n")) {
+ if (!line.startsWith("FILE:")) continue;
+ const segs = {};
+ for (const seg of line.split("|")) {
+ const idx = seg.indexOf(":");
+ if (idx > 0) segs[seg.slice(0, idx)] = seg.slice(idx + 1);
+ }
+ if (segs.FILE) fileDetails[segs.FILE] = segs;
}
+ }
+
+ // Sessions zusammenbauen: Index + Datei-Info kombinieren
+ const sessions = [];
+
+ // Aus sessions.json die Session-Keys und IDs
+ const indexEntries = Array.isArray(sessionsIndex) ? sessionsIndex
+ : Array.isArray(sessionsIndex.sessions) ? sessionsIndex.sessions
+ : Object.entries(sessionsIndex).map(([k, v]) => ({ key: k, ...(typeof v === "object" ? v : { id: v }) }));
+
+ for (const entry of indexEntries) {
+ const id = entry.id || entry.sessionId || "";
+ const rawKey = entry.key || entry.sessionKey || entry.name || id;
+ // "agent:main:aria-diagnostic" → "aria-diagnostic"
+ const key = rawKey.replace(/^agent:main:/, "");
+ const filename = `${id}.jsonl`;
+ const details = fileDetails[filename] || {};
+ // updatedAt aus sessions.json (ms) ist genauer als stat
+ const updatedAt = entry.updatedAt || 0;
+ const model = entry.model || "";
sessions.push({
- path: parts.FILE,
- sessionKey,
- lines: parseInt(parts.LINES) || 0,
- size: parts.SIZE || "?",
- modified: parseInt(parts.MODIFIED) || 0,
+ path: `${SESSIONS_DIR}/${filename}`,
+ sessionKey: key,
+ sessionId: id,
+ lines: parseInt(details.LINES) || 0,
+ size: details.SIZE || "?",
+ modified: updatedAt ? Math.floor(updatedAt / 1000) : (parseInt(details.MODIFIED) || 0),
+ model,
+ });
+
+ // Aus fileDetails entfernen (fuer Waisen-Check)
+ delete fileDetails[filename];
+ }
+
+ // Dateien die nicht im Index stehen (Waisen / Reset-Files)
+ for (const [filename, details] of Object.entries(fileDetails)) {
+ const id = filename.replace(".jsonl", "");
+ sessions.push({
+ path: `${SESSIONS_DIR}/${filename}`,
+ sessionKey: id.slice(0, 12) + "...",
+ sessionId: id,
+ lines: parseInt(details.LINES) || 0,
+ size: details.SIZE || "?",
+ modified: parseInt(details.MODIFIED) || 0,
+ orphan: true,
});
}
- // 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 }));
+ clientWs.send(JSON.stringify({ type: "sessions_list", sessions }));
log("info", "server", `${sessions.length} Session(s) gefunden`);
} catch (err) {
log("error", "server", `Sessions laden fehlgeschlagen: ${err.message}`);
@@ -1190,7 +1224,7 @@ async function handleReadSession(clientWs, sessionPath) {
}
async function handleDeleteSession(clientWs, sessionPath) {
- if (!sessionPath || sessionPath.includes("..") || !sessionPath.startsWith("/home/node/.openclaw/")) {
+ if (!sessionPath || sessionPath.includes("..") || !sessionPath.startsWith(SESSIONS_DIR)) {
clientWs.send(JSON.stringify({ type: "session_deleted", ok: false, error: "Ungueltiger Pfad" }));
return;
}