upgrade Skill system auf regitry

This commit is contained in:
Sithies
2026-03-17 21:51:00 +01:00
parent 389c759166
commit 4e6b2c6759
28 changed files with 534 additions and 478 deletions
+2
View File
@@ -7,3 +7,5 @@ edition = "2024"
nazarick-core = { path = "../nazarick-core" }
tracing = "0.1.44"
anyhow = "1.0.102"
async-trait = "0.1.89"
inventory = "0.3.22"
+4 -4
View File
@@ -1,6 +1,6 @@
// crates/skills/src/lib.rs
//
// Skills — explizite Fähigkeiten für Nazarick-Agenten.
// Kein generischer Shell-Zugriff — jeder Skill ist bewusst implementiert.
pub mod skills;
pub mod personality;
// Stellt sicher dass alle inventory::submit! ausgeführt werden.
// Ohne diesen Import würden Skills nie eingesammelt.
pub use skills::personality;
-93
View File
@@ -1,93 +0,0 @@
// crates/skills/src/personality.rs
//
// Personality Skill — implementiert PersonalityWriter Trait.
// Schreibt Persönlichkeits-Updates in soul_personality.md.
use nazarick_core::agent::PersonalityWriter;
use tracing::info;
/// Konkrete Implementierung des PersonalityWriter Traits.
/// Wird in main.rs erstellt und via Dependency Injection an SkillExecutor übergeben.
pub struct PersonalitySkill {
/// Pfad zur soul_personality.md des Agenten
path: String,
}
impl PersonalitySkill {
/// Erstellt einen neuen PersonalitySkill für einen Agenten.
/// `path` → Pfad zur soul_personality.md
pub fn new(path: impl Into<String>) -> Self {
Self { path: path.into() }
}
}
impl PersonalityWriter for PersonalitySkill {
/// Aktualisiert ein Feld in soul_personality.md.
/// Abschnitt wird ersetzt wenn vorhanden, sonst angehängt.
fn update(&self, field: &str, value: &str) -> anyhow::Result<()> {
let content = std::fs::read_to_string(&self.path)?;
let section_header = format!("## {}", field);
let new_section = format!("## {}\n{}", field, value);
let updated = if content.contains(&section_header) {
let mut result = String::new();
let mut in_section = false;
for line in content.lines() {
if line.trim_start().starts_with("## ") && line.contains(field) {
result.push_str(&new_section);
result.push('\n');
in_section = true;
} else if line.trim_start().starts_with("## ") && in_section {
in_section = false;
result.push_str(line);
result.push('\n');
} else if !in_section {
result.push_str(line);
result.push('\n');
}
}
result
} else {
format!("{}\n{}\n", content.trim_end(), new_section)
};
std::fs::write(&self.path, updated)?;
info!(path = %self.path, field = %field, "Persönlichkeit aktualisiert");
Ok(())
}
/// Entfernt einen Abschnitt aus soul_personality.md.
/// Macht nichts wenn der Abschnitt nicht existiert.
fn remove(&self, field: &str) -> anyhow::Result<()> {
let content = std::fs::read_to_string(&self.path)?;
let section_header = format!("## {}", field);
// Abschnitt nicht vorhanden — nichts zu tun
if !content.contains(&section_header) {
return Ok(());
}
let mut result = String::new();
let mut in_section = false;
for line in content.lines() {
if line.trim_start().starts_with("## ") && line.contains(field) {
// Abschnitt gefunden — überspringen
in_section = true;
} else if line.trim_start().starts_with("## ") && in_section {
// Nächster Abschnitt — Section-Modus beenden
in_section = false;
result.push_str(line);
result.push('\n');
} else if !in_section {
result.push_str(line);
result.push('\n');
}
}
std::fs::write(&self.path, result.trim_end())?;
info!(path = %self.path, field = %field, "Persönlichkeits-Abschnitt entfernt");
Ok(())
}
}
+1
View File
@@ -0,0 +1 @@
pub mod personality;
+131
View File
@@ -0,0 +1,131 @@
// crates/skills/src/skills/personality.rs
use std::sync::Arc;
use async_trait::async_trait;
use anyhow::Result;
use tracing::info;
use nazarick_core::agent::traits::{Skill, SkillInput, SkillOutput};
use nazarick_core::agent::context::AgentContext;
use nazarick_core::agent::skill_registry::SkillMeta;
pub struct PersonalitySkill;
impl PersonalitySkill {
fn path(agent_id: &str) -> String {
format!("crates/{}/config/soul_personality.md", agent_id)
}
fn do_update(path: &str, field: &str, value: &str) -> Result<()> {
let content = std::fs::read_to_string(path)?;
let section_header = format!("## {}", field);
let new_section = format!("## {}\n{}", field, value);
let updated = if content.contains(&section_header) {
let mut result = String::new();
let mut in_section = false;
for line in content.lines() {
if line.trim_start().starts_with("## ") && line.contains(field) {
result.push_str(&new_section);
result.push('\n');
in_section = true;
} else if line.trim_start().starts_with("## ") && in_section {
in_section = false;
result.push_str(line);
result.push('\n');
} else if !in_section {
result.push_str(line);
result.push('\n');
}
}
result
} else {
format!("{}\n{}\n", content.trim_end(), new_section)
};
std::fs::write(path, updated)?;
info!(path = %path, field = %field, "Persönlichkeit aktualisiert");
Ok(())
}
fn do_remove(path: &str, field: &str) -> Result<()> {
let content = std::fs::read_to_string(path)?;
if !content.contains(&format!("## {}", field)) {
return Ok(());
}
let mut result = String::new();
let mut in_section = false;
for line in content.lines() {
if line.trim_start().starts_with("## ") && line.contains(field) {
in_section = true;
} else if line.trim_start().starts_with("## ") && in_section {
in_section = false;
result.push_str(line);
result.push('\n');
} else if !in_section {
result.push_str(line);
result.push('\n');
}
}
std::fs::write(path, result.trim_end())?;
info!(path = %path, field = %field, "Persönlichkeits-Abschnitt entfernt");
Ok(())
}
}
#[async_trait]
impl Skill for PersonalitySkill {
fn summary(&self) -> &str {
"Liest und schreibt den PERSONALITY [MUTABLE] Block — speichert dauerhaft Eigenschaften wie Ton, Stil oder Präferenzen des Herrn die das Verhalten des Agenten beeinflussen"
}
fn details(&self) -> &str {
"Verwaltet Persönlichkeitswerte in soul_personality.md.
## update
Setzt oder überschreibt einen Wert:
<skill name=\"personality\">
<action>update</action>
<field>Ton</field>
<value>kurz und direkt</value>
</skill>
## remove
Entfernt einen Wert:
<skill name=\"personality\">
<action>remove</action>
<field>Ton</field>
</skill>"
}
async fn execute(&self, input: SkillInput, ctx: AgentContext) -> Result<SkillOutput> {
let path = Self::path(&ctx.agent_id);
let field = input.require("field")?;
// action ist optional — fehlt es, wird aus value abgeleitet
let action = input.get("action").unwrap_or_else(|| {
if input.get("value").is_some() { "update" } else { "remove" }
});
match action {
"update" => {
let value = input.require("value")?;
Self::do_update(&path, field, value)?;
Ok(SkillOutput::ok(format!("'{}' gesetzt auf '{}'", field, value)))
}
"remove" => {
Self::do_remove(&path, field)?;
Ok(SkillOutput::ok(format!("'{}' entfernt", field)))
}
unknown => Ok(SkillOutput::err(format!("Unbekannte Action '{}'", unknown)))
}
}
}
inventory::submit!(SkillMeta {
name: "personality",
allowed: &["all"],
awaits_result: false,
skill: || Arc::new(PersonalitySkill),
});