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:
@@ -123,6 +123,67 @@ META_TOOLS = [
|
||||
"parameters": {"type": "object", "properties": {}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "skill_update",
|
||||
"description": (
|
||||
"Aktualisiere einen EXISTIERENDEN Skill statt eine zweite Version "
|
||||
"mit `-v2`/`-new`/`-fixed` Suffix anzulegen. Stefan hasst Skill-"
|
||||
"Friedhoefe. Wenn Du `youtube2mp3` umbauen sollst → `skill_update` "
|
||||
"auf den bestehenden, NICHT `skill_create` mit neuem Namen.\n\n"
|
||||
"Du kannst gleichzeitig `entry_code` (Python-Code austauschen), "
|
||||
"`readme`, `pip_packages` (bei Aenderung wird die venv automatisch "
|
||||
"neu aufgebaut), `args`, `description` und `active` setzen. Felder "
|
||||
"die Du weglaesst bleiben unberuehrt.\n\n"
|
||||
"WENN Du Dir bei einem grundlegenden API-Bruch unsicher bist ob "
|
||||
"der Skill noch zum Namen passt: lieber `skill_delete` + "
|
||||
"`skill_create` mit neuem semantischen Namen statt eines "
|
||||
"halbgaren Updates."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "Bestehender Skill-Name"},
|
||||
"entry_code": {"type": "string", "description": "Neuer Python-Code (optional)"},
|
||||
"readme": {"type": "string", "description": "Neuer README-Inhalt (optional)"},
|
||||
"pip_packages": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Neue pip-Pakete (ueberschreibt komplette Liste; triggert venv-Rebuild)",
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"items": {"type": "object"},
|
||||
"description": "Neues Args-Schema (optional)",
|
||||
},
|
||||
"description": {"type": "string", "description": "Neue Beschreibung (optional)"},
|
||||
"active": {"type": "boolean", "description": "Aktivieren/deaktivieren (optional)"},
|
||||
},
|
||||
"required": ["name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "skill_delete",
|
||||
"description": (
|
||||
"Loescht einen Skill samt venv und Logs. Nutze das wenn:\n"
|
||||
"1. Stefan explizit sagt der Skill soll weg\n"
|
||||
"2. Du eine alte Skill-Version losgeworden bist nachdem `skill_create` "
|
||||
"mit besserem Namen erfolgreich war (Aufraeumen statt Skill-Friedhof)\n"
|
||||
"3. Ein Skill grundlegend kaputt und ein Update sich nicht mehr lohnt — "
|
||||
"in dem Fall bestaetige vorher kurz bei Stefan.\n\n"
|
||||
"Nicht rueckholbar."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"name": {"type": "string"}},
|
||||
"required": ["name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -746,6 +807,46 @@ class Agent:
|
||||
f"- {s['name']} ({s['execution']}) {'aktiv' if s.get('active', True) else 'DEAKTIVIERT'}: {s.get('description', '')}"
|
||||
for s in items
|
||||
)
|
||||
if name == "skill_update":
|
||||
skill_name = (arguments.get("name") or "").strip()
|
||||
if not skill_name:
|
||||
return "FEHLER: name ist Pflicht."
|
||||
patch: dict = {}
|
||||
for k in ("entry_code", "readme", "description", "args", "active"):
|
||||
if k in arguments and arguments[k] is not None:
|
||||
patch[k] = arguments[k]
|
||||
if "pip_packages" in arguments and isinstance(arguments["pip_packages"], list):
|
||||
patch["pip_packages"] = arguments["pip_packages"]
|
||||
if not patch:
|
||||
return "FEHLER: keine Felder zum Update angegeben."
|
||||
try:
|
||||
manifest = skills_mod.update_skill(skill_name, patch)
|
||||
except ValueError as exc:
|
||||
return f"FEHLER: {exc}"
|
||||
# Side-Channel-Event als skill_created getarnt — gleiche Bubble-Mechanik
|
||||
# in App/Diagnostic; das Update soll fuer Stefan ebenfalls sichtbar werden.
|
||||
self._pending_events.append({
|
||||
"type": "skill_created",
|
||||
"skill": {
|
||||
"name": manifest["name"],
|
||||
"description": manifest.get("description", ""),
|
||||
"execution": manifest.get("execution", ""),
|
||||
"active": manifest.get("active", True),
|
||||
"setup_error": manifest.get("setup_error"),
|
||||
"updated": True,
|
||||
},
|
||||
})
|
||||
changed = ", ".join(sorted(patch.keys()))
|
||||
return f"OK — Skill '{skill_name}' aktualisiert ({changed}). active={manifest['active']}"
|
||||
if name == "skill_delete":
|
||||
skill_name = (arguments.get("name") or "").strip()
|
||||
if not skill_name:
|
||||
return "FEHLER: name ist Pflicht."
|
||||
try:
|
||||
skills_mod.delete_skill(skill_name)
|
||||
except ValueError as exc:
|
||||
return f"FEHLER: {exc}"
|
||||
return f"OK — Skill '{skill_name}' geloescht."
|
||||
if name.startswith("run_"):
|
||||
skill_name = name[len("run_"):]
|
||||
res = skills_mod.run_skill(skill_name, args=arguments)
|
||||
|
||||
Reference in New Issue
Block a user