feat(skills): capacités agent découvrables via idea_list_agents
Ajoute SkillKind (Workflow/Reference) sur Skill, extrait le use case ResolveAgentCapabilities à partir des SkillRef assignés, et enrichit idea_list_agents d'un champ additif capabilities (name, description, kind) — remplace l'exposition de SkillRef opaques par un inventaire de capacités interrogeable, source commune avec le bloc « Skills disponibles » injecté au lancement (#119). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -27,6 +27,17 @@ pub enum SkillScope {
|
||||
Project,
|
||||
}
|
||||
|
||||
/// Nature of a skill when surfaced as an agent capability.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum SkillKind {
|
||||
/// Procedural workflow the agent may execute on demand.
|
||||
#[default]
|
||||
Workflow,
|
||||
/// Reference material the agent may consult selectively.
|
||||
Reference,
|
||||
}
|
||||
|
||||
/// A reusable workflow assignable to one or more agents.
|
||||
///
|
||||
/// Invariants enforced here:
|
||||
@ -45,6 +56,10 @@ pub struct Skill {
|
||||
/// [`Skill::effective_description`] falls back to the first line of the body.
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
/// Explicit skill nature. Defaults to [`SkillKind::Workflow`] for legacy
|
||||
/// skill JSON/index rows that predate this field.
|
||||
#[serde(default)]
|
||||
pub kind: SkillKind,
|
||||
/// Markdown body — the workflow injected into an agent's convention file.
|
||||
pub content_md: MarkdownDoc,
|
||||
/// Scope (selects the backing store).
|
||||
@ -74,6 +89,7 @@ impl Skill {
|
||||
id,
|
||||
name,
|
||||
description: None,
|
||||
kind: SkillKind::Workflow,
|
||||
content_md,
|
||||
scope,
|
||||
})
|
||||
@ -90,6 +106,13 @@ impl Skill {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns this skill with an explicit [`SkillKind`] set (builder).
|
||||
#[must_use]
|
||||
pub const fn with_kind(mut self, kind: SkillKind) -> Self {
|
||||
self.kind = kind;
|
||||
self
|
||||
}
|
||||
|
||||
/// The description to surface for this skill, with a deterministic fallback.
|
||||
///
|
||||
/// Returns [`Skill::description`] when present and non-blank (trimmed);
|
||||
@ -122,7 +145,8 @@ impl Skill {
|
||||
pub fn with_content(&self, content_md: MarkdownDoc) -> Result<Self, DomainError> {
|
||||
Ok(
|
||||
Skill::new(self.id, self.name.clone(), content_md, self.scope)?
|
||||
.with_description(self.description.clone()),
|
||||
.with_description(self.description.clone())
|
||||
.with_kind(self.kind),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -230,22 +254,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn deserialize_legacy_skill_without_description_defaults_to_none() {
|
||||
// A skill JSON written before `description` existed must deserialise with
|
||||
// `description: None` thanks to `#[serde(default)]`.
|
||||
// A skill JSON written before `description`/`kind` existed must
|
||||
// deserialise with additive defaults.
|
||||
let id = uuid::Uuid::from_u128(7);
|
||||
let json =
|
||||
format!(r##"{{"id":"{id}","name":"legacy","contentMd":"# body","scope":"global"}}"##);
|
||||
let parsed: Skill = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed.description, None);
|
||||
assert_eq!(parsed.kind, SkillKind::Workflow);
|
||||
assert_eq!(parsed.name, "legacy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_round_trip_with_description() {
|
||||
let original = skill("# body").with_description(Some("affordance".to_owned()));
|
||||
fn serde_round_trip_with_description_and_kind() {
|
||||
let original = skill("# body")
|
||||
.with_description(Some("affordance".to_owned()))
|
||||
.with_kind(SkillKind::Reference);
|
||||
let json = serde_json::to_string(&original).unwrap();
|
||||
let back: Skill = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(back, original);
|
||||
assert_eq!(back.description.as_deref(), Some("affordance"));
|
||||
assert_eq!(back.kind, SkillKind::Reference);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user