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:
2026-05-28 23:52:46 +02:00
parent 1a72f27861
commit 8359500476
6 changed files with 884 additions and 2 deletions
+65
View File
@@ -757,6 +757,7 @@ class SkillCreate(BaseModel):
requires: dict = Field(default_factory=dict)
pip_packages: list = Field(default_factory=list)
author: str = "stefan"
config_schema: list = Field(default_factory=list)
class SkillRun(BaseModel):
@@ -769,6 +770,18 @@ class SkillPatch(BaseModel):
description: str | None = None
active: bool | None = None
args: list | None = None
entry_code: str | None = None
readme: str | None = None
pip_packages: list | None = None
config_schema: list | None = None
class SkillConfigSet(BaseModel):
values: dict
class SkillRollback(BaseModel):
version_id: str
@app.get("/skills/list")
@@ -798,6 +811,7 @@ def skills_create(body: SkillCreate):
requires=body.requires,
pip_packages=body.pip_packages,
author=body.author,
config_schema=body.config_schema,
)
except ValueError as exc:
raise HTTPException(400, str(exc))
@@ -834,6 +848,57 @@ def skills_logs(name: str, limit: int = 50):
return {"logs": skills_mod.list_logs(name, limit=limit)}
# ── Skill-Configs (P3): statische Werte (API-Keys etc.) je Skill ───
@app.get("/skills/{name}/config")
def skills_config_get(name: str):
"""Liefert config_schema + aktuelle Werte (secret-Felder gemaskt mit
'***SET***')."""
manifest = skills_mod.read_manifest(name)
if manifest is None:
raise HTTPException(404, f"Skill '{name}' nicht gefunden")
return {
"schema": manifest.get("config_schema") or [],
"values": skills_mod.get_skill_config_masked(name),
}
@app.post("/skills/{name}/config")
def skills_config_set(name: str, body: SkillConfigSet):
"""Setzt Config-Werte (komplett ueberschreibend). Werte greifen ab dem
naechsten skill_run. Secret-Felder werden in der Antwort gemaskt."""
manifest = skills_mod.read_manifest(name)
if manifest is None:
raise HTTPException(404, f"Skill '{name}' nicht gefunden")
skills_mod.set_skill_config(name, body.values)
return {"ok": True, "values": skills_mod.get_skill_config_masked(name)}
# ── Skill-Versions (P4): rollback ──────────────────────────────────
@app.get("/skills/{name}/versions")
def skills_versions_list(name: str):
if skills_mod.read_manifest(name) is None:
raise HTTPException(404, f"Skill '{name}' nicht gefunden")
return {"versions": skills_mod.list_skill_versions(name)}
@app.post("/skills/{name}/rollback")
def skills_rollback(name: str, body: SkillRollback):
try:
return skills_mod.rollback_skill(name, body.version_id)
except ValueError as exc:
raise HTTPException(404, str(exc))
@app.delete("/skills/{name}/versions/{version_id}")
def skills_versions_delete(name: str, version_id: str):
try:
return skills_mod.delete_skill_version(name, version_id)
except ValueError as exc:
raise HTTPException(404, str(exc))
@app.get("/skills/{name}/export")
def skills_export(name: str):
try: