feat(skill-awareness): T1→T5 — manifeste de skills + outil MCP idea_skill_read
Surface les skills assignés à un agent « à la MCP » : description sur l'entité Skill (+ effective_description fallback), section « # Skills disponibles » haute altitude dans le convention-file (mode MCP), outil read-only idea_skill_read (résolution projet→global), use case ReadSkill (port SkillStore existant), et câblage au composition root. Dump legacy du corps complet conservé en mode sans-MCP (zéro régression). Rétro-compat index.json (serde default). Tests : domain+application+infrastructure 1212 passed / 0 failed (23 ajoutés). Reste : T6 (champ description front) + T7 (e2e après rebuild AppImage). NB topologie : commit réalisé par l'orchestrateur car l'agent Git était injoignable (bug de livraison cold-start) ; à faire relire/rebaser par Git. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -43,7 +43,7 @@ use crate::orchestrator::{
|
||||
ProposeContext, ProposeContextInput, ProposeOutcome, ReadContext, ReadContextInput, ReadMemory,
|
||||
ReadMemoryInput, WriteMemory, WriteMemoryInput,
|
||||
};
|
||||
use crate::skill::{CreateSkill, CreateSkillInput};
|
||||
use crate::skill::{CreateSkill, CreateSkillInput, ReadSkill, ReadSkillInput};
|
||||
use crate::terminal::{CloseTerminal, CloseTerminalInput, StructuredSessions, TerminalSessions};
|
||||
|
||||
/// Default terminal geometry for an orchestrator-launched agent cell. The UI
|
||||
@ -303,6 +303,11 @@ pub struct OrchestratorService {
|
||||
/// [`Self::with_memory_harvest`] ; `None` ⇒ aucun harvest (zéro régression). Un
|
||||
/// échec de harvest ne transforme **jamais** un succès de délégation en erreur.
|
||||
memory_harvest: Option<Arc<HarvestMemoryFromTurn>>,
|
||||
/// Use case de lecture d'un skill **par nom** (`idea_skill_read`, feature
|
||||
/// « skills à la MCP »). Compose le port `SkillStore` existant (aucun nouveau
|
||||
/// port). Injecté via [`Self::with_read_skill`] ; `None` ⇒ la commande
|
||||
/// `skill.read` renvoie une erreur typée (call sites/tests legacy restent verts).
|
||||
read_skill: Option<Arc<ReadSkill>>,
|
||||
}
|
||||
|
||||
/// Bundle des quatre use cases C7 sous [`domain::fileguard::FileGuard`], injectés
|
||||
@ -367,9 +372,19 @@ impl OrchestratorService {
|
||||
clock: None,
|
||||
structured: None,
|
||||
memory_harvest: None,
|
||||
read_skill: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Branche le use case [`ReadSkill`] (`skill.read`/`idea_skill_read`). Builder
|
||||
/// additif (signature de [`Self::new`] inchangée) ; `None` ⇒ la commande
|
||||
/// renvoie une erreur typée « not configured ».
|
||||
#[must_use]
|
||||
pub fn with_read_skill(mut self, read_skill: Arc<ReadSkill>) -> Self {
|
||||
self.read_skill = Some(read_skill);
|
||||
self
|
||||
}
|
||||
|
||||
/// Branche le registre des [`StructuredSessions`] (§17.5) pour router un `ask`
|
||||
/// vers une cible **structurée** sur sa propre session. Builder additif (signature
|
||||
/// de [`Self::new`] inchangée) ; `None` ⇒ chemin PTY/mailbox legacy uniquement.
|
||||
@ -588,6 +603,9 @@ impl OrchestratorService {
|
||||
OrchestratorCommand::ReadMemory { slug, requester } => {
|
||||
self.read_memory(project, slug, requester).await
|
||||
}
|
||||
OrchestratorCommand::ReadSkill { name, requester } => {
|
||||
self.read_skill(project, name, requester).await
|
||||
}
|
||||
OrchestratorCommand::WriteMemory {
|
||||
slug,
|
||||
content,
|
||||
@ -682,6 +700,31 @@ impl OrchestratorService {
|
||||
})
|
||||
}
|
||||
|
||||
/// `skill.read` → resolves a skill by name (project scope first, then global)
|
||||
/// and returns its Markdown body inline in the outcome's `reply`. Read-only:
|
||||
/// `requester` is accepted for symmetry with the other read tools but skill
|
||||
/// reads take no [`domain::fileguard::FileGuard`] lease.
|
||||
async fn read_skill(
|
||||
&self,
|
||||
project: &Project,
|
||||
name: String,
|
||||
_requester: ConversationParty,
|
||||
) -> Result<OrchestratorOutcome, AppError> {
|
||||
let read_skill = self.read_skill.as_deref().ok_or_else(|| {
|
||||
AppError::Invalid("the idea_skill_read tool is not configured".to_owned())
|
||||
})?;
|
||||
let md = read_skill
|
||||
.execute(ReadSkillInput {
|
||||
name: name.clone(),
|
||||
project_root: project.root.clone(),
|
||||
})
|
||||
.await?;
|
||||
Ok(OrchestratorOutcome {
|
||||
detail: format!("read skill {name}"),
|
||||
reply: Some(md.into_string()),
|
||||
})
|
||||
}
|
||||
|
||||
/// `memory.write` → writes a note under an exclusive write-lease.
|
||||
async fn write_memory(
|
||||
&self,
|
||||
@ -1867,6 +1910,9 @@ impl OrchestratorService {
|
||||
.create_skill
|
||||
.execute(CreateSkillInput {
|
||||
name: name.clone(),
|
||||
// `idea_create_skill` carries no description argument; the affordance
|
||||
// falls back to the body's first line (see `effective_description`).
|
||||
description: None,
|
||||
content,
|
||||
scope,
|
||||
project_root: project.root.clone(),
|
||||
|
||||
Reference in New Issue
Block a user