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 @@
💭 ARIA denkt...
-
+
+
+
+
@@ -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",