feat(app+brain): App-Bugfixes + Skill-Mgmt-Tools + Voice-Speed persistent + Skill-Browser

App-Bugs:
- Trigger-Liste war leer: brainApi.listTriggers() cast'te {triggers: [...]}
  direkt als Array, t.sort() warf — TriggerBrowser blieb leer. Fix: unwrap.
- GPS-Tracking startete erst bei SettingsScreen-Mount, nicht beim App-Boot.
  Wenn Stefan direkt in den Chat ging, blieb GPS aus. Fix: restoreFromStorage()
  in App.tsx useEffect.
- Text in Chat-Bubbles nicht markierbar / kein Copy-Mechanismus: Bubble jetzt
  Pressable mit onLongPress + neues ⎘-Icon in Status-Row → openBubbleActions().
  Alert-Menu mit "Ganzen Text teilen" + pro extrahierte URL/Mail/Tel eine
  eigene Option. Share.share() — keine neuen Native-Deps noetig.

Brain — Skill-Mgmt:
- ARIA legte beim Skill-Umbau neue Versionen mit Suffix an (Skill-Friedhof),
  weil sie kein Update/Delete-Tool kannte. Zwei neue META_TOOLS in agent.py:
  skill_update (kann entry_code, readme, pip_packages, args, description,
  active patchen — venv wird bei pip_packages-Aenderung rebuilt) + skill_delete.
- skills.py update_skill um entry_code/readme/pip_packages erweitert,
  venv-Rebuild bei pip-Aenderung.

Bridge — Voice-Speed persistent:
- _next_speed_override war pro-Request-Override ohne Persistenz. Bei
  Diagnostic-Chats / Trigger-Replies ohne vorherigen App-Chat fiel der Speed
  auf 1.0 zurueck, ebenso nach Bridge-Restart. Jetzt: _persistent_xtts_speed
  aus voice_config.json (xttsSpeed), wird nach jedem App-chat mit speed
  autopersistiert. TTS-Generation faellt zurueck: per-Request > persistent > 1.0.

App — Feature 6:
- SkillBrowser.tsx: Liste aller Skills, Toggle aktiv/inaktiv, Detail-Modal
  mit Args-Inputs, Ausfuehren mit Live-stdout/stderr, Logs der letzten 20
  Runs, Loeschen. Settings-Sektion "Skills" (🛠️) zwischen Trigger und
  Protokoll. brainApi.listSkills/getSkill/runSkill/updateSkill/deleteSkill/
  getSkillLogs ergaenzt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 17:24:03 +02:00
parent 9ed9c99b0e
commit 30c1dd7473
8 changed files with 862 additions and 8 deletions
+72 -3
View File
@@ -519,6 +519,10 @@ class ARIABridge:
self.xtts_voice = ""
self._f5tts_config: dict = {}
self._flux_config: dict = {}
# Persistente TTS-Speed (App-Setting), wird aus voice_config.json
# gelesen + bei config-Broadcasts (siehe handle config in chat)
# geupdated. Fallback wenn der Per-Request-Override fehlt.
self._persistent_xtts_speed: Optional[float] = None
vc: dict = {}
# Gespeicherte Voice-Config laden
try:
@@ -528,6 +532,19 @@ class ARIABridge:
vc = json.load(f)
self.tts_enabled = vc.get("ttsEnabled", True)
self.xtts_voice = vc.get("xttsVoice", "")
# Persistente TTS-Speed: vorher war's nur per-Chat-Override
# (App schickte speed mit jeder Nachricht). Bei Diagnostic-Chat
# OHNE App-Vor-Chat blieb _next_speed_override=None → 1.0.
# Jetzt persistent — Bridge greift bei TTS immer auf den
# zuletzt von der App gesetzten Wert zurueck.
try:
persisted_speed = float(vc.get("xttsSpeed", 1.0))
if 0.1 <= persisted_speed <= 5.0:
self._persistent_xtts_speed: Optional[float] = persisted_speed
else:
self._persistent_xtts_speed = None
except (TypeError, ValueError):
self._persistent_xtts_speed = None
# F5-TTS-Felder aufsammeln (werden spaeter via RVS rebroadcastet,
# damit die f5tts-bridge auf der Gamebox die Settings auch nach
# Restart wiederbekommt — sonst stuende sie auf Hard-Defaults)
@@ -1185,7 +1202,16 @@ class ARIABridge:
# TTS-Call wieder die alte Default-Stimme. Der Override bleibt gueltig bis
# zum naechsten chat-Event, wo er entweder ueberschrieben oder geloescht wird.
xtts_voice = self._next_voice_override or getattr(self, 'xtts_voice', '')
xtts_speed = self._next_speed_override or 1.0
# Speed-Reihenfolge: Per-Request-Override (App schickte gerade) >
# persistierter App-Setting (voice_config.json xttsSpeed) > 1.0 default.
# Damit greift die App-Speed auch bei Diagnostic-Chats / Trigger-
# Replies / Bridge-Restart, ohne dass die App vorher noch mal getippt
# haben muss.
xtts_speed = (
self._next_speed_override
or getattr(self, "_persistent_xtts_speed", None)
or 1.0
)
tts_text = tts_text_preview or text
if not tts_text:
@@ -1274,6 +1300,8 @@ class ARIABridge:
"xttsVoice": getattr(self, "xtts_voice", ""),
"whisperModel": self.stt_engine.model_size,
}
if getattr(self, "_persistent_xtts_speed", None) is not None:
payload["xttsSpeed"] = self._persistent_xtts_speed
payload.update(getattr(self, "_f5tts_config", {}) or {})
payload.update(getattr(self, "_flux_config", {}) or {})
await self._send_to_rvs({
@@ -1285,6 +1313,24 @@ class ARIABridge:
except Exception as e:
logger.debug("[rvs] Config-Broadcast fehlgeschlagen: %s", e)
async def _persist_speed_change(self, speed: float) -> None:
"""Schreibt nur den xttsSpeed-Eintrag in voice_config.json — der
Rest bleibt unangetastet. Wird gerufen wenn App per chat-Event
einen neuen Speed mitschickt (kein config-Broadcast)."""
try:
path = "/shared/config/voice_config.json"
data: dict = {}
if os.path.exists(path):
with open(path) as f:
data = json.load(f) or {}
data["xttsSpeed"] = speed
os.makedirs("/shared/config", exist_ok=True)
with open(path, "w") as f:
json.dump(data, f, indent=2)
logger.info("[speed] Persistiert: %.2fx", speed)
except Exception as exc:
logger.warning("[speed] Persistierung fehlgeschlagen: %s", exc)
def _fetch_active_session(self) -> None:
"""Holt die aktive Session vom Diagnostic-Endpoint."""
try:
@@ -1732,11 +1778,23 @@ class ARIABridge:
self._next_voice_override = voice_override or None
logger.info("[rvs] Voice fuer Antworten: %s",
self._next_voice_override or "(Default)")
# Speed-Override (TTS-Wiedergabegeschwindigkeit, pro Geraet)
# Speed-Override (TTS-Wiedergabegeschwindigkeit, pro Geraet)
# plus persistente Spiegelung damit der Wert nach Bridge-Restart
# erhalten bleibt und Diagnostic-Chats / Trigger-Replies den
# zuletzt von der App gesetzten Speed bekommen.
if "speed" in payload:
try:
speed = float(payload.get("speed", 0) or 0)
self._next_speed_override = speed if 0.1 <= speed <= 5.0 else None
if 0.1 <= speed <= 5.0:
self._next_speed_override = speed
# Persistieren wenn der Wert sich gegenueber dem
# gespeicherten geaendert hat — vermeidet voice_config.json
# auf jeder Nachricht zu schreiben.
if speed != getattr(self, "_persistent_xtts_speed", None):
self._persistent_xtts_speed = speed
asyncio.create_task(self._persist_speed_change(speed))
else:
self._next_speed_override = None
except (TypeError, ValueError):
self._next_speed_override = None
if text:
@@ -1865,6 +1923,15 @@ class ARIABridge:
self.xtts_voice = payload["xttsVoice"]
logger.info("[rvs] XTTS-Stimme: %s", self.xtts_voice or "default")
changed = True
if "xttsSpeed" in payload:
try:
new_speed = float(payload["xttsSpeed"])
if 0.1 <= new_speed <= 5.0:
self._persistent_xtts_speed = new_speed
logger.info("[rvs] XTTS-Speed (persistent): %.2fx", new_speed)
changed = True
except (TypeError, ValueError):
pass
if "whisperModel" in payload:
new_model = payload["whisperModel"]
allowed = {"tiny", "base", "small", "medium", "large-v3"}
@@ -1900,6 +1967,8 @@ class ARIABridge:
"xttsVoice": getattr(self, "xtts_voice", ""),
"whisperModel": self.stt_engine.model_size,
}
if getattr(self, "_persistent_xtts_speed", None) is not None:
config_data["xttsSpeed"] = self._persistent_xtts_speed
config_data.update(getattr(self, "_f5tts_config", {}))
config_data.update(getattr(self, "_flux_config", {}))
with open("/shared/config/voice_config.json", "w") as f: