fix: Thinking indicator respringt nach chat:final durch trailing events

Nach chat:final kommen oft noch agent-Events rein (Core raeumt nach),
die den Thinking-Indicator wieder anspringen liessen.

- Diagnostic: 3s-Settled-Window nach chat:final, agent_activity-Broadcasts
  werden in dem Fenster unterdrueckt (idle kommt weiter durch).
- Bridge: Gleiches Fenster in _emit_activity() — App bekommt keine
  trailing thinking/tool-Events mehr nach dem finalen Antwort.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-18 11:51:22 +02:00
parent 271fc4edf6
commit 8c1014d281
2 changed files with 29 additions and 4 deletions
+16 -3
View File
@@ -82,6 +82,12 @@ const browserClients = new Set();
let pipelineActive = false;
let pipelineStartTime = 0;
// Nach chat:final kommen oft noch Trailing Agent-Events. Waehrend dieses
// Fensters unterdruecken wir agent_activity-Broadcasts, damit der
// Thinking-Indicator nicht wieder anspringt.
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}` };
@@ -356,17 +362,22 @@ function handleGatewayMessage(msg) {
broadcast({ type: "chat_delta", delta, payload });
}
// Nach chat:final trickeln noch Aufraeum-Events rein — unterdruecken,
// damit der Thinking-Indicator nicht wieder anspringt.
const settled = lastChatFinalAt && (Date.now() - lastChatFinalAt) < SETTLED_WINDOW_MS;
// Tool-Nutzung erkennen und broadcasten
if (stream === "tool_use" || data.type === "tool_use") {
const toolName = data.name || data.tool || payload.tool || "";
if (toolName) {
if (toolName && !settled) {
broadcast({ type: "agent_activity", activity: "tool", tool: toolName, data });
log("info", "gateway", `Tool: ${toolName}`);
}
}
// Genereller Activity-Heartbeat (ARIA denkt)
broadcast({ type: "agent_activity", activity: stream || "thinking" });
if (!settled) {
broadcast({ type: "agent_activity", activity: stream || "thinking" });
}
updateAgentActivity();
return;
}
@@ -381,6 +392,7 @@ function handleGatewayMessage(msg) {
if (runId && seenFinalRuns.has(runId)) return; // Duplikat
if (runId) { seenFinalRuns.add(runId); setTimeout(() => seenFinalRuns.delete(runId), 60000); }
log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`);
lastChatFinalAt = Date.now();
if (pipelineActive) pipelineEnd(true, `"${text.slice(0, 120)}"`);
broadcast({ type: "chat_final", text, payload });
broadcast({ type: "agent_activity", activity: "idle" });
@@ -424,6 +436,7 @@ function handleGatewayMessage(msg) {
if (runId) { seenFinalRuns.add(runId); setTimeout(() => seenFinalRuns.delete(runId), 60000); }
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)}"`);
else broadcast({ type: "agent_activity", activity: "idle" });
broadcast({ type: "chat_final", text, payload });