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:
@ -14,8 +14,9 @@
|
||||
mod usecases;
|
||||
|
||||
pub use usecases::{
|
||||
AssignSkillToAgent, AssignSkillToAgentInput, AssignedSkillResolver, CreateSkill,
|
||||
CreateSkillInput, CreateSkillOutput, DeleteSkill, DeleteSkillInput, ListSkills,
|
||||
ListSkillsInput, ListSkillsOutput, ReadSkill, ReadSkillInput, UnassignSkillFromAgent,
|
||||
UnassignSkillFromAgentInput, UpdateSkill, UpdateSkillInput, UpdateSkillOutput,
|
||||
AgentCapability, AssignSkillToAgent, AssignSkillToAgentInput, AssignedSkillResolver,
|
||||
CreateSkill, CreateSkillInput, CreateSkillOutput, DeleteSkill, DeleteSkillInput, ListSkills,
|
||||
ListSkillsInput, ListSkillsOutput, ReadSkill, ReadSkillInput, ResolveAgentCapabilities,
|
||||
ResolveAgentCapabilitiesInput, UnassignSkillFromAgent, UnassignSkillFromAgentInput,
|
||||
UpdateSkill, UpdateSkillInput, UpdateSkillOutput,
|
||||
};
|
||||
|
||||
@ -11,8 +11,8 @@ use std::sync::Arc;
|
||||
use domain::ports::{AgentContextStore, EventBus, IdGenerator, SkillStore, StoreError};
|
||||
use domain::{
|
||||
Agent, AgentId, AgentManifest, AssignedSkillSnapshot, ConversationParty, DomainEvent,
|
||||
MarkdownDoc, OrchestrationCapabilitySnapshot, Project, ProjectPath, Skill, SkillId, SkillRef,
|
||||
SkillScope,
|
||||
MarkdownDoc, OrchestrationCapabilitySnapshot, Project, ProjectPath, Skill, SkillId, SkillKind,
|
||||
SkillRef, SkillScope,
|
||||
};
|
||||
|
||||
use crate::error::AppError;
|
||||
@ -211,9 +211,80 @@ impl DeleteSkill {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ReadSkill
|
||||
// ResolveAgentCapabilities / ReadSkill
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// One discoverable capability carried by an agent.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AgentCapability {
|
||||
/// Capability display name.
|
||||
pub name: String,
|
||||
/// One-line affordance description.
|
||||
pub description: String,
|
||||
/// Capability nature.
|
||||
pub kind: SkillKind,
|
||||
}
|
||||
|
||||
impl From<&AssignedSkillSnapshot> for AgentCapability {
|
||||
fn from(skill: &AssignedSkillSnapshot) -> Self {
|
||||
Self {
|
||||
name: skill.name.clone(),
|
||||
description: skill.description.clone(),
|
||||
kind: skill.kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`ResolveAgentCapabilities::execute`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ResolveAgentCapabilitiesInput {
|
||||
/// Agent whose assigned skills must be resolved.
|
||||
pub agent: Agent,
|
||||
/// Active project root used to resolve project-scoped skills.
|
||||
pub project_root: ProjectPath,
|
||||
}
|
||||
|
||||
/// Resolves assigned skills into discoverable agent capabilities.
|
||||
pub struct ResolveAgentCapabilities {
|
||||
resolver: Arc<AssignedSkillResolver>,
|
||||
}
|
||||
|
||||
impl ResolveAgentCapabilities {
|
||||
/// Builds the use case from a skill store.
|
||||
#[must_use]
|
||||
pub fn new(skills: Arc<dyn SkillStore>) -> Self {
|
||||
Self {
|
||||
resolver: Arc::new(AssignedSkillResolver::new(skills)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the use case from a shared resolver.
|
||||
#[must_use]
|
||||
pub fn with_resolver(resolver: Arc<AssignedSkillResolver>) -> Self {
|
||||
Self { resolver }
|
||||
}
|
||||
|
||||
/// Resolves the compact capability list, skipping dangling skill refs.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`AppError::Store`] on store failures other than a missing skill.
|
||||
pub async fn execute(
|
||||
&self,
|
||||
input: ResolveAgentCapabilitiesInput,
|
||||
) -> Result<Vec<AgentCapability>, AppError> {
|
||||
let snapshot = self
|
||||
.resolver
|
||||
.resolve_for_agent(&input.agent, &input.project_root)
|
||||
.await?;
|
||||
Ok(snapshot
|
||||
.assigned_skills
|
||||
.iter()
|
||||
.map(AgentCapability::from)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves assigned skills into the runtime capability snapshot used by both
|
||||
/// context rendering and `idea_skill_read` authorization.
|
||||
pub struct AssignedSkillResolver {
|
||||
@ -269,6 +340,7 @@ impl AssignedSkillResolver {
|
||||
scope: skill.scope,
|
||||
name: skill.name,
|
||||
description,
|
||||
kind: skill.kind,
|
||||
assignment_index: index as u32,
|
||||
};
|
||||
assigned.push((snapshot, skill.content_md));
|
||||
@ -691,6 +763,56 @@ mod tests {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_agent_capabilities_returns_compact_kind_aware_affordances() {
|
||||
let store = Arc::new(FakeSkillStore::default());
|
||||
store.push(
|
||||
skill(1, "deploy", "# Deploy\n\nbody", SkillScope::Project)
|
||||
.with_description(Some("Deploys safely".to_owned())),
|
||||
);
|
||||
store.push(
|
||||
skill(2, "runbook", "# Runbook", SkillScope::Global).with_kind(SkillKind::Reference),
|
||||
);
|
||||
let requester = agent_id(10);
|
||||
let agent = manifest_for(
|
||||
requester,
|
||||
vec![
|
||||
SkillRef::new(
|
||||
SkillId::from_uuid(uuid::Uuid::from_u128(1)),
|
||||
SkillScope::Project,
|
||||
),
|
||||
SkillRef::new(
|
||||
SkillId::from_uuid(uuid::Uuid::from_u128(99)),
|
||||
SkillScope::Project,
|
||||
),
|
||||
SkillRef::new(
|
||||
SkillId::from_uuid(uuid::Uuid::from_u128(2)),
|
||||
SkillScope::Global,
|
||||
),
|
||||
],
|
||||
)
|
||||
.entries[0]
|
||||
.to_agent()
|
||||
.unwrap();
|
||||
let uc = ResolveAgentCapabilities::new(store);
|
||||
|
||||
let capabilities = uc
|
||||
.execute(ResolveAgentCapabilitiesInput {
|
||||
agent,
|
||||
project_root: root(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(capabilities.len(), 2);
|
||||
assert_eq!(capabilities[0].name, "deploy");
|
||||
assert_eq!(capabilities[0].description, "Deploys safely");
|
||||
assert_eq!(capabilities[0].kind, SkillKind::Workflow);
|
||||
assert_eq!(capabilities[1].name, "runbook");
|
||||
assert_eq!(capabilities[1].description, "Runbook");
|
||||
assert_eq!(capabilities[1].kind, SkillKind::Reference);
|
||||
}
|
||||
|
||||
fn read_skill_uc(
|
||||
store: Arc<FakeSkillStore>,
|
||||
manifest: AgentManifest,
|
||||
|
||||
Reference in New Issue
Block a user