Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 53b49eacad | |||
| 0f11d23c75 | |||
| 311030bdaa | |||
| 1e05c66baa | |||
| 4082a6bf2a | |||
| 3485642b3e |
@@ -79,8 +79,8 @@ android {
|
|||||||
applicationId "com.ariacockpit"
|
applicationId "com.ariacockpit"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 10105
|
versionCode 10107
|
||||||
versionName "0.1.1.5"
|
versionName "0.1.1.7"
|
||||||
// Fallback fuer Libraries mit Product Flavors
|
// Fallback fuer Libraries mit Product Flavors
|
||||||
missingDimensionStrategy 'react-native-camera', 'general'
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aria-cockpit",
|
"name": "aria-cockpit",
|
||||||
"version": "0.1.1.5",
|
"version": "0.1.1.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -1329,6 +1329,31 @@ const SettingsScreen: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Text style={[styles.clearButtonText, {color: '#FF3B30'}]}>{'🚨 ARIA hart neu starten'}</Text>
|
<Text style={[styles.clearButtonText, {color: '#FF3B30'}]}>{'🚨 ARIA hart neu starten'}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
<Text style={[styles.toggleHint, {marginTop: 12}]}>
|
||||||
|
Konversation komplett zuruecksetzen — alle bisherigen Nachrichten
|
||||||
|
aus ARIA's Session loeschen + Container neu. Anders als der harte
|
||||||
|
Restart wird hier auch ARIA's Erinnerung an die laufende
|
||||||
|
Konversation gewipt. Geschieht automatisch alle 140 Nachrichten
|
||||||
|
(Bridge-Setting COMPACT_AFTER_MESSAGES).
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.clearButton, {marginTop: 8, backgroundColor: 'rgba(255,149,0,0.15)'}]}
|
||||||
|
onPress={() => {
|
||||||
|
Alert.alert(
|
||||||
|
'Konversation komprimieren?',
|
||||||
|
'Alle Nachrichten in ARIAs aktueller Session werden geloescht und der Container neu gestartet. ARIA vergisst den bisherigen Gespraechsverlauf.',
|
||||||
|
[
|
||||||
|
{ text: 'Abbrechen', style: 'cancel' },
|
||||||
|
{ text: 'Komprimieren', style: 'destructive', onPress: () => {
|
||||||
|
rvs.send('aria_session_reset' as any, {});
|
||||||
|
ToastAndroid.show('Session wird zurueckgesetzt…', ToastAndroid.LONG);
|
||||||
|
}},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={[styles.clearButtonText, {color: '#FF9500'}]}>{'🧹 Konversation komprimieren'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
</>)}
|
</>)}
|
||||||
|
|||||||
@@ -114,6 +114,14 @@ OHNE diesen Marker erscheint die Datei NICHT in der App / Diagnostic.
|
|||||||
Mehrere Dateien: mehrere `[FILE: ...]`-Marker am Ende, jeder in
|
Mehrere Dateien: mehrere `[FILE: ...]`-Marker am Ende, jeder in
|
||||||
eigener Zeile.
|
eigener Zeile.
|
||||||
|
|
||||||
|
**WICHTIG — Datei MUSS existieren bevor du den Marker setzt.**
|
||||||
|
Marker fuer nicht-existente Pfade werden silent gefiltert + Stefan
|
||||||
|
bekommt einen Hinweis dass du eine Datei versprochen aber nicht
|
||||||
|
erstellt hast. Wenn du z.B. eine MIDI-Datei nicht generieren kannst,
|
||||||
|
sag das offen statt nur den Marker zu setzen. Verifiziere zur Not
|
||||||
|
mit `Bash` + `ls -la /shared/uploads/aria_<name>.<ext>` dass die
|
||||||
|
Datei wirklich da ist.
|
||||||
|
|
||||||
### Beispiel — kompletter Workflow
|
### Beispiel — kompletter Workflow
|
||||||
|
|
||||||
User: "Schreib mir ein Lasagne-Rezept als md-Datei"
|
User: "Schreib mir ein Lasagne-Rezept als md-Datei"
|
||||||
|
|||||||
+69
-4
@@ -549,6 +549,12 @@ class ARIABridge:
|
|||||||
# Beeinflusst das Timeout fuer stt_request — bei "loading" warten wir laenger,
|
# Beeinflusst das Timeout fuer stt_request — bei "loading" warten wir laenger,
|
||||||
# weil das Modell beim ersten Request noch ~1-2 Min runtergeladen werden kann.
|
# weil das Modell beim ersten Request noch ~1-2 Min runtergeladen werden kann.
|
||||||
self._remote_stt_ready: bool = False
|
self._remote_stt_ready: bool = False
|
||||||
|
# User-Message-Counter fuer Auto-Compact. Bei zu langer Konversation
|
||||||
|
# sprengt die argv-Liste beim Claude-Subprocess-Spawn (E2BIG). Bei
|
||||||
|
# COMPACT_AFTER erreicht → Sessions reset + Container restart.
|
||||||
|
# Counter ueberlebt Bridge-Restart nicht (frischer Zaehler beim Start ok).
|
||||||
|
self._user_message_count: int = 0
|
||||||
|
self._compact_after = int(os.getenv("COMPACT_AFTER_MESSAGES", "140"))
|
||||||
# Pending Files: wenn die App ein Bild + Text gleichzeitig schickt, kommen
|
# Pending Files: wenn die App ein Bild + Text gleichzeitig schickt, kommen
|
||||||
# zwei separate RVS-Events ('file' und 'chat') — wir buffern die Files
|
# zwei separate RVS-Events ('file' und 'chat') — wir buffern die Files
|
||||||
# kurz und mergen sie mit dem nachfolgenden Chat-Text zu einer einzigen
|
# kurz und mergen sie mit dem nachfolgenden Chat-Text zu einer einzigen
|
||||||
@@ -888,9 +894,11 @@ class ARIABridge:
|
|||||||
# enthalten, Endung beliebig). Mehrfach im Text moeglich.
|
# enthalten, Endung beliebig). Mehrfach im Text moeglich.
|
||||||
_FILE_MARKER_RE = re.compile(r"\[FILE:\s*(/shared/uploads/[^\]]+?)\s*\]", re.IGNORECASE)
|
_FILE_MARKER_RE = re.compile(r"\[FILE:\s*(/shared/uploads/[^\]]+?)\s*\]", re.IGNORECASE)
|
||||||
|
|
||||||
def _extract_file_markers(self, text: str) -> tuple[str, list[dict]]:
|
def _extract_file_markers(self, text: str) -> tuple[str, list[dict], list[str]]:
|
||||||
"""Sucht [FILE: /shared/uploads/...]-Marker, gibt (cleaned_text, file_list) zurueck."""
|
"""Sucht [FILE: /shared/uploads/...]-Marker.
|
||||||
|
Returns (cleaned_text, valid_files, missing_paths)."""
|
||||||
files: list[dict] = []
|
files: list[dict] = []
|
||||||
|
missing: list[str] = []
|
||||||
for m in self._FILE_MARKER_RE.finditer(text):
|
for m in self._FILE_MARKER_RE.finditer(text):
|
||||||
path = m.group(1).strip()
|
path = m.group(1).strip()
|
||||||
if not path.startswith("/shared/uploads/"):
|
if not path.startswith("/shared/uploads/"):
|
||||||
@@ -898,6 +906,7 @@ class ARIABridge:
|
|||||||
continue
|
continue
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
logger.warning("[core] FILE-Marker zeigt auf nicht existente Datei: %s", path)
|
logger.warning("[core] FILE-Marker zeigt auf nicht existente Datei: %s", path)
|
||||||
|
missing.append(path)
|
||||||
continue
|
continue
|
||||||
name = os.path.basename(path)
|
name = os.path.basename(path)
|
||||||
mime, _ = mimetypes.guess_type(path)
|
mime, _ = mimetypes.guess_type(path)
|
||||||
@@ -911,7 +920,7 @@ class ARIABridge:
|
|||||||
cleaned = self._FILE_MARKER_RE.sub("", text).strip()
|
cleaned = self._FILE_MARKER_RE.sub("", text).strip()
|
||||||
# Zwei aufeinanderfolgende Leerzeilen → eine
|
# Zwei aufeinanderfolgende Leerzeilen → eine
|
||||||
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
|
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
|
||||||
return cleaned, files
|
return cleaned, files, missing
|
||||||
|
|
||||||
async def _broadcast_aria_file(self, file_info: dict) -> None:
|
async def _broadcast_aria_file(self, file_info: dict) -> None:
|
||||||
"""ARIA hat eine Datei fuer den User erstellt — App+Diagnostic informieren."""
|
"""ARIA hat eine Datei fuer den User erstellt — App+Diagnostic informieren."""
|
||||||
@@ -944,9 +953,17 @@ class ARIABridge:
|
|||||||
# ARIA legt damit Dateien fuer den User bereit (Bilder, PDFs, etc.).
|
# ARIA legt damit Dateien fuer den User bereit (Bilder, PDFs, etc.).
|
||||||
# Der Marker wird aus dem Antworttext entfernt (TTS soll ihn nicht
|
# Der Marker wird aus dem Antworttext entfernt (TTS soll ihn nicht
|
||||||
# vorlesen) und parallel als file_from_aria-Event geschickt.
|
# vorlesen) und parallel als file_from_aria-Event geschickt.
|
||||||
text, aria_files = self._extract_file_markers(text)
|
text, aria_files, missing_files = self._extract_file_markers(text)
|
||||||
for f in aria_files:
|
for f in aria_files:
|
||||||
await self._broadcast_aria_file(f)
|
await self._broadcast_aria_file(f)
|
||||||
|
# Bei fehlenden Files: User informieren (sonst sieht er nur stille
|
||||||
|
# Verluste — ARIA hat den Marker hingeschrieben aber das File nicht
|
||||||
|
# tatsaechlich angelegt).
|
||||||
|
if missing_files:
|
||||||
|
missing_list = "\n".join(f" • {os.path.basename(p)}" for p in missing_files)
|
||||||
|
text = (text + "\n\n[Hinweis] Folgende Dateien hat ARIA zwar erwaehnt "
|
||||||
|
f"aber nicht erstellt:\n{missing_list}\n"
|
||||||
|
"Bitte ARIA bitten, sie wirklich zu schreiben.").strip()
|
||||||
|
|
||||||
metadata = payload.get("metadata", {})
|
metadata = payload.get("metadata", {})
|
||||||
is_critical = metadata.get("critical", False)
|
is_critical = metadata.get("critical", False)
|
||||||
@@ -1170,12 +1187,53 @@ class ARIABridge:
|
|||||||
await self.send_to_core(text, source="app-file+chat")
|
await self.send_to_core(text, source="app-file+chat")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def _trigger_session_reset(self) -> None:
|
||||||
|
"""Sessions loeschen + Container restart via Diagnostic HTTP-API."""
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
"http://localhost:3001/api/aria-session-reset",
|
||||||
|
data=b"{}",
|
||||||
|
method="POST",
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
def _do_reset():
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=45) as resp:
|
||||||
|
return resp.status
|
||||||
|
except Exception as e:
|
||||||
|
return f"err:{e}"
|
||||||
|
result = await asyncio.get_event_loop().run_in_executor(None, _do_reset)
|
||||||
|
logger.info("[core] Session-Reset Result: %s", result)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("[core] Session-Reset Trigger fehlgeschlagen: %s", e)
|
||||||
|
|
||||||
async def send_to_core(self, text: str, source: str = "bridge") -> None:
|
async def send_to_core(self, text: str, source: str = "bridge") -> None:
|
||||||
"""Sendet Text an aria-core (OpenClaw chat.send Protokoll)."""
|
"""Sendet Text an aria-core (OpenClaw chat.send Protokoll)."""
|
||||||
if self.ws_core is None:
|
if self.ws_core is None:
|
||||||
logger.error("[core] Nicht verbunden — Nachricht verworfen: '%s'", text[:60])
|
logger.error("[core] Nicht verbunden — Nachricht verworfen: '%s'", text[:60])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Auto-Compact: bei zu vielen User-Messages laeuft argv beim Subprocess-
|
||||||
|
# Spawn ueber (E2BIG). Vor send pruefen, ggf. Sessions resetten.
|
||||||
|
if source.startswith("app") and self._compact_after > 0:
|
||||||
|
self._user_message_count += 1
|
||||||
|
if self._user_message_count >= self._compact_after:
|
||||||
|
logger.warning("[core] Auto-Compact: %d Messages erreicht — Session-Reset",
|
||||||
|
self._user_message_count)
|
||||||
|
self._user_message_count = 0
|
||||||
|
# Reset triggern via Diagnostic (asynchron, blockiert send nicht)
|
||||||
|
asyncio.create_task(self._trigger_session_reset())
|
||||||
|
# User informieren — der naechste Request kommt erst nach Restart durch
|
||||||
|
await self._send_to_rvs({
|
||||||
|
"type": "chat",
|
||||||
|
"payload": {
|
||||||
|
"text": f"[Compact] Konversation war lang ({self._compact_after} Nachrichten) — Session wurde geleert, ARIA startet frisch. Deine letzte Nachricht bitte gleich nochmal senden.",
|
||||||
|
"sender": "aria",
|
||||||
|
},
|
||||||
|
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
# Aktive Session vom Diagnostic holen
|
# Aktive Session vom Diagnostic holen
|
||||||
self._fetch_active_session()
|
self._fetch_active_session()
|
||||||
|
|
||||||
@@ -1580,6 +1638,13 @@ 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_session_reset":
|
||||||
|
# Manueller Compact-Trigger: Sessions weg + Restart
|
||||||
|
logger.warning("[rvs] aria_session_reset Request von App")
|
||||||
|
self._user_message_count = 0
|
||||||
|
asyncio.create_task(self._trigger_session_reset())
|
||||||
|
return
|
||||||
|
|
||||||
elif msg_type == "aria_restart":
|
elif msg_type == "aria_restart":
|
||||||
# App-Button "ARIA hart neu starten" → docker restart aria-core
|
# App-Button "ARIA hart neu starten" → docker restart aria-core
|
||||||
# via Diagnostic (der hat den Docker-Socket gemountet).
|
# via Diagnostic (der hat den Docker-Socket gemountet).
|
||||||
|
|||||||
+18
-6
@@ -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="ariaSessionReset()" style="padding:2px 10px;font-size:11px;color:#FF9500;border-color:#FF9500;" title="Konversation komprimieren — Sessions weg + Restart">🧹 Compact</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="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>
|
||||||
@@ -997,12 +998,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.type === 'chat_final') {
|
if (msg.type === 'chat_final') {
|
||||||
// [FILE: /shared/uploads/aria_xxx.ext]-Marker aus dem Antworttext
|
addChat('received', msg.text || '', 'chat:final');
|
||||||
// entfernen — die Datei kommt separat via file_from_aria.
|
|
||||||
// (Diagnostic empfaengt chat_final direkt vom Gateway, Bridge
|
|
||||||
// hat darum nicht filtern koennen.)
|
|
||||||
const cleaned = (msg.text || '').replace(/\[FILE:\s*\/shared\/uploads\/[^\]]+\]/gi, '').replace(/\n{3,}/g, '\n\n').trim();
|
|
||||||
addChat('received', cleaned, 'chat:final');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg.type === 'file_from_aria') {
|
if (msg.type === 'file_from_aria') {
|
||||||
@@ -1448,6 +1444,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addChat(type, text, meta, options) {
|
function addChat(type, text, meta, options) {
|
||||||
|
// [FILE: /shared/uploads/aria_xxx.ext]-Marker aus dem Antworttext entfernen —
|
||||||
|
// die Datei kommt separat via file_from_aria-Event als eigene Bubble.
|
||||||
|
// /gi entfernt mehrere Marker, falls ARIA mehrere Dateien in einer Antwort liefert.
|
||||||
|
if (text) text = text.replace(/\[FILE:\s*\/shared\/uploads\/[^\]]+\]/gi, '').replace(/\n{3,}/g, '\n\n').trim();
|
||||||
const escaped = escapeHtml(text);
|
const escaped = escapeHtml(text);
|
||||||
let linked = linkifyText(escaped);
|
let linked = linkifyText(escaped);
|
||||||
// /shared/uploads/ Pfade als Inline-Bilder anzeigen
|
// /shared/uploads/ Pfade als Inline-Bilder anzeigen
|
||||||
@@ -1891,6 +1891,18 @@
|
|||||||
.catch(err => addLog('error', 'server', 'Restart Request fehlgeschlagen: ' + err.message));
|
.catch(err => addLog('error', 'server', 'Restart Request fehlgeschlagen: ' + err.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Compact / Session-Reset ──────
|
||||||
|
function ariaSessionReset() {
|
||||||
|
if (!confirm('Konversation komprimieren: alle Nachrichten in ARIAs aktueller Session werden geloescht und der Container neu gestartet. ARIA vergisst den bisherigen Gespraechsverlauf. Sicher?')) return;
|
||||||
|
fetch('/api/aria-session-reset', { method: 'POST' })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.ok) addLog('info', 'server', 'Session geleert, ARIA neu gestartet');
|
||||||
|
else addLog('error', 'server', 'Reset fehlgeschlagen: ' + (data.error || ''));
|
||||||
|
})
|
||||||
|
.catch(err => addLog('error', 'server', 'Reset Request fehlgeschlagen: ' + err.message));
|
||||||
|
}
|
||||||
|
|
||||||
// ── Abbrechen ──────────────────────────────
|
// ── Abbrechen ──────────────────────────────
|
||||||
function cancelRequest() {
|
function cancelRequest() {
|
||||||
send({ action: 'cancel_request' });
|
send({ action: 'cancel_request' });
|
||||||
|
|||||||
+71
-17
@@ -1358,26 +1358,80 @@ 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") {
|
} else if (req.url === "/api/aria-session-reset" && req.method === "POST") {
|
||||||
// Harter Restart — fuer Faelle wo doctor --fix nicht reicht (alive aber
|
// Sessions weg + Container neu — fuer Compact-After-N-Messages.
|
||||||
// haengender Run). docker restart killt PID 1 vom Container, alle Locks
|
// E2BIG bei zu langen Sessions: argv beim Subprocess-spawn ueberschritten.
|
||||||
// weg, neuer State. Dauert ~10-20s bis ARIA wieder antwortet.
|
log("warn", "server", "HTTP /api/aria-session-reset — Sessions loeschen + Restart");
|
||||||
log("warn", "server", "HTTP /api/aria-restart — harter Container-Restart");
|
broadcast({ type: "watchdog", status: "fixing", message: "Sessions werden geleert — ARIA bekommt frischen Start" });
|
||||||
broadcast({ type: "watchdog", status: "fixing", message: "ARIA wird hart neu gestartet (~15s)" });
|
dockerExec("aria-core", "rm -f /home/node/.openclaw/agents/main/sessions/*.jsonl /home/node/.openclaw/agents/main/sessions/*.lock 2>&1 && echo '{}' > /home/node/.openclaw/agents/main/sessions/sessions.json")
|
||||||
const exec = require("child_process").exec;
|
.then(() => {
|
||||||
exec("docker restart aria-core", { timeout: 30000 }, (err, stdout, stderr) => {
|
// Restart via Docker-API (gleicher Pfad wie /api/aria-restart)
|
||||||
if (err) {
|
const restartReq = http.request({
|
||||||
log("error", "server", `aria-restart fehlgeschlagen: ${err.message}`);
|
socketPath: "/var/run/docker.sock",
|
||||||
broadcast({ type: "watchdog", status: "error", message: `Restart fehlgeschlagen: ${err.message}` });
|
path: "/containers/aria-core/restart?t=10",
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Length": 0 },
|
||||||
|
timeout: 30000,
|
||||||
|
}, (dRes) => {
|
||||||
|
if (dRes.statusCode === 204) {
|
||||||
|
log("info", "server", "aria-session-reset OK");
|
||||||
|
broadcast({ type: "watchdog", status: "fixed", message: "Sessions geleert, ARIA neu gestartet" });
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ ok: true }));
|
||||||
|
} else {
|
||||||
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ ok: false, error: `Docker-API ${dRes.statusCode}` }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
restartReq.on("error", (err) => {
|
||||||
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ ok: false, error: err.message }));
|
||||||
|
});
|
||||||
|
restartReq.end();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
log("error", "server", `aria-session-reset Cleanup fehlgeschlagen: ${err.message}`);
|
||||||
res.writeHead(500, { "Content-Type": "application/json" });
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
res.end(JSON.stringify({ ok: false, error: err.message }));
|
res.end(JSON.stringify({ ok: false, error: err.message }));
|
||||||
return;
|
});
|
||||||
}
|
return;
|
||||||
log("info", "server", `aria-restart OK: ${(stdout || "").trim()}`);
|
} else if (req.url === "/api/aria-restart" && req.method === "POST") {
|
||||||
broadcast({ type: "watchdog", status: "fixed", message: "ARIA wurde neu gestartet — sollte gleich antworten" });
|
// Harter Restart — fuer Faelle wo doctor --fix nicht reicht (alive aber
|
||||||
res.writeHead(200, { "Content-Type": "application/json" });
|
// haengender Run). Geht ueber Docker-API (Socket), kein CLI noetig.
|
||||||
res.end(JSON.stringify({ ok: true, output: stdout }));
|
// POST /containers/aria-core/restart?t=10 → SIGTERM, dann nach 10s SIGKILL.
|
||||||
|
log("warn", "server", "HTTP /api/aria-restart — harter Container-Restart");
|
||||||
|
broadcast({ type: "watchdog", status: "fixing", message: "ARIA wird hart neu gestartet (~15s)" });
|
||||||
|
const restartReq = http.request({
|
||||||
|
socketPath: "/var/run/docker.sock",
|
||||||
|
path: "/containers/aria-core/restart?t=10",
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Length": 0 },
|
||||||
|
timeout: 30000,
|
||||||
|
}, (dRes) => {
|
||||||
|
let body = "";
|
||||||
|
dRes.on("data", (c) => body += c);
|
||||||
|
dRes.on("end", () => {
|
||||||
|
// Docker-API: 204 = OK, 404 = container nicht da, 500 = anderer Fehler
|
||||||
|
if (dRes.statusCode === 204) {
|
||||||
|
log("info", "server", "aria-restart OK (Docker-API)");
|
||||||
|
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 }));
|
||||||
|
} else {
|
||||||
|
log("error", "server", `aria-restart Docker-API ${dRes.statusCode}: ${body.slice(0, 200)}`);
|
||||||
|
broadcast({ type: "watchdog", status: "error", message: `Restart fehlgeschlagen: HTTP ${dRes.statusCode}` });
|
||||||
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ ok: false, error: `Docker-API ${dRes.statusCode}: ${body}` }));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
restartReq.on("error", (err) => {
|
||||||
|
log("error", "server", `aria-restart Socket-Fehler: ${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 }));
|
||||||
|
});
|
||||||
|
restartReq.end();
|
||||||
return;
|
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)
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ services:
|
|||||||
- RVS_TLS=${RVS_TLS:-true}
|
- RVS_TLS=${RVS_TLS:-true}
|
||||||
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
|
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
|
||||||
- RVS_TOKEN=${RVS_TOKEN:-}
|
- RVS_TOKEN=${RVS_TOKEN:-}
|
||||||
|
- COMPACT_AFTER_MESSAGES=${COMPACT_AFTER_MESSAGES:-140}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# ─── Diagnostic (Selbstcheck-UI und Einstellungen) ────
|
# ─── Diagnostic (Selbstcheck-UI und Einstellungen) ────
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const ALLOWED_TYPES = new Set([
|
|||||||
"file_from_aria",
|
"file_from_aria",
|
||||||
"doctor_fix",
|
"doctor_fix",
|
||||||
"aria_restart",
|
"aria_restart",
|
||||||
|
"aria_session_reset",
|
||||||
"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