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.info { color: #AAB; }
.log-entry.debug { color: #555570; }
.log-entry.pipeline-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.pipeline-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-step { color: #0096FF; border-left: 2px solid #0096FF; 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.trace-err { color: #FF3B30; border-left: 2px solid #FF3B30; padding-left: 6px; margin: 2px 0; }
.log-entry.trace-sep { color: #333; margin: 6px 0 2px; }
.chat-box { background: #080810; border: 1px solid #1E1E2E; border-radius: 6px;
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="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="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 class="log-panel">
@@ -398,7 +398,7 @@
<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-server"></div>
<div class="log-box hidden" id="log-pipeline"></div>
<div class="log-box hidden" id="log-trace"></div>
</div>
</div>
@@ -729,8 +729,8 @@
let ws;
let activeTab = 'all';
const DOCKER_TABS = ['gateway', 'proxy', 'bridge'];
const autoScroll = { all: true, gateway: true, rvs: true, proxy: true, bridge: true, server: true, pipeline: true };
const logCounts = { all: 0, gateway: 0, rvs: 0, proxy: 0, bridge: 0, server: 0, pipeline: 0 };
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, trace: 0 };
const logBoxes = {
all: document.getElementById('log-all'),
@@ -739,7 +739,7 @@
proxy: document.getElementById('log-proxy'),
bridge: document.getElementById('log-bridge'),
server: document.getElementById('log-server'),
pipeline: document.getElementById('log-pipeline'),
trace: document.getElementById('log-trace'),
tts: document.getElementById('log-tts'),
};
@@ -794,7 +794,7 @@
if (source === 'proxy') return 'proxy';
if (source === 'bridge') return 'bridge';
if (source === 'server' || source === 'browser') return 'server';
if (source === 'pipeline') return 'pipeline';
if (source === 'trace') return 'trace';
return null;
}
@@ -1333,12 +1333,12 @@
const time = ts ? new Date(ts).toLocaleTimeString('de-DE') : new Date().toLocaleTimeString('de-DE');
const line = `${time} [${source}] ${message}`;
// Pipeline-Eintraege nur in Pipeline-Tab (nicht in Alle)
if (source === 'pipeline') {
const pipeLevel = level === 'error' ? 'pipeline-err' : level === 'info' && message.includes('>>>') ? 'pipeline-ok' : 'pipeline-step';
appendToLog('pipeline', pipeLevel, `${time} ${message}`);
logCounts.pipeline++;
document.getElementById('count-pipeline').textContent = logCounts.pipeline;
// Trace-Eintraege nur in Trace-Tab (nicht in Alle)
if (source === 'trace') {
const pipeLevel = level === 'error' ? 'trace-err' : level === 'info' && message.includes('>>>') ? 'trace-ok' : 'trace-step';
appendToLog('trace', pipeLevel, `${time} ${message}`);
logCounts.trace++;
document.getElementById('count-trace').textContent = logCounts.trace;
return;
}
+42 -42
View File
@@ -113,9 +113,9 @@ let rvsWs = null;
let reqIdCounter = 0;
const browserClients = new Set();
// ── Pipeline Tracking ──────────────────────────────────
let pipelineActive = false;
let pipelineStartTime = 0;
// ── Trace-Tracking (End-to-End-Mitschnitt einer Anfrage) ──────────────────────────────────
let traceActive = false;
let traceStartTime = 0;
// Nach chat:final kommen oft noch Trailing Agent-Events. Waehrend dieses
// Fensters unterdruecken wir agent_activity-Broadcasts, damit der
@@ -124,40 +124,40 @@ let lastChatFinalAt = 0;
const SETTLED_WINDOW_MS = 3000;
function plog(message, level) {
const elapsed = pipelineActive ? `+${Date.now() - pipelineStartTime}ms` : "";
const entry = { ts: new Date().toISOString(), level: level || "info", source: "pipeline", message: `${elapsed ? `[${elapsed}] ` : ""}${message}` };
const elapsed = traceActive ? `+${Date.now() - traceStartTime}ms` : "";
const entry = { ts: new Date().toISOString(), level: level || "info", source: "trace", message: `${elapsed ? `[${elapsed}] ` : ""}${message}` };
logs.push(entry);
if (logs.length > 500) logs.shift();
console.log(`[PIPELINE] ${entry.message}`);
console.log(`[TRACE] ${entry.message}`);
broadcast({ type: "log", entry });
}
let pipelineTimeout = null;
let traceTimeout = null;
function pipelineStart(method, text) {
// Falls noch eine Pipeline laeuft, beenden
if (pipelineActive) pipelineEnd(false, "Abgebrochen (neue Nachricht)");
pipelineActive = true;
pipelineStartTime = Date.now();
if (pipelineTimeout) clearTimeout(pipelineTimeout);
pipelineTimeout = setTimeout(() => {
if (pipelineActive) pipelineEnd(false, "Timeout — keine Antwort nach 10min");
function traceStart(method, text) {
// Falls noch ein Trace laeuft, beenden
if (traceActive) traceEnd(false, "Abgebrochen (neue Nachricht)");
traceActive = true;
traceStartTime = Date.now();
if (traceTimeout) clearTimeout(traceTimeout);
traceTimeout = setTimeout(() => {
if (traceActive) traceEnd(false, "Timeout — keine Antwort nach 10min");
}, 600000);
plog(`━━━ Pipeline Start: ${method} ━━━`);
plog(`━━━ Trace Start: ${method} ━━━`);
plog(`Nachricht: "${text}"`);
}
function pipelineEnd(ok, detail) {
if (!pipelineActive) return;
if (pipelineTimeout) { clearTimeout(pipelineTimeout); pipelineTimeout = null; }
const elapsed = Date.now() - pipelineStartTime;
function traceEnd(ok, detail) {
if (!traceActive) return;
if (traceTimeout) { clearTimeout(traceTimeout); traceTimeout = null; }
const elapsed = Date.now() - traceStartTime;
if (ok) {
plog(`>>> Fertig (${elapsed}ms): ${detail}`);
} else {
plog(`>>> FEHLER (${elapsed}ms): ${detail}`, "error");
}
plog(`━━━ Pipeline Ende ━━━`);
pipelineActive = false;
plog(`━━━ Trace Ende ━━━`);
traceActive = false;
// Thinking-Indikator IMMER zuruecksetzen — auch bei Timeout/Fehler/Abbruch
broadcast({ type: "agent_activity", activity: "idle" });
pendingMessageTime = 0;
@@ -327,8 +327,8 @@ async function connectGateway() {
state.gateway.handshakeOk = false;
gatewayWs = null;
broadcastState();
// Stuck "ARIA denkt..." vermeiden, falls Gateway waehrend Pipeline abkackt
if (pipelineActive) pipelineEnd(false, `Gateway-Verbindung verloren (${code})`);
// Stuck "ARIA denkt..." vermeiden, falls Gateway waehrend Trace abkackt
if (traceActive) traceEnd(false, `Gateway-Verbindung verloren (${code})`);
else broadcast({ type: "agent_activity", activity: "idle" });
checkGatewayHealth();
setTimeout(connectGateway, 5000);
@@ -375,7 +375,7 @@ function handleGatewayMessage(msg) {
if (msg.type === "res") {
const status = msg.ok ? "OK" : `FEHLER: ${JSON.stringify(msg.error).slice(0, 100)}`;
log("info", "gateway", `Response [${msg.id}]: ${status}`);
if (pipelineActive) {
if (traceActive) {
if (msg.ok) plog(`Gateway ACK [${msg.id}] — Nachricht angenommen`);
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.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();
if (trimmed === "NO_REPLY" || trimmed.startsWith("NO_REPLY")) {
log("info", "gateway", "NO_REPLY empfangen — still verworfen");
lastChatFinalAt = Date.now();
if (pipelineActive) pipelineEnd(true, "NO_REPLY (stumm)");
if (traceActive) traceEnd(true, "NO_REPLY (stumm)");
broadcast({ type: "agent_activity", activity: "idle" });
pendingMessageTime = 0;
updateAgentActivity();
@@ -441,7 +441,7 @@ function handleGatewayMessage(msg) {
log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`);
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: "agent_activity", activity: "idle" });
pendingMessageTime = 0; // Watchdog: Antwort erhalten
@@ -462,7 +462,7 @@ function handleGatewayMessage(msg) {
if (state === "error") {
const error = payload.error || text || "Unbekannt";
log("error", "gateway", `Chat-Fehler: ${error}`);
if (pipelineActive) pipelineEnd(false, error);
if (traceActive) traceEnd(false, error);
else broadcast({ type: "agent_activity", activity: "idle" });
broadcast({ type: "chat_error", error, payload });
return;
@@ -485,7 +485,7 @@ function handleGatewayMessage(msg) {
const text = extractChatText(payload) || payload.text || "";
log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`);
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" });
broadcast({ type: "chat_final", text, payload });
return;
@@ -493,7 +493,7 @@ function handleGatewayMessage(msg) {
if (event === "chat:error") {
const error = payload.error || payload.message || "Unbekannt";
log("error", "gateway", `Chat-Fehler: ${error}`);
if (pipelineActive) pipelineEnd(false, error);
if (traceActive) traceEnd(false, error);
else broadcast({ type: "agent_activity", activity: "idle" });
broadcast({ type: "chat_error", error, payload });
return;
@@ -505,10 +505,10 @@ function handleGatewayMessage(msg) {
}
}
function sendToGateway(text, isPipeline) {
function sendToGateway(text, isTrace) {
if (!gatewayWs || gatewayWs.readyState !== WebSocket.OPEN) {
log("error", "gateway", "Nicht verbunden — kann nicht senden");
if (isPipeline) pipelineEnd(false, "Gateway nicht verbunden");
if (isTrace) traceEnd(false, "Gateway nicht verbunden");
return false;
}
@@ -535,7 +535,7 @@ function sendToGateway(text, isPipeline) {
fs.appendFileSync("/shared/config/chat_backup.jsonl", entry);
} catch {}
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)
return true;
@@ -605,8 +605,8 @@ function connectRVS(forcePlain) {
// Eigene Nachrichten ignorieren (Echo)
if (sender === "diagnostic") return;
log("info", "rvs", `Chat von ${sender}: "${(msg.payload.text || "").slice(0, 100)}"`);
if (pipelineActive) {
pipelineEnd(true, `Antwort via RVS von ${sender}: "${(msg.payload.text || "").slice(0, 120)}"`);
if (traceActive) {
traceEnd(true, `Antwort via RVS von ${sender}: "${(msg.payload.text || "").slice(0, 120)}"`);
}
broadcast({ type: "rvs_chat", msg });
} else if (msg.type === "file_saved" && msg.payload) {
@@ -744,14 +744,14 @@ function sendToRVS_raw(msgObj) {
freshWs.on("error", () => {});
}
function sendToRVS(text, isPipeline) {
function sendToRVS(text, isTrace) {
// Ueber Gateway senden (zuverlaessig) UND an RVS fuer App-Sichtbarkeit
// Die Bridge empfaengt RVS-Nachrichten von der App zuverlaessig,
// aber die Diagnostic→RVS→Bridge Route hat Zombie-Probleme.
// Deshalb: Gateway fuer ARIA, RVS nur fuer App-Anzeige.
// 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)
sendToRVS_raw({
@@ -1337,7 +1337,7 @@ const server = http.createServer((req, res) => {
pendingMessageTime = 0;
watchdogWarned = 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" });
dockerExec("aria-core", "openclaw doctor --fix 2>/dev/null || true").catch(() => {});
res.writeHead(200, { "Content-Type": "application/json" });
@@ -1475,10 +1475,10 @@ wss.on("connection", (ws) => {
const msg = JSON.parse(raw.toString());
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);
} 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);
} else if (msg.action === "reconnect_gateway") {
connectGateway();
@@ -1521,7 +1521,7 @@ wss.on("connection", (ws) => {
pendingMessageTime = 0;
watchdogWarned = false;
watchdogFixAttempted = false;
if (pipelineActive) pipelineEnd(false, "Vom Benutzer abgebrochen");
if (traceActive) traceEnd(false, "Vom Benutzer abgebrochen");
broadcast({ type: "agent_activity", activity: "idle" });
dockerExec("aria-core", "openclaw doctor --fix 2>/dev/null || true").catch(() => {});
} else if (msg.action === "voice_upload") {