rename(diagnostic): Pipeline-Tab → Trace (End-to-End-Mitschnitt einer Anfrage)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 19:41:54 +02:00
parent 0d203af8fb
commit b56cef6298
2 changed files with 58 additions and 58 deletions
+16 -16
View File
@@ -57,10 +57,10 @@
.log-entry.warn { color: #FFD60A; } .log-entry.warn { color: #FFD60A; }
.log-entry.info { color: #AAB; } .log-entry.info { color: #AAB; }
.log-entry.debug { color: #555570; } .log-entry.debug { color: #555570; }
.log-entry.pipeline-step { color: #0096FF; border-left: 2px solid #0096FF; padding-left: 6px; margin: 2px 0; } .log-entry.trace-step { color: #0096FF; border-left: 2px solid #0096FF; padding-left: 6px; margin: 2px 0; }
.log-entry.pipeline-ok { color: #34C759; border-left: 2px solid #34C759; padding-left: 6px; margin: 2px 0; } .log-entry.trace-ok { color: #34C759; border-left: 2px solid #34C759; padding-left: 6px; margin: 2px 0; }
.log-entry.pipeline-err { color: #FF3B30; border-left: 2px solid #FF3B30; padding-left: 6px; margin: 2px 0; } .log-entry.trace-err { color: #FF3B30; border-left: 2px solid #FF3B30; padding-left: 6px; margin: 2px 0; }
.log-entry.pipeline-sep { color: #333; margin: 6px 0 2px; } .log-entry.trace-sep { color: #333; margin: 6px 0 2px; }
.chat-box { background: #080810; border: 1px solid #1E1E2E; border-radius: 6px; .chat-box { background: #080810; border: 1px solid #1E1E2E; border-radius: 6px;
min-height: 120px; max-height: 250px; overflow-y: auto; min-height: 120px; max-height: 250px; overflow-y: auto;
@@ -379,7 +379,7 @@
<button class="tab-btn" data-tab="proxy" onclick="switchTab('proxy')">Proxy <span class="tab-count" id="count-proxy">0</span></button> <button class="tab-btn" data-tab="proxy" onclick="switchTab('proxy')">Proxy <span class="tab-count" id="count-proxy">0</span></button>
<button class="tab-btn" data-tab="bridge" onclick="switchTab('bridge')">Bridge <span class="tab-count" id="count-bridge">0</span></button> <button class="tab-btn" data-tab="bridge" onclick="switchTab('bridge')">Bridge <span class="tab-count" id="count-bridge">0</span></button>
<button class="tab-btn" data-tab="server" onclick="switchTab('server')">Server <span class="tab-count" id="count-server">0</span></button> <button class="tab-btn" data-tab="server" onclick="switchTab('server')">Server <span class="tab-count" id="count-server">0</span></button>
<button class="tab-btn" data-tab="pipeline" onclick="switchTab('pipeline')" style="margin-left:auto;border-color:#0096FF44;color:#0096FF">Pipeline <span class="tab-count" id="count-pipeline">0</span></button> <button class="tab-btn" data-tab="trace" onclick="switchTab('trace')" style="margin-left:auto;border-color:#0096FF44;color:#0096FF" title="End-to-End-Mitschnitt einer einzelnen Anfrage mit Zeitstempeln">Trace <span class="tab-count" id="count-trace">0</span></button>
</div> </div>
</div> </div>
<div class="log-panel"> <div class="log-panel">
@@ -398,7 +398,7 @@
<div class="log-box hidden" id="log-proxy"></div> <div class="log-box hidden" id="log-proxy"></div>
<div class="log-box hidden" id="log-bridge"></div> <div class="log-box hidden" id="log-bridge"></div>
<div class="log-box hidden" id="log-server"></div> <div class="log-box hidden" id="log-server"></div>
<div class="log-box hidden" id="log-pipeline"></div> <div class="log-box hidden" id="log-trace"></div>
</div> </div>
</div> </div>
@@ -729,8 +729,8 @@
let ws; let ws;
let activeTab = 'all'; let activeTab = 'all';
const DOCKER_TABS = ['gateway', 'proxy', 'bridge']; const DOCKER_TABS = ['gateway', 'proxy', 'bridge'];
const autoScroll = { all: true, gateway: true, rvs: true, proxy: true, bridge: true, server: true, pipeline: true }; const autoScroll = { all: true, gateway: true, rvs: true, proxy: true, bridge: true, server: true, trace: true };
const logCounts = { all: 0, gateway: 0, rvs: 0, proxy: 0, bridge: 0, server: 0, pipeline: 0 }; const logCounts = { all: 0, gateway: 0, rvs: 0, proxy: 0, bridge: 0, server: 0, trace: 0 };
const logBoxes = { const logBoxes = {
all: document.getElementById('log-all'), all: document.getElementById('log-all'),
@@ -739,7 +739,7 @@
proxy: document.getElementById('log-proxy'), proxy: document.getElementById('log-proxy'),
bridge: document.getElementById('log-bridge'), bridge: document.getElementById('log-bridge'),
server: document.getElementById('log-server'), server: document.getElementById('log-server'),
pipeline: document.getElementById('log-pipeline'), trace: document.getElementById('log-trace'),
tts: document.getElementById('log-tts'), tts: document.getElementById('log-tts'),
}; };
@@ -794,7 +794,7 @@
if (source === 'proxy') return 'proxy'; if (source === 'proxy') return 'proxy';
if (source === 'bridge') return 'bridge'; if (source === 'bridge') return 'bridge';
if (source === 'server' || source === 'browser') return 'server'; if (source === 'server' || source === 'browser') return 'server';
if (source === 'pipeline') return 'pipeline'; if (source === 'trace') return 'trace';
return null; return null;
} }
@@ -1333,12 +1333,12 @@
const time = ts ? new Date(ts).toLocaleTimeString('de-DE') : new Date().toLocaleTimeString('de-DE'); const time = ts ? new Date(ts).toLocaleTimeString('de-DE') : new Date().toLocaleTimeString('de-DE');
const line = `${time} [${source}] ${message}`; const line = `${time} [${source}] ${message}`;
// Pipeline-Eintraege nur in Pipeline-Tab (nicht in Alle) // Trace-Eintraege nur in Trace-Tab (nicht in Alle)
if (source === 'pipeline') { if (source === 'trace') {
const pipeLevel = level === 'error' ? 'pipeline-err' : level === 'info' && message.includes('>>>') ? 'pipeline-ok' : 'pipeline-step'; const pipeLevel = level === 'error' ? 'trace-err' : level === 'info' && message.includes('>>>') ? 'trace-ok' : 'trace-step';
appendToLog('pipeline', pipeLevel, `${time} ${message}`); appendToLog('trace', pipeLevel, `${time} ${message}`);
logCounts.pipeline++; logCounts.trace++;
document.getElementById('count-pipeline').textContent = logCounts.pipeline; document.getElementById('count-trace').textContent = logCounts.trace;
return; return;
} }
+42 -42
View File
@@ -113,9 +113,9 @@ let rvsWs = null;
let reqIdCounter = 0; let reqIdCounter = 0;
const browserClients = new Set(); const browserClients = new Set();
// ── Pipeline Tracking ────────────────────────────────── // ── Trace-Tracking (End-to-End-Mitschnitt einer Anfrage) ──────────────────────────────────
let pipelineActive = false; let traceActive = false;
let pipelineStartTime = 0; let traceStartTime = 0;
// Nach chat:final kommen oft noch Trailing Agent-Events. Waehrend dieses // Nach chat:final kommen oft noch Trailing Agent-Events. Waehrend dieses
// Fensters unterdruecken wir agent_activity-Broadcasts, damit der // Fensters unterdruecken wir agent_activity-Broadcasts, damit der
@@ -124,40 +124,40 @@ let lastChatFinalAt = 0;
const SETTLED_WINDOW_MS = 3000; const SETTLED_WINDOW_MS = 3000;
function plog(message, level) { function plog(message, level) {
const elapsed = pipelineActive ? `+${Date.now() - pipelineStartTime}ms` : ""; const elapsed = traceActive ? `+${Date.now() - traceStartTime}ms` : "";
const entry = { ts: new Date().toISOString(), level: level || "info", source: "pipeline", message: `${elapsed ? `[${elapsed}] ` : ""}${message}` }; const entry = { ts: new Date().toISOString(), level: level || "info", source: "trace", message: `${elapsed ? `[${elapsed}] ` : ""}${message}` };
logs.push(entry); logs.push(entry);
if (logs.length > 500) logs.shift(); if (logs.length > 500) logs.shift();
console.log(`[PIPELINE] ${entry.message}`); console.log(`[TRACE] ${entry.message}`);
broadcast({ type: "log", entry }); broadcast({ type: "log", entry });
} }
let pipelineTimeout = null; let traceTimeout = null;
function pipelineStart(method, text) { function traceStart(method, text) {
// Falls noch eine Pipeline laeuft, beenden // Falls noch ein Trace laeuft, beenden
if (pipelineActive) pipelineEnd(false, "Abgebrochen (neue Nachricht)"); if (traceActive) traceEnd(false, "Abgebrochen (neue Nachricht)");
pipelineActive = true; traceActive = true;
pipelineStartTime = Date.now(); traceStartTime = Date.now();
if (pipelineTimeout) clearTimeout(pipelineTimeout); if (traceTimeout) clearTimeout(traceTimeout);
pipelineTimeout = setTimeout(() => { traceTimeout = setTimeout(() => {
if (pipelineActive) pipelineEnd(false, "Timeout — keine Antwort nach 10min"); if (traceActive) traceEnd(false, "Timeout — keine Antwort nach 10min");
}, 600000); }, 600000);
plog(`━━━ Pipeline Start: ${method} ━━━`); plog(`━━━ Trace Start: ${method} ━━━`);
plog(`Nachricht: "${text}"`); plog(`Nachricht: "${text}"`);
} }
function pipelineEnd(ok, detail) { function traceEnd(ok, detail) {
if (!pipelineActive) return; if (!traceActive) return;
if (pipelineTimeout) { clearTimeout(pipelineTimeout); pipelineTimeout = null; } if (traceTimeout) { clearTimeout(traceTimeout); traceTimeout = null; }
const elapsed = Date.now() - pipelineStartTime; const elapsed = Date.now() - traceStartTime;
if (ok) { if (ok) {
plog(`>>> Fertig (${elapsed}ms): ${detail}`); plog(`>>> Fertig (${elapsed}ms): ${detail}`);
} else { } else {
plog(`>>> FEHLER (${elapsed}ms): ${detail}`, "error"); plog(`>>> FEHLER (${elapsed}ms): ${detail}`, "error");
} }
plog(`━━━ Pipeline Ende ━━━`); plog(`━━━ Trace Ende ━━━`);
pipelineActive = false; traceActive = false;
// Thinking-Indikator IMMER zuruecksetzen — auch bei Timeout/Fehler/Abbruch // Thinking-Indikator IMMER zuruecksetzen — auch bei Timeout/Fehler/Abbruch
broadcast({ type: "agent_activity", activity: "idle" }); broadcast({ type: "agent_activity", activity: "idle" });
pendingMessageTime = 0; pendingMessageTime = 0;
@@ -327,8 +327,8 @@ async function connectGateway() {
state.gateway.handshakeOk = false; state.gateway.handshakeOk = false;
gatewayWs = null; gatewayWs = null;
broadcastState(); broadcastState();
// Stuck "ARIA denkt..." vermeiden, falls Gateway waehrend Pipeline abkackt // Stuck "ARIA denkt..." vermeiden, falls Gateway waehrend Trace abkackt
if (pipelineActive) pipelineEnd(false, `Gateway-Verbindung verloren (${code})`); if (traceActive) traceEnd(false, `Gateway-Verbindung verloren (${code})`);
else broadcast({ type: "agent_activity", activity: "idle" }); else broadcast({ type: "agent_activity", activity: "idle" });
checkGatewayHealth(); checkGatewayHealth();
setTimeout(connectGateway, 5000); setTimeout(connectGateway, 5000);
@@ -375,7 +375,7 @@ function handleGatewayMessage(msg) {
if (msg.type === "res") { if (msg.type === "res") {
const status = msg.ok ? "OK" : `FEHLER: ${JSON.stringify(msg.error).slice(0, 100)}`; const status = msg.ok ? "OK" : `FEHLER: ${JSON.stringify(msg.error).slice(0, 100)}`;
log("info", "gateway", `Response [${msg.id}]: ${status}`); log("info", "gateway", `Response [${msg.id}]: ${status}`);
if (pipelineActive) { if (traceActive) {
if (msg.ok) plog(`Gateway ACK [${msg.id}] — Nachricht angenommen`); if (msg.ok) plog(`Gateway ACK [${msg.id}] — Nachricht angenommen`);
else plog(`Gateway NACK [${msg.id}] — ${JSON.stringify(msg.error).slice(0, 100)}`, "error"); else plog(`Gateway NACK [${msg.id}] — ${JSON.stringify(msg.error).slice(0, 100)}`, "error");
} }
@@ -427,12 +427,12 @@ function handleGatewayMessage(msg) {
if (runId && seenFinalRuns.has(runId)) return; // Duplikat if (runId && seenFinalRuns.has(runId)) return; // Duplikat
if (runId) { seenFinalRuns.add(runId); setTimeout(() => seenFinalRuns.delete(runId), 60000); } if (runId) { seenFinalRuns.add(runId); setTimeout(() => seenFinalRuns.delete(runId), 60000); }
// NO_REPLY → ARIA signalisiert "nicht antworten", Pipeline beenden aber nichts zeigen // NO_REPLY → ARIA signalisiert "nicht antworten", Trace beenden aber nichts zeigen
const trimmed = (text || "").trim().replace(/^["'`*.\s]+|["'`*.\s]+$/g, "").toUpperCase(); const trimmed = (text || "").trim().replace(/^["'`*.\s]+|["'`*.\s]+$/g, "").toUpperCase();
if (trimmed === "NO_REPLY" || trimmed.startsWith("NO_REPLY")) { if (trimmed === "NO_REPLY" || trimmed.startsWith("NO_REPLY")) {
log("info", "gateway", "NO_REPLY empfangen — still verworfen"); log("info", "gateway", "NO_REPLY empfangen — still verworfen");
lastChatFinalAt = Date.now(); lastChatFinalAt = Date.now();
if (pipelineActive) pipelineEnd(true, "NO_REPLY (stumm)"); if (traceActive) traceEnd(true, "NO_REPLY (stumm)");
broadcast({ type: "agent_activity", activity: "idle" }); broadcast({ type: "agent_activity", activity: "idle" });
pendingMessageTime = 0; pendingMessageTime = 0;
updateAgentActivity(); updateAgentActivity();
@@ -441,7 +441,7 @@ function handleGatewayMessage(msg) {
log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`); log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`);
lastChatFinalAt = Date.now(); lastChatFinalAt = Date.now();
if (pipelineActive) pipelineEnd(true, `"${text.slice(0, 120)}"`); if (traceActive) traceEnd(true, `"${text.slice(0, 120)}"`);
broadcast({ type: "chat_final", text, payload }); broadcast({ type: "chat_final", text, payload });
broadcast({ type: "agent_activity", activity: "idle" }); broadcast({ type: "agent_activity", activity: "idle" });
pendingMessageTime = 0; // Watchdog: Antwort erhalten pendingMessageTime = 0; // Watchdog: Antwort erhalten
@@ -462,7 +462,7 @@ function handleGatewayMessage(msg) {
if (state === "error") { if (state === "error") {
const error = payload.error || text || "Unbekannt"; const error = payload.error || text || "Unbekannt";
log("error", "gateway", `Chat-Fehler: ${error}`); log("error", "gateway", `Chat-Fehler: ${error}`);
if (pipelineActive) pipelineEnd(false, error); if (traceActive) traceEnd(false, error);
else broadcast({ type: "agent_activity", activity: "idle" }); else broadcast({ type: "agent_activity", activity: "idle" });
broadcast({ type: "chat_error", error, payload }); broadcast({ type: "chat_error", error, payload });
return; return;
@@ -485,7 +485,7 @@ function handleGatewayMessage(msg) {
const text = extractChatText(payload) || payload.text || ""; const text = extractChatText(payload) || payload.text || "";
log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`); log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`);
lastChatFinalAt = Date.now(); lastChatFinalAt = Date.now();
if (pipelineActive) pipelineEnd(true, `"${text.slice(0, 120)}"`); if (traceActive) traceEnd(true, `"${text.slice(0, 120)}"`);
else broadcast({ type: "agent_activity", activity: "idle" }); else broadcast({ type: "agent_activity", activity: "idle" });
broadcast({ type: "chat_final", text, payload }); broadcast({ type: "chat_final", text, payload });
return; return;
@@ -493,7 +493,7 @@ function handleGatewayMessage(msg) {
if (event === "chat:error") { if (event === "chat:error") {
const error = payload.error || payload.message || "Unbekannt"; const error = payload.error || payload.message || "Unbekannt";
log("error", "gateway", `Chat-Fehler: ${error}`); log("error", "gateway", `Chat-Fehler: ${error}`);
if (pipelineActive) pipelineEnd(false, error); if (traceActive) traceEnd(false, error);
else broadcast({ type: "agent_activity", activity: "idle" }); else broadcast({ type: "agent_activity", activity: "idle" });
broadcast({ type: "chat_error", error, payload }); broadcast({ type: "chat_error", error, payload });
return; return;
@@ -505,10 +505,10 @@ function handleGatewayMessage(msg) {
} }
} }
function sendToGateway(text, isPipeline) { function sendToGateway(text, isTrace) {
if (!gatewayWs || gatewayWs.readyState !== WebSocket.OPEN) { if (!gatewayWs || gatewayWs.readyState !== WebSocket.OPEN) {
log("error", "gateway", "Nicht verbunden — kann nicht senden"); log("error", "gateway", "Nicht verbunden — kann nicht senden");
if (isPipeline) pipelineEnd(false, "Gateway nicht verbunden"); if (isTrace) traceEnd(false, "Gateway nicht verbunden");
return false; return false;
} }
@@ -535,7 +535,7 @@ function sendToGateway(text, isPipeline) {
fs.appendFileSync("/shared/config/chat_backup.jsonl", entry); fs.appendFileSync("/shared/config/chat_backup.jsonl", entry);
} catch {} } catch {}
log("info", "gateway", `chat.send [${reqId}]: "${text}"`); log("info", "gateway", `chat.send [${reqId}]: "${text}"`);
if (isPipeline) plog(`chat.send [${reqId}] an Gateway gesendet — warte auf ACK...`); if (isTrace) plog(`chat.send [${reqId}] an Gateway gesendet — warte auf ACK...`);
// Gateway-Nachrichten NICHT an RVS senden (sonst doppelter ARIA-Request via Bridge) // Gateway-Nachrichten NICHT an RVS senden (sonst doppelter ARIA-Request via Bridge)
return true; return true;
@@ -605,8 +605,8 @@ function connectRVS(forcePlain) {
// Eigene Nachrichten ignorieren (Echo) // Eigene Nachrichten ignorieren (Echo)
if (sender === "diagnostic") return; if (sender === "diagnostic") return;
log("info", "rvs", `Chat von ${sender}: "${(msg.payload.text || "").slice(0, 100)}"`); log("info", "rvs", `Chat von ${sender}: "${(msg.payload.text || "").slice(0, 100)}"`);
if (pipelineActive) { if (traceActive) {
pipelineEnd(true, `Antwort via RVS von ${sender}: "${(msg.payload.text || "").slice(0, 120)}"`); traceEnd(true, `Antwort via RVS von ${sender}: "${(msg.payload.text || "").slice(0, 120)}"`);
} }
broadcast({ type: "rvs_chat", msg }); broadcast({ type: "rvs_chat", msg });
} else if (msg.type === "file_saved" && msg.payload) { } else if (msg.type === "file_saved" && msg.payload) {
@@ -744,14 +744,14 @@ function sendToRVS_raw(msgObj) {
freshWs.on("error", () => {}); freshWs.on("error", () => {});
} }
function sendToRVS(text, isPipeline) { function sendToRVS(text, isTrace) {
// Ueber Gateway senden (zuverlaessig) UND an RVS fuer App-Sichtbarkeit // Ueber Gateway senden (zuverlaessig) UND an RVS fuer App-Sichtbarkeit
// Die Bridge empfaengt RVS-Nachrichten von der App zuverlaessig, // Die Bridge empfaengt RVS-Nachrichten von der App zuverlaessig,
// aber die Diagnostic→RVS→Bridge Route hat Zombie-Probleme. // aber die Diagnostic→RVS→Bridge Route hat Zombie-Probleme.
// Deshalb: Gateway fuer ARIA, RVS nur fuer App-Anzeige. // Deshalb: Gateway fuer ARIA, RVS nur fuer App-Anzeige.
// 1. An Gateway senden (damit ARIA antwortet) // 1. An Gateway senden (damit ARIA antwortet)
const gatewayOk = sendToGateway(text, isPipeline); const gatewayOk = sendToGateway(text, isTrace);
// 2. An RVS senden (damit die App die Nachricht sieht) // 2. An RVS senden (damit die App die Nachricht sieht)
sendToRVS_raw({ sendToRVS_raw({
@@ -1337,7 +1337,7 @@ const server = http.createServer((req, res) => {
pendingMessageTime = 0; pendingMessageTime = 0;
watchdogWarned = false; watchdogWarned = false;
watchdogFixAttempted = false; watchdogFixAttempted = false;
if (pipelineActive) pipelineEnd(false, "Vom Benutzer abgebrochen (App)"); if (traceActive) traceEnd(false, "Vom Benutzer abgebrochen (App)");
else broadcast({ type: "agent_activity", activity: "idle" }); else broadcast({ type: "agent_activity", activity: "idle" });
dockerExec("aria-core", "openclaw doctor --fix 2>/dev/null || true").catch(() => {}); dockerExec("aria-core", "openclaw doctor --fix 2>/dev/null || true").catch(() => {});
res.writeHead(200, { "Content-Type": "application/json" }); res.writeHead(200, { "Content-Type": "application/json" });
@@ -1475,10 +1475,10 @@ wss.on("connection", (ws) => {
const msg = JSON.parse(raw.toString()); const msg = JSON.parse(raw.toString());
if (msg.action === "test_gateway") { if (msg.action === "test_gateway") {
pipelineStart("Gateway", msg.text || "aria lebst du noch?"); traceStart("Gateway", msg.text || "aria lebst du noch?");
sendToGateway(msg.text || "aria lebst du noch?", true); sendToGateway(msg.text || "aria lebst du noch?", true);
} else if (msg.action === "test_rvs") { } else if (msg.action === "test_rvs") {
pipelineStart("RVS", msg.text || "aria lebst du noch?"); traceStart("RVS", msg.text || "aria lebst du noch?");
sendToRVS(msg.text || "aria lebst du noch?", true); sendToRVS(msg.text || "aria lebst du noch?", true);
} else if (msg.action === "reconnect_gateway") { } else if (msg.action === "reconnect_gateway") {
connectGateway(); connectGateway();
@@ -1521,7 +1521,7 @@ wss.on("connection", (ws) => {
pendingMessageTime = 0; pendingMessageTime = 0;
watchdogWarned = false; watchdogWarned = false;
watchdogFixAttempted = false; watchdogFixAttempted = false;
if (pipelineActive) pipelineEnd(false, "Vom Benutzer abgebrochen"); if (traceActive) traceEnd(false, "Vom Benutzer abgebrochen");
broadcast({ type: "agent_activity", activity: "idle" }); broadcast({ type: "agent_activity", activity: "idle" });
dockerExec("aria-core", "openclaw doctor --fix 2>/dev/null || true").catch(() => {}); dockerExec("aria-core", "openclaw doctor --fix 2>/dev/null || true").catch(() => {});
} else if (msg.action === "voice_upload") { } else if (msg.action === "voice_upload") {