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:
2026-08-01 00:26:56 +02:00
parent 5efb026a80
commit 92b17e9a69
18 changed files with 467 additions and 83 deletions

View File

@ -1967,20 +1967,58 @@ pub fn parse_profile_id(raw: &str) -> Result<ProfileId, ErrorDto> {
// ---------------------------------------------------------------------------
use application::{
ChangeAgentProfileOutput, CreateAgentOutput, InspectConversationOutput, LaunchAgentOutput,
ListAgentsOutput, ReadAgentContextOutput, ReadMcpToolPermissionsOutput,
AgentCapability, ChangeAgentProfileOutput, CreateAgentOutput, InspectConversationOutput,
LaunchAgentOutput, ListAgentsOutput, ReadAgentContextOutput, ReadMcpToolPermissionsOutput,
};
use domain::{
Agent, AgentMcpToolPolicyOverride, EffectivePermissions, McpToolPolicy, PermissionSet,
ProjectPermissions, TerminalSession,
ProjectPermissions, SkillKind, TerminalSession,
};
/// An agent crossing the wire. [`Agent`] already serialises camelCase
/// (`id`, `name`, `contextPath`, `profileId`, `origin` tagged, `synchronized`),
/// so we embed it directly — the TS mirror matches this shape.
/// One discoverable capability carried by an agent.
#[derive(Debug, Clone, Serialize)]
#[serde(transparent)]
pub struct AgentDto(pub Agent);
#[serde(rename_all = "camelCase")]
pub struct AgentCapabilityDto {
/// Capability display name.
pub name: String,
/// One-line affordance description.
pub description: String,
/// Capability nature.
pub kind: SkillKind,
}
impl From<AgentCapability> for AgentCapabilityDto {
fn from(value: AgentCapability) -> Self {
Self {
name: value.name,
description: value.description,
kind: value.kind,
}
}
}
/// An agent crossing the wire. The raw [`Agent`] shape is flattened so existing
/// fields, including `skills`, remain compatible; `capabilities` is additive.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentDto {
/// Raw agent manifest shape.
#[serde(flatten)]
pub agent: Agent,
/// Resolved discoverable capabilities.
pub capabilities: Vec<AgentCapabilityDto>,
}
impl AgentDto {
/// Builds a DTO without resolved capabilities.
#[must_use]
pub fn from_agent(agent: Agent) -> Self {
Self {
agent,
capabilities: Vec::new(),
}
}
}
/// A list of agents (camelCase array on the wire).
#[derive(Debug, Clone, Serialize)]
@ -1989,13 +2027,25 @@ pub struct AgentListDto(pub Vec<AgentDto>);
impl From<ListAgentsOutput> for AgentListDto {
fn from(out: ListAgentsOutput) -> Self {
Self(out.agents.into_iter().map(AgentDto).collect())
Self(
out.discovery_entries()
.into_iter()
.map(|entry| AgentDto {
agent: entry.agent,
capabilities: entry
.capabilities
.into_iter()
.map(AgentCapabilityDto::from)
.collect(),
})
.collect(),
)
}
}
impl From<CreateAgentOutput> for AgentDto {
fn from(out: CreateAgentOutput) -> Self {
Self(out.agent)
Self::from_agent(out.agent)
}
}
@ -2326,7 +2376,7 @@ pub struct ChangeAgentProfileDto {
impl From<ChangeAgentProfileOutput> for ChangeAgentProfileDto {
fn from(out: ChangeAgentProfileOutput) -> Self {
Self {
agent: AgentDto(out.agent),
agent: AgentDto::from_agent(out.agent),
relaunched_session: out.relaunched.map(TerminalSessionDto::from),
}
}