diff --git a/diagnostic/index.html b/diagnostic/index.html
index a67448d..fce8737 100644
--- a/diagnostic/index.html
+++ b/diagnostic/index.html
@@ -357,6 +357,37 @@
+
+
Idle β warte auf ARIA-Aktivitaet
Leeren
+
π Archiv
Auto-Scroll
@@ -3185,6 +3217,93 @@
_ariaMaybeScroll();
} catch (_) {}
}
+
+ // ββ ARIA-Stream Archiv-Modal (Pagination) ββββββββββββββββ
+ let _ariaArchivePage = 1;
+ let _ariaArchivePagesTotal = 1;
+
+ function openAriaStreamModal() {
+ const m = document.getElementById('aria-archive-modal');
+ if (!m) return;
+ m.style.display = 'flex';
+ loadAriaArchivePage(1);
+ }
+
+ function closeAriaStreamModal() {
+ const m = document.getElementById('aria-archive-modal');
+ if (m) m.style.display = 'none';
+ }
+
+ async function loadAriaArchivePage(page) {
+ const listEl = document.getElementById('aria-archive-list');
+ const infoEl = document.getElementById('aria-arch-pageinfo');
+ const totalEl = document.getElementById('aria-archive-total');
+ if (!listEl) return;
+ const perPage = parseInt(document.getElementById('aria-archive-perpage').value, 10) || 100;
+ page = Math.max(1, page || 1);
+ listEl.innerHTML = '
Lade...
';
+ try {
+ const r = await fetch(`/api/agent-stream?page=${page}&perPage=${perPage}`);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ const d = await r.json();
+ const events = d.lines || [];
+ _ariaArchivePage = d.page || page;
+ _ariaArchivePagesTotal = d.pagesTotal || 1;
+ if (totalEl) totalEl.textContent = `(${d.total || 0} Eintraege gesamt)`;
+ if (infoEl) infoEl.textContent = `Seite ${_ariaArchivePage} / ${_ariaArchivePagesTotal}`;
+ // Nav-Buttons enablen/disablen
+ document.getElementById('aria-arch-first').disabled = (_ariaArchivePage <= 1);
+ document.getElementById('aria-arch-prev').disabled = (_ariaArchivePage <= 1);
+ document.getElementById('aria-arch-next').disabled = (_ariaArchivePage >= _ariaArchivePagesTotal);
+ document.getElementById('aria-arch-last').disabled = (_ariaArchivePage >= _ariaArchivePagesTotal);
+
+ if (!events.length) {
+ listEl.innerHTML = '
Keine Eintraege auf dieser Seite.
';
+ return;
+ }
+ // Eintraege rendern β wir teilen sie in HTML-Snippets analog zu
+ // appendAriaStreamEvent, schreiben aber direkt in den Modal-Container.
+ const html = events.map(p => renderArchiveLine(p)).join('');
+ listEl.innerHTML = html;
+ listEl.scrollTop = listEl.scrollHeight;
+ } catch (e) {
+ listEl.innerHTML = `
Fehler beim Laden: ${_ariaEsc(e.message)}
`;
+ }
+ }
+
+ function renderArchiveLine(p) {
+ const t = _ariaTimePrefix(p.ts);
+ const kind = p.kind || '';
+ const time = `
[${t}] `;
+ if (kind === 'start') {
+ return `
βββ ${t} session start (${_ariaEsc(p.model||'unknown')}) βββ
`;
+ }
+ if (kind === 'end') {
+ const reason = p.reason || '?';
+ const codePart = (p.code != null) ? ` code=${_ariaEsc(p.code)}` : '';
+ const errPart = p.error ? ` err=${_ariaEsc(String(p.error).slice(0,120))}` : '';
+ return `
βββ ${t} session end (${_ariaEsc(reason)}${codePart}${errPart}) βββ
`;
+ }
+ if (kind === 'text') {
+ return `
${time} ${_ariaEsc(p.text || '')}
`;
+ }
+ if (kind === 'thinking') {
+ return `
${time} π ${_ariaEsc(p.text || '')}
`;
+ }
+ if (kind === 'tool_use') {
+ const name = _ariaEsc(p.name || '?');
+ const inp = _ariaEsc(p.input || '');
+ const tail = p.inputTruncatedBytes ? `
...(+${p.inputTruncatedBytes} bytes) ` : '';
+ return `
${time} βΆ ${name} ${inp}${tail}
`;
+ }
+ if (kind === 'tool_result') {
+ const isError = p.isError === true;
+ const head = isError ? '
β result (ERROR) ' : '
β result ';
+ const tail = p.truncatedBytes ? `
...(+${p.truncatedBytes} bytes) ` : '';
+ return `
${time} ${head}
${_ariaEsc(p.content || '')}${tail}
`;
+ }
+ return `
${time} ${_ariaEsc(kind)}: ${_ariaEsc(JSON.stringify(p).slice(0, 500))}
`;
+ }
function ariaPanicStop() {
if (!confirm('Wirklich NOT-AUS? Alle aktiven Claude-Subprocesses werden sofort gekillt.')) return;
send({ action: 'aria_panic_stop' });
diff --git a/diagnostic/server.js b/diagnostic/server.js
index bec7bc3..7719b75 100644
--- a/diagnostic/server.js
+++ b/diagnostic/server.js
@@ -1773,22 +1773,42 @@ const server = http.createServer((req, res) => {
return res.end(JSON.stringify({ ok: false, error: e.message }));
}
} else if (req.url.startsWith("/api/agent-stream") && req.method === "GET") {
- // Tail des persistierten agent_stream.jsonl. Browser-Live-View laedt das
- // beim Tab-Oeffnen damit Reload/Standby keine Events mehr wegschmeisst.
+ // Tail / paginierter Slice des persistierten agent_stream.jsonl.
+ // Modi:
+ // ?lines=N β letzte N Zeilen (Live-View Initial-Load)
+ // ?page=P&perPage=M β 1-indexed Pagination (Modal-Browser);
+ // page=1 = neueste Seite, hoehere Pages = aelter
try {
const u = new URL(req.url, "http://localhost");
- const lines = Math.max(1, Math.min(5000, parseInt(u.searchParams.get("lines") || "200", 10) || 200));
+ const linesParam = u.searchParams.get("lines");
+ const pageParam = u.searchParams.get("page");
+ const perPageParam = u.searchParams.get("perPage");
const file = AGENT_STREAM_LOG;
let raw = "";
try { raw = fs.readFileSync(file, "utf-8"); } catch {
res.writeHead(200, { "Content-Type": "application/json" });
- return res.end(JSON.stringify({ ok: true, file, lines: [] }));
+ return res.end(JSON.stringify({ ok: true, file, total: 0, lines: [] }));
}
const all = raw.split("\n").filter(l => l.trim());
- const tail = all.slice(-lines);
- const parsed = tail.map(l => { try { return JSON.parse(l); } catch { return { _raw: l }; } });
+ let slice, page = 1, perPage = 0, pagesTotal = 1;
+ if (pageParam || perPageParam) {
+ perPage = Math.max(10, Math.min(5000, parseInt(perPageParam || "100", 10) || 100));
+ pagesTotal = Math.max(1, Math.ceil(all.length / perPage));
+ page = Math.max(1, Math.min(pagesTotal, parseInt(pageParam || "1", 10) || 1));
+ // page=1 = juengste Seite β vom Ende her slicen
+ const end = all.length - (page - 1) * perPage;
+ const start = Math.max(0, end - perPage);
+ slice = all.slice(start, end);
+ } else {
+ const lines = Math.max(1, Math.min(5000, parseInt(linesParam || "200", 10) || 200));
+ slice = all.slice(-lines);
+ }
+ const parsed = slice.map(l => { try { return JSON.parse(l); } catch { return { _raw: l }; } });
res.writeHead(200, { "Content-Type": "application/json" });
- return res.end(JSON.stringify({ ok: true, file, count: parsed.length, total: all.length, lines: parsed }));
+ return res.end(JSON.stringify({
+ ok: true, file, total: all.length, count: parsed.length,
+ page, perPage, pagesTotal, lines: parsed,
+ }));
} catch (e) {
res.writeHead(500, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ ok: false, error: e.message }));