feat: "ARIA hart neu starten"-Button (docker restart aria-core)
Zweiter Eskalations-Button neben dem Reparieren-Button — fuer Faelle wo doctor --fix nicht reicht (Run alive aber haengt im Tool-Call). Mit Confirmation-Dialog damit's nicht versehentlich gedrueckt wird. Wege: - App-Settings: Reparatur-Sektion, zwei Buttons (Reparieren + Hart neu) - App-Thinking-Bubble: 🔧 + 🚨 + Abbrechen - Diagnostic-Thinking-Indicator: 🔧 + 🚨 + Abbrechen - Diagnostic-Server: POST /api/aria-restart → child_process exec `docker restart aria-core` - Bridge: rvs aria_restart → HTTP zu Diagnostic Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
|||||||
ToastAndroid,
|
ToastAndroid,
|
||||||
AppState,
|
AppState,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
|
Alert,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import RNFS from 'react-native-fs';
|
import RNFS from 'react-native-fs';
|
||||||
@@ -1251,7 +1252,22 @@ const ChatScreen: React.FC = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
<View style={{flexDirection: 'row', gap: 6}}>
|
<View style={{flexDirection: 'row', gap: 6}}>
|
||||||
<TouchableOpacity style={[styles.thinkingCancel, {borderColor: '#FF9500'}]} onPress={() => rvs.send('doctor_fix' as any, {})}>
|
<TouchableOpacity style={[styles.thinkingCancel, {borderColor: '#FF9500'}]} onPress={() => rvs.send('doctor_fix' as any, {})}>
|
||||||
<Text style={[styles.thinkingCancelText, {color: '#FF9500'}]}>{'🔧 Reparieren'}</Text>
|
<Text style={[styles.thinkingCancelText, {color: '#FF9500'}]}>{'🔧'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.thinkingCancel, {borderColor: '#FF3B30'}]}
|
||||||
|
onPress={() => {
|
||||||
|
Alert.alert(
|
||||||
|
'ARIA hart neu starten?',
|
||||||
|
'Container-Restart (~15s). Laufende Anfragen gehen verloren.',
|
||||||
|
[
|
||||||
|
{ text: 'Abbrechen', style: 'cancel' },
|
||||||
|
{ text: 'Neu starten', style: 'destructive', onPress: () => rvs.send('aria_restart' as any, {}) },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={[styles.thinkingCancelText, {color: '#FF3B30'}]}>{'🚨'}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity style={styles.thinkingCancel} onPress={cancelRequest}>
|
<TouchableOpacity style={styles.thinkingCancel} onPress={cancelRequest}>
|
||||||
<Text style={styles.thinkingCancelText}>Abbrechen</Text>
|
<Text style={styles.thinkingCancelText}>Abbrechen</Text>
|
||||||
|
|||||||
@@ -1306,6 +1306,29 @@ const SettingsScreen: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Text style={[styles.clearButtonText, {color: '#FF9500'}]}>{'🔧 ARIA reparieren'}</Text>
|
<Text style={[styles.clearButtonText, {color: '#FF9500'}]}>{'🔧 ARIA reparieren'}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
<Text style={[styles.toggleHint, {marginTop: 12}]}>
|
||||||
|
Wenn auch Reparieren nicht hilft — Container hart neu starten.
|
||||||
|
ARIA ist dann ~15 Sekunden weg und kommt mit frischem State zurueck.
|
||||||
|
Laufende Anfragen gehen verloren.
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.clearButton, {marginTop: 8, backgroundColor: 'rgba(255,59,48,0.15)'}]}
|
||||||
|
onPress={() => {
|
||||||
|
Alert.alert(
|
||||||
|
'ARIA hart neu starten?',
|
||||||
|
'Container-Restart (~15s). Laufende Anfragen gehen verloren.',
|
||||||
|
[
|
||||||
|
{ text: 'Abbrechen', style: 'cancel' },
|
||||||
|
{ text: 'Neu starten', style: 'destructive', onPress: () => {
|
||||||
|
rvs.send('aria_restart' as any, {});
|
||||||
|
ToastAndroid.show('Container-Restart angestossen…', ToastAndroid.LONG);
|
||||||
|
}},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={[styles.clearButtonText, {color: '#FF3B30'}]}>{'🚨 ARIA hart neu starten'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
</>)}
|
</>)}
|
||||||
|
|||||||
@@ -1580,6 +1580,32 @@ class ARIABridge:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("[rvs] file_saved konnte nicht an App gesendet werden: %s", e)
|
logger.warning("[rvs] file_saved konnte nicht an App gesendet werden: %s", e)
|
||||||
|
|
||||||
|
elif msg_type == "aria_restart":
|
||||||
|
# App-Button "ARIA hart neu starten" → docker restart aria-core
|
||||||
|
# via Diagnostic (der hat den Docker-Socket gemountet).
|
||||||
|
logger.warning("[rvs] aria_restart Request von App — harter Container-Restart")
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
"http://localhost:3001/api/aria-restart",
|
||||||
|
data=b"{}",
|
||||||
|
method="POST",
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
def _do_restart():
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=45) 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_restart)
|
||||||
|
logger.info("[rvs] aria_restart Result: status=%s", status)
|
||||||
|
# Note: bei erfolgreichem Restart ist die RVS-Verbindung sehr
|
||||||
|
# wahrscheinlich kurz weg (aria-bridge ist im service:aria-Network).
|
||||||
|
# Die Antwort kommt evtl. nicht mehr durch — egal.
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("[rvs] aria_restart Weiterleitung fehlgeschlagen: %s", e)
|
||||||
|
return
|
||||||
|
|
||||||
elif msg_type == "doctor_fix":
|
elif msg_type == "doctor_fix":
|
||||||
# App-Button "ARIA reparieren" → openclaw doctor --fix anstossen.
|
# App-Button "ARIA reparieren" → openclaw doctor --fix anstossen.
|
||||||
# Bridge erreicht aria-core nicht via docker (kein docker-socket
|
# Bridge erreicht aria-core nicht via docker (kein docker-socket
|
||||||
|
|||||||
@@ -290,6 +290,7 @@
|
|||||||
<span><span style="animation:pulse 1s infinite;">💭</span> <span id="thinking-text">ARIA denkt...</span></span>
|
<span><span style="animation:pulse 1s infinite;">💭</span> <span id="thinking-text">ARIA denkt...</span></span>
|
||||||
<div style="display:flex;gap:6px;">
|
<div style="display:flex;gap:6px;">
|
||||||
<button class="btn secondary" onclick="doctorFix()" style="padding:2px 10px;font-size:11px;color:#FF9500;border-color:#FF9500;" title="ARIA reparieren — openclaw doctor --fix">🔧 Reparieren</button>
|
<button class="btn secondary" onclick="doctorFix()" style="padding:2px 10px;font-size:11px;color:#FF9500;border-color:#FF9500;" title="ARIA reparieren — openclaw doctor --fix">🔧 Reparieren</button>
|
||||||
|
<button class="btn secondary" onclick="ariaRestart()" style="padding:2px 10px;font-size:11px;color:#FF3B30;border-color:#FF3B30;" title="Container hart neu starten (~15s)">🚨 Hart neu</button>
|
||||||
<button class="btn secondary" onclick="cancelRequest()" style="padding:2px 10px;font-size:11px;color:#FF3B30;border-color:#FF3B30;">Abbrechen</button>
|
<button class="btn secondary" onclick="cancelRequest()" style="padding:2px 10px;font-size:11px;color:#FF3B30;border-color:#FF3B30;">Abbrechen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1875,6 +1876,21 @@
|
|||||||
.catch(err => addLog('error', 'server', 'Reparatur Request fehlgeschlagen: ' + err.message));
|
.catch(err => addLog('error', 'server', 'Reparatur Request fehlgeschlagen: ' + err.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Hard-Restart — docker restart aria-core ──────
|
||||||
|
function ariaRestart() {
|
||||||
|
if (!confirm('ARIA wird hart neu gestartet (Container-Restart, ~15s).\n\nLaufende Anfragen gehen verloren. Sicher?')) return;
|
||||||
|
fetch('/api/aria-restart', { method: 'POST' })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.ok) {
|
||||||
|
addLog('info', 'server', 'ARIA neu gestartet — wartet auf Reconnect');
|
||||||
|
} else {
|
||||||
|
addLog('error', 'server', 'Restart fehlgeschlagen: ' + (data.error || ''));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => addLog('error', 'server', 'Restart Request fehlgeschlagen: ' + err.message));
|
||||||
|
}
|
||||||
|
|
||||||
// ── Abbrechen ──────────────────────────────
|
// ── Abbrechen ──────────────────────────────
|
||||||
function cancelRequest() {
|
function cancelRequest() {
|
||||||
send({ action: 'cancel_request' });
|
send({ action: 'cancel_request' });
|
||||||
|
|||||||
@@ -1358,6 +1358,27 @@ const server = http.createServer((req, res) => {
|
|||||||
res.end(JSON.stringify({ ok: false, error: err.message }));
|
res.end(JSON.stringify({ ok: false, error: err.message }));
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
} else if (req.url === "/api/aria-restart" && req.method === "POST") {
|
||||||
|
// Harter Restart — fuer Faelle wo doctor --fix nicht reicht (alive aber
|
||||||
|
// haengender Run). docker restart killt PID 1 vom Container, alle Locks
|
||||||
|
// weg, neuer State. Dauert ~10-20s bis ARIA wieder antwortet.
|
||||||
|
log("warn", "server", "HTTP /api/aria-restart — harter Container-Restart");
|
||||||
|
broadcast({ type: "watchdog", status: "fixing", message: "ARIA wird hart neu gestartet (~15s)" });
|
||||||
|
const exec = require("child_process").exec;
|
||||||
|
exec("docker restart aria-core", { timeout: 30000 }, (err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
log("error", "server", `aria-restart fehlgeschlagen: ${err.message}`);
|
||||||
|
broadcast({ type: "watchdog", status: "error", message: `Restart fehlgeschlagen: ${err.message}` });
|
||||||
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ ok: false, error: err.message }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("info", "server", `aria-restart OK: ${(stdout || "").trim()}`);
|
||||||
|
broadcast({ type: "watchdog", status: "fixed", message: "ARIA wurde neu gestartet — sollte gleich antworten" });
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ ok: true, output: stdout }));
|
||||||
|
});
|
||||||
|
return;
|
||||||
} else if (req.url.startsWith("/shared/")) {
|
} else if (req.url.startsWith("/shared/")) {
|
||||||
// Dateien aus Shared Volume ausliefern (Bilder, Uploads)
|
// Dateien aus Shared Volume ausliefern (Bilder, Uploads)
|
||||||
const filePath = decodeURIComponent(req.url);
|
const filePath = decodeURIComponent(req.url);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const ALLOWED_TYPES = new Set([
|
|||||||
"audio_pcm",
|
"audio_pcm",
|
||||||
"file_from_aria",
|
"file_from_aria",
|
||||||
"doctor_fix",
|
"doctor_fix",
|
||||||
|
"aria_restart",
|
||||||
"xtts_delete_voice",
|
"xtts_delete_voice",
|
||||||
"voice_preload", "voice_ready",
|
"voice_preload", "voice_ready",
|
||||||
"stt_request", "stt_response",
|
"stt_request", "stt_response",
|
||||||
|
|||||||
Reference in New Issue
Block a user