diagnostic/server.js — Handler umgebaut: event: "agent" für Deltas, event: "chat" mit state: "final" für Antworten, extractChatText() parst das content[] Array

bridge/aria_bridge.py — Gleicher Fix: _extract_chat_text() Methode, neue Event-Handler für agent und chat mit state, Legacy-Namen als Fallback
This commit is contained in:
duffyduck 2026-03-13 08:14:52 +01:00
parent fcb22f60d3
commit 9cad631015
3 changed files with 133 additions and 39 deletions

View File

@ -16,6 +16,15 @@ Alle Änderungen am Projekt. Format: [Keep a Changelog](https://keepachangelog.c
- 60s Timeout — markiert Pipeline als fehlgeschlagen wenn keine Antwort kommt - 60s Timeout — markiert Pipeline als fehlgeschlagen wenn keine Antwort kommt
- Funktioniert für Gateway-direkt und RVS-Nachrichten - Funktioniert für Gateway-direkt und RVS-Nachrichten
### Behoben
**OpenClaw Gateway Event-Format — ARIA antwortet jetzt**
- OpenClaw sendet `event: "agent"` (Streaming-Deltas in `payload.data.delta`) und `event: "chat"` mit `payload.state: "delta"|"final"|"error"`**nicht** `chat:delta`/`chat:final`/`chat:error` wie angenommen
- Antworttext steckt in `payload.message.content[0].text` (Array von Content-Blöcken, nicht flacher String) — `text.slice is not a function` Fehler behoben
- `ackReactionScope` von `"group-mentions"` auf `"all"` geändert — Agent reagierte nur auf @mentions, nicht auf direkte Nachrichten
- Diagnostic Server und Bridge auf neues Event-Format umgestellt
- Legacy-Event-Namen (`chat:delta`, `chat:final`, `chat:error`) als Fallback beibehalten
### Geändert ### Geändert
**OpenClaw Config — Custom Provider Format** **OpenClaw Config — Custom Provider Format**

View File

@ -625,28 +625,64 @@ class ARIABridge:
event_name = message.get("event", "") event_name = message.get("event", "")
payload = message.get("payload", {}) payload = message.get("payload", {})
if event_name == "chat:delta": # ── agent Events: Streaming-Deltas vom LLM ──
# Streaming-Delta — fuer spaeter (Live-Typing in der App) if event_name == "agent":
delta = payload.get("delta", payload.get("text", "")) data = payload.get("data", {})
if delta: delta = data.get("delta", "")
if delta and payload.get("stream") == "assistant":
logger.debug("[core] Delta: '%s'", delta[:40]) logger.debug("[core] Delta: '%s'", delta[:40])
return return
# ── chat Events: Snapshots mit state=delta|final|error ──
if event_name == "chat":
state = payload.get("state", "")
if state == "final":
text = self._extract_chat_text(payload)
if not text:
logger.warning("[core] chat final ohne Text: %s", json.dumps(payload)[:200])
return
logger.info("[core] Antwort: '%s'", text[:80])
await self._process_core_response(text, payload)
return
if state == "error":
error = payload.get("error", "Unbekannt")
logger.error("[core] Chat-Fehler: %s", error)
await self._send_to_rvs({
"type": "chat",
"payload": {
"text": f"[Fehler] {error}",
"sender": "aria",
},
"timestamp": int(asyncio.get_event_loop().time() * 1000),
})
return
# state=delta — periodischer Snapshot, ignorieren
return
# ── Legacy event names (chat:delta, chat:final, chat:error) ──
if event_name == "chat:delta":
delta = payload.get("delta", payload.get("text", ""))
if delta:
logger.debug("[core] Delta (legacy): '%s'", delta[:40])
return
if event_name == "chat:final": if event_name == "chat:final":
# Fertige Antwort von aria-core
text = payload.get("text", payload.get("message", "")) text = payload.get("text", payload.get("message", ""))
if not text:
text = self._extract_chat_text(payload)
if not text: if not text:
logger.warning("[core] chat:final ohne Text: %s", json.dumps(payload)[:200]) logger.warning("[core] chat:final ohne Text: %s", json.dumps(payload)[:200])
return return
logger.info("[core] Antwort (legacy): '%s'", text[:80])
logger.info("[core] Antwort: '%s'", text[:80])
await self._process_core_response(text, payload) await self._process_core_response(text, payload)
return return
if event_name == "chat:error": if event_name == "chat:error":
error = payload.get("error", payload.get("message", "Unbekannt")) error = payload.get("error", payload.get("message", "Unbekannt"))
logger.error("[core] Chat-Fehler: %s", error) logger.error("[core] Chat-Fehler (legacy): %s", error)
# Fehler auch an die App melden
await self._send_to_rvs({ await self._send_to_rvs({
"type": "chat", "type": "chat",
"payload": { "payload": {
@ -657,9 +693,27 @@ class ARIABridge:
}) })
return return
# Andere Events loggen (presence, tick, etc.) # tick, health, etc. — ignorieren
if event_name in ("tick", "health"):
return
logger.debug("[core] Event: %s", event_name) logger.debug("[core] Event: %s", event_name)
@staticmethod
def _extract_chat_text(payload: dict) -> str:
"""Extrahiert Text aus OpenClaw chat-Event message.content Array."""
try:
content = payload.get("message", {}).get("content", [])
if isinstance(content, list):
return "".join(
c.get("text", "") for c in content if c.get("type") == "text"
)
if isinstance(content, str):
return content
except (AttributeError, TypeError):
pass
return payload.get("text", "")
async def _process_core_response(self, text: str, payload: dict) -> None: async def _process_core_response(self, text: str, payload: dict) -> None:
"""Verarbeitet eine fertige Antwort von aria-core. """Verarbeitet eine fertige Antwort von aria-core.

View File

@ -241,6 +241,21 @@ async function connectGateway() {
} }
} }
// Extrahiert Text aus OpenClaw chat-Event message.content Array
function extractChatText(payload) {
try {
const content = payload.message?.content;
if (Array.isArray(content)) {
return content
.filter(c => c.type === "text")
.map(c => c.text || "")
.join("");
}
if (typeof payload.message === "string") return payload.message;
return payload.text || "";
} catch { return ""; }
}
function handleGatewayMessage(msg) { 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)}`;
@ -257,24 +272,59 @@ function handleGatewayMessage(msg) {
const event = msg.event || "?"; const event = msg.event || "?";
const payload = msg.payload || {}; const payload = msg.payload || {};
if (event === "chat:delta") { // ── agent Events: Streaming-Deltas vom LLM ──
const delta = payload.delta || payload.text || ""; if (event === "agent") {
if (delta) { const data = payload.data || {};
log("info", "gateway", `Delta: "${delta.slice(0, 60)}"`); const delta = data.delta || "";
if (pipelineActive) plog(`Streaming Delta: "${delta.slice(0, 80)}"`); if (delta && payload.stream === "assistant") {
broadcast({ type: "chat_delta", delta, payload }); broadcast({ type: "chat_delta", delta, payload });
} }
// agent Events nicht einzeln loggen (zu viele)
return; return;
} }
// ── chat Events: Snapshots mit state=delta|final ──
if (event === "chat") {
const state = payload.state || "";
const text = extractChatText(payload);
if (state === "final") {
log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`);
if (pipelineActive) pipelineEnd(true, `"${text.slice(0, 120)}"`);
broadcast({ type: "chat_final", text, payload });
return;
}
if (state === "delta") {
// Periodischer Snapshot — nicht einzeln loggen
return;
}
if (state === "error") {
const error = payload.error || text || "Unbekannt";
log("error", "gateway", `Chat-Fehler: ${error}`);
if (pipelineActive) pipelineEnd(false, error);
broadcast({ type: "chat_error", error, payload });
return;
}
log("debug", "gateway", `chat state=${state}`);
return;
}
// ── Legacy event names (chat:delta, chat:final, chat:error) ──
if (event === "chat:delta") {
const delta = payload.delta || payload.text || "";
if (delta) broadcast({ type: "chat_delta", delta, payload });
return;
}
if (event === "chat:final") { if (event === "chat:final") {
const text = payload.text || payload.message || ""; const text = extractChatText(payload) || payload.text || "";
log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`); log("info", "gateway", `ANTWORT: "${text.slice(0, 200)}"`);
if (pipelineActive) pipelineEnd(true, `"${text.slice(0, 120)}"`); if (pipelineActive) pipelineEnd(true, `"${text.slice(0, 120)}"`);
broadcast({ type: "chat_final", text, payload }); broadcast({ type: "chat_final", text, payload });
return; return;
} }
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}`);
@ -283,28 +333,9 @@ function handleGatewayMessage(msg) {
return; return;
} }
// Alle anderen Events — vollstaendig loggen fuer Debugging // ── Andere Events (tick, health, presence) ──
const payloadStr = JSON.stringify(payload).slice(0, 500); if (event === "tick" || event === "health") return; // Noise
log("info", "gateway", `Event: ${event} | ${payloadStr}`); log("debug", "gateway", `Event: ${event}`);
if (pipelineActive) plog(`Gateway Event: ${event} | ${payloadStr.slice(0, 200)}`);
// "chat" Event koennte die Antwort sein (anderes Format als erwartet)
if (event === "chat") {
const text = payload.text || payload.message || payload.content || "";
const delta = payload.delta || "";
const error = payload.error || "";
if (error) {
log("error", "gateway", `Chat-Fehler (event:chat): ${error}`);
if (pipelineActive) pipelineEnd(false, error);
broadcast({ type: "chat_error", error, payload });
} else if (text) {
log("info", "gateway", `ANTWORT (event:chat): "${text.slice(0, 200)}"`);
if (pipelineActive) pipelineEnd(true, `"${text.slice(0, 120)}"`);
broadcast({ type: "chat_final", text, payload });
} else if (delta) {
broadcast({ type: "chat_delta", delta, payload });
}
}
} }
} }