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

@ -9,14 +9,15 @@ use app_tauri_lib::dto::{
};
use application::AppError;
use application::{
AgentTicketState, AgentWorkState, CreateAgentOutput, InspectConversationOutput,
LaunchAgentOutput, ListAgentsOutput, LiveSessionKind, LiveSessionSnapshot, LiveWorkSession,
ProjectWorkState, TicketWorkSource, TicketWorkStatus,
AgentCapability, AgentTicketState, AgentWorkState, CreateAgentOutput,
InspectConversationOutput, LaunchAgentOutput, ListAgentsOutput, ListedAgentCapabilities,
LiveSessionKind, LiveSessionSnapshot, LiveWorkSession, ProjectWorkState, TicketWorkSource,
TicketWorkStatus,
};
use domain::ids::{AgentId, NodeId, ProfileId, SessionId};
use domain::ports::ConversationDetails;
use domain::terminal::{PtySize, SessionKind, SessionStatus, TerminalSession};
use domain::{Agent, AgentOrigin, ProjectPath};
use domain::{Agent, AgentOrigin, ProjectPath, SkillKind};
use serde_json::json;
use uuid::Uuid;
@ -40,7 +41,7 @@ fn make_agent(agent_uuid: u128, profile_uuid: u128) -> Agent {
#[test]
fn agent_dto_serialises_camelcase() {
let agent = make_agent(1, 2);
let dto = AgentDto(agent.clone());
let dto = AgentDto::from_agent(agent.clone());
let v = serde_json::to_value(&dto).unwrap();
assert_eq!(v["id"], agent.id.to_string());
@ -53,6 +54,7 @@ fn agent_dto_serialises_camelcase() {
assert_eq!(v["synchronized"], false);
// origin: tagged `{ "type": "scratch" }`
assert_eq!(v["origin"]["type"], "scratch");
assert_eq!(v["capabilities"], json!([]));
// no snake_case leak
assert!(v.get("context_path").is_none());
assert!(v.get("profile_id").is_none());
@ -60,14 +62,27 @@ fn agent_dto_serialises_camelcase() {
#[test]
fn agent_list_dto_is_transparent_array() {
let first = make_agent(1, 2);
let out = ListAgentsOutput {
agents: vec![make_agent(1, 2), make_agent(3, 4)],
agents: vec![first.clone(), make_agent(3, 4)],
capabilities: vec![ListedAgentCapabilities {
agent_id: first.id,
capabilities: vec![AgentCapability {
name: "review".to_owned(),
description: "Reviews changes".to_owned(),
kind: SkillKind::Reference,
}],
}],
};
let dto = AgentListDto::from(out);
let v = serde_json::to_value(&dto).unwrap();
let arr = v.as_array().expect("transparent array");
assert_eq!(arr.len(), 2);
assert_eq!(arr[0]["name"], "My Agent");
assert_eq!(arr[0]["capabilities"][0]["name"], "review");
assert_eq!(arr[0]["capabilities"][0]["description"], "Reviews changes");
assert_eq!(arr[0]["capabilities"][0]["kind"], "reference");
assert_eq!(arr[1]["capabilities"], json!([]));
}
#[test]
@ -77,7 +92,8 @@ fn create_agent_output_maps_to_agent_dto() {
agent: agent.clone(),
};
let dto = AgentDto::from(out);
assert_eq!(dto.0.id, agent.id);
assert_eq!(dto.agent.id, agent.id);
assert!(dto.capabilities.is_empty());
}
// ---------------------------------------------------------------------------