feat(skills): P3 config_schema + P4 Versionierung mit Rollback
P3 — Skill-Configuration
- aria-brain/skills.py: SKILL_CONFIGS_FILE (/shared/config/skill_configs.json)
als zentrale Werte-Persistenz. _normalize_config_schema validiert die
Schema-Felder (name/type/label/secret/description/default), CFG_<UPPER_NAME>
ENV beim run_skill. create_skill + update_skill akzeptieren config_schema.
- agent.py: skill_set_config Brain-Tool fuer ARIA. skill_create/update um
config_schema-Property erweitert.
- main.py: GET/POST /skills/{name}/config — secret-Werte in Antwort gemaskt.
P4 — Versionierung mit Rollback
- aria-brain/skills.py: archive_current_version archiviert nach
versions/v_<ts>/ (ohne venv/logs). update_skill ruft das automatisch auf
bevor strukturelle Aenderungen passieren. list_skill_versions,
rollback_skill (mit Safety-Snapshot + automatischem venv-Rebuild),
delete_skill_version.
- agent.py: skill_list_versions, skill_rollback Brain-Tools.
- main.py: GET /skills/{name}/versions, POST /skills/{name}/rollback,
DELETE /skills/{name}/versions/{version_id}.
UI
- diagnostic/index.html: Skill-Detail um Config-Form (typ-spezifisch,
Secrets als password-Input mit ***SET***-Hinweis) und Versions-Liste
mit Rollback-/Delete-Button.
- android SkillBrowser: SkillDetailModal laedt config_schema + versions
on-mount. Config-Form (TextInput + Switch fuer boolean), Versionen mit
Rollback-Confirm. brainApi um SkillConfigField/SkillVersion +
getSkillConfig/setSkillConfig/listSkillVersions/rollbackSkill/
deleteSkillVersion erweitert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -171,11 +171,85 @@ META_TOOLS = [
|
||||
},
|
||||
"description": {"type": "string", "description": "Neue Beschreibung (optional)"},
|
||||
"active": {"type": "boolean", "description": "Aktivieren/deaktivieren (optional)"},
|
||||
"config_schema": {
|
||||
"type": "array",
|
||||
"items": {"type": "object"},
|
||||
"description": (
|
||||
"Optional neues config_schema fuer den Skill. Liste von "
|
||||
"Feldern [{name, type, label, secret?, description?, default?}]. "
|
||||
"type: string|number|boolean|password (password impliziert secret=true). "
|
||||
"Setzt Stefan in Diagnostic; Skill bekommt CFG_<NAME> ENV."
|
||||
),
|
||||
},
|
||||
},
|
||||
"required": ["name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "skill_set_config",
|
||||
"description": (
|
||||
"Setzt Config-Werte fuer einen Skill persistent (z.B. API-Keys, "
|
||||
"User-IDs, Endpoint-URLs). Werte landen als CFG_<UPPER_NAME> ENV "
|
||||
"im naechsten skill_run. Nutze das wenn Stefan dir im Chat einen "
|
||||
"Wert nennt ('mein OpenWeather-Key ist abc123') — schreib den "
|
||||
"NICHT in den Skill-Code, sondern hierher.\n\n"
|
||||
"WICHTIG: values ueberschreibt komplett. Wenn Du nur einen Wert "
|
||||
"aendern willst: erst per Diagnostic-UI oder Skill-Inspect die "
|
||||
"aktuelle Liste ansehen und mit dem neuen Wert ergaenzen."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "Skill-Name"},
|
||||
"values": {
|
||||
"type": "object",
|
||||
"description": "Map config-Feldname → Wert. Felder muessen im config_schema deklariert sein.",
|
||||
},
|
||||
},
|
||||
"required": ["name", "values"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "skill_list_versions",
|
||||
"description": (
|
||||
"Listet archivierte Versionen eines Skills (jeder skill_update "
|
||||
"legt automatisch eine an). Returns [{version_id, archived_at, "
|
||||
"summary}]. Brauchst Du fuer skill_rollback."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"name": {"type": "string"}},
|
||||
"required": ["name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "skill_rollback",
|
||||
"description": (
|
||||
"Stellt eine fruehere Skill-Version wieder her. Vor dem Rollback "
|
||||
"wird der aktuelle Stand automatisch archiviert — du verlierst "
|
||||
"nichts. Nutze das wenn ein skill_update was kaputt gemacht hat "
|
||||
"oder Stefan sagt 'mach den letzten Stand wieder her'. "
|
||||
"version_id bekommst Du aus skill_list_versions."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"version_id": {"type": "string", "description": "Format v_<timestamp>"},
|
||||
},
|
||||
"required": ["name", "version_id"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -856,6 +930,7 @@ class Agent:
|
||||
readme=arguments.get("readme", ""),
|
||||
args=arguments.get("args", []),
|
||||
pip_packages=arguments.get("pip_packages", []),
|
||||
config_schema=arguments.get("config_schema") or None,
|
||||
author="aria",
|
||||
)
|
||||
# Side-Channel-Event: Stefan soll sehen wenn ARIA was anlegt
|
||||
@@ -888,6 +963,8 @@ class Agent:
|
||||
patch[k] = arguments[k]
|
||||
if "pip_packages" in arguments and isinstance(arguments["pip_packages"], list):
|
||||
patch["pip_packages"] = arguments["pip_packages"]
|
||||
if "config_schema" in arguments and isinstance(arguments["config_schema"], list):
|
||||
patch["config_schema"] = arguments["config_schema"]
|
||||
if not patch:
|
||||
return "FEHLER: keine Felder zum Update angegeben."
|
||||
try:
|
||||
@@ -918,6 +995,57 @@ class Agent:
|
||||
except ValueError as exc:
|
||||
return f"FEHLER: {exc}"
|
||||
return f"OK — Skill '{skill_name}' geloescht."
|
||||
if name == "skill_set_config":
|
||||
skill_name = (arguments.get("name") or "").strip()
|
||||
values = arguments.get("values")
|
||||
if not skill_name or not isinstance(values, dict):
|
||||
return "FEHLER: name + values (dict) erforderlich."
|
||||
try:
|
||||
skills_mod.set_skill_config(skill_name, values)
|
||||
except ValueError as exc:
|
||||
return f"FEHLER: {exc}"
|
||||
masked = skills_mod.get_skill_config_masked(skill_name)
|
||||
return (
|
||||
f"OK — Config fuer Skill '{skill_name}' gesetzt. "
|
||||
f"Aktuelle Werte (secrets gemasked): {masked}"
|
||||
)
|
||||
if name == "skill_list_versions":
|
||||
skill_name = (arguments.get("name") or "").strip()
|
||||
if not skill_name:
|
||||
return "FEHLER: name ist Pflicht."
|
||||
versions = skills_mod.list_skill_versions(skill_name)
|
||||
if not versions:
|
||||
return f"Skill '{skill_name}' hat keine archivierten Versionen."
|
||||
lines = [
|
||||
f"- {v.get('version_id')} ({v.get('archived_at','?')}) {v.get('summary','')}"
|
||||
for v in versions
|
||||
]
|
||||
return "Versionen (neueste zuerst):\n" + "\n".join(lines)
|
||||
if name == "skill_rollback":
|
||||
skill_name = (arguments.get("name") or "").strip()
|
||||
version_id = (arguments.get("version_id") or "").strip()
|
||||
if not skill_name or not version_id:
|
||||
return "FEHLER: name + version_id erforderlich."
|
||||
try:
|
||||
res = skills_mod.rollback_skill(skill_name, version_id)
|
||||
except ValueError as exc:
|
||||
return f"FEHLER: {exc}"
|
||||
# Side-Channel-Event als skill_created getarnt — App/Diagnostic
|
||||
# zeigen Rollback dann als sichtbare Aktion an
|
||||
self._pending_events.append({
|
||||
"type": "skill_created",
|
||||
"skill": {
|
||||
"name": skill_name,
|
||||
"description": "(rollback)",
|
||||
"execution": "local-venv",
|
||||
"active": True,
|
||||
"updated": True,
|
||||
},
|
||||
})
|
||||
return (
|
||||
f"OK — Skill '{skill_name}' auf '{version_id}' zurueckgerollt. "
|
||||
f"Sicherheits-Snapshot des vorherigen Stands: {res.get('safety_snapshot')}"
|
||||
)
|
||||
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