diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index 94f291d..0647f94 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -1249,9 +1249,14 @@ const ChatScreen: React.FC = () => { ? '\u270D\uFE0F ARIA schreibt...' : '\uD83D\uDCAD ARIA denkt...'} - - Abbrechen - + + rvs.send('doctor_fix' as any, {})}> + {'🔧 Reparieren'} + + + Abbrechen + + )} diff --git a/android/src/screens/SettingsScreen.tsx b/android/src/screens/SettingsScreen.tsx index 3036160..6d7080c 100644 --- a/android/src/screens/SettingsScreen.tsx +++ b/android/src/screens/SettingsScreen.tsx @@ -1288,6 +1288,26 @@ const SettingsScreen: React.FC = () => { + {/* === ARIA Reparatur === */} + Reparatur + + + Wenn ARIA gar nicht mehr antwortet oder auf jede Anfrage mit + "Antwort ohne Text" zurueckkommt — meistens ein steckengebliebener + Run im aria-core. Dieser Button fuehrt {'“'}openclaw doctor --fix{'”'} + aus und macht ARIA wieder ansprechbar. + + { + rvs.send('doctor_fix' as any, {}); + ToastAndroid.show('Reparatur-Befehl gesendet — Antwort kommt gleich', ToastAndroid.SHORT); + }} + > + {'🔧 ARIA reparieren'} + + + )} {/* === Logs === */} diff --git a/bridge/aria_bridge.py b/bridge/aria_bridge.py index 699b529..2b887e6 100644 --- a/bridge/aria_bridge.py +++ b/bridge/aria_bridge.py @@ -1580,6 +1580,43 @@ class ARIABridge: except Exception as e: logger.warning("[rvs] file_saved konnte nicht an App gesendet werden: %s", e) + elif msg_type == "doctor_fix": + # App-Button "ARIA reparieren" → openclaw doctor --fix anstossen. + # Bridge erreicht aria-core nicht via docker (kein docker-socket + # gemountet), aber der Diagnostic-Server hat den Socket. HTTP-Call + # an http://localhost:3001/api/doctor-fix. + logger.info("[rvs] doctor_fix Request von App — leite an Diagnostic weiter") + try: + req = urllib.request.Request( + "http://localhost:3001/api/doctor-fix", + data=b"{}", + method="POST", + headers={"Content-Type": "application/json"}, + ) + # Blocking call ist OK weil openclaw doctor schnell durchlaeuft. + # In Executor laufen lassen damit der asyncio-Loop nicht blockt. + def _do_fix(): + try: + with urllib.request.urlopen(req, timeout=30) as resp: + return resp.status, resp.read().decode("utf-8", errors="ignore") + except Exception as e: + return None, str(e) + status, body = await asyncio.get_event_loop().run_in_executor(None, _do_fix) + ok = status == 200 + logger.info("[rvs] doctor_fix Result: status=%s ok=%s", status, ok) + await self._send_to_rvs({ + "type": "chat", + "payload": { + "text": "[Reparatur] ARIA wurde durchgecheckt — sollte wieder antworten." if ok + else f"[Reparatur] Fehlgeschlagen: {body[:200]}", + "sender": "aria", + }, + "timestamp": int(asyncio.get_event_loop().time() * 1000), + }) + except Exception as e: + logger.warning("[rvs] doctor_fix Weiterleitung fehlgeschlagen: %s", e) + return + elif msg_type == "file_request": # App fordert eine Datei an (Re-Download nach Cache-Leerung) server_path = payload.get("serverPath", "") diff --git a/diagnostic/index.html b/diagnostic/index.html index 98cea7e..1dafbd4 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -288,7 +288,10 @@
@@ -1858,6 +1861,20 @@ renderDiagPending(); } + // ── Reparieren — openclaw doctor --fix ────── + function doctorFix() { + fetch('/api/doctor-fix', { method: 'POST' }) + .then(r => r.json()) + .then(data => { + if (data.ok) { + addLog('info', 'server', 'Reparatur ausgefuehrt: ' + (data.output || 'OK').slice(0, 200)); + } else { + addLog('error', 'server', 'Reparatur fehlgeschlagen: ' + (data.error || '')); + } + }) + .catch(err => addLog('error', 'server', 'Reparatur Request fehlgeschlagen: ' + err.message)); + } + // ── Abbrechen ────────────────────────────── function cancelRequest() { send({ action: 'cancel_request' }); diff --git a/diagnostic/server.js b/diagnostic/server.js index 5716904..388ec19 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -1342,6 +1342,22 @@ const server = http.createServer((req, res) => { dockerExec("aria-core", "openclaw doctor --fix 2>/dev/null || true").catch(() => {}); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ ok: true })); + } else if (req.url === "/api/doctor-fix" && req.method === "POST") { + // Manueller "ARIA reparieren"-Button — stuck OpenClaw-Runs aufloesen. + log("info", "server", "HTTP /api/doctor-fix — manueller Reparatur-Trigger"); + dockerExec("aria-core", "openclaw doctor --fix 2>&1") + .then(out => { + const summary = (out || "").split("\n").filter(l => l.trim()).slice(-3).join(" | "); + broadcast({ type: "watchdog", status: "fixed", message: `Reparatur ausgefuehrt: ${summary || "OK"}` }); + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ ok: true, output: out })); + }) + .catch(err => { + broadcast({ type: "watchdog", status: "error", message: `Reparatur fehlgeschlagen: ${err.message}` }); + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ ok: false, error: err.message })); + }); + return; } else if (req.url.startsWith("/shared/")) { // Dateien aus Shared Volume ausliefern (Bilder, Uploads) const filePath = decodeURIComponent(req.url); diff --git a/rvs/server.js b/rvs/server.js index cf75142..e67c9ed 100644 --- a/rvs/server.js +++ b/rvs/server.js @@ -19,6 +19,7 @@ const ALLOWED_TYPES = new Set([ "agent_activity", "cancel_request", "audio_pcm", "file_from_aria", + "doctor_fix", "xtts_delete_voice", "voice_preload", "voice_ready", "stt_request", "stt_response",