feat(plugins): paramètres projet plugins + attribution skills/tools aux agents — #282 (manifeste accepte contributes.skills IdeA-native multi-harness; store .ideai/project-plugins.json v1: enable plugin par projet + assignments skills/tools par agent; refs + use cases settings/assign/unassign; resolver skills pour idea_skill_read et injection catalogue lancement agent; permissions MCP dynamiques projetées par harness Claude Code/Codex/OpenCode; tombstones uninstall, hot reload recalcule catalogue et marque assignments cassés; frontend: Settings projet Plugins, détail capabilities, attribution agent, catalogue permissions; bump pointeur IdeaSDK 6eb8db3; QA verte: cargo test -p application, project_plugin_settings 3/3, dto_plugins 2/2, structured_launch_d3 26/26, catalogue mcp 7/7, frontend 5 tests ProjectPluginSettings/plugin, npm build frontend)
This commit is contained in:
@ -16,6 +16,7 @@ use domain::{
|
||||
};
|
||||
|
||||
use crate::error::AppError;
|
||||
use crate::ProjectPluginResolver;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CreateSkill
|
||||
@ -293,13 +294,24 @@ impl ResolveAgentCapabilities {
|
||||
/// context rendering and `idea_skill_read` authorization.
|
||||
pub struct AssignedSkillResolver {
|
||||
skills: Arc<dyn SkillStore>,
|
||||
plugin_skills: Option<Arc<ProjectPluginResolver>>,
|
||||
}
|
||||
|
||||
impl AssignedSkillResolver {
|
||||
/// Builds the resolver from the existing skill store port.
|
||||
#[must_use]
|
||||
pub fn new(skills: Arc<dyn SkillStore>) -> Self {
|
||||
Self { skills }
|
||||
Self {
|
||||
skills,
|
||||
plugin_skills: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the provider-agnostic project-plugin resolver.
|
||||
#[must_use]
|
||||
pub fn with_plugin_skills(mut self, plugin_skills: Arc<ProjectPluginResolver>) -> Self {
|
||||
self.plugin_skills = Some(plugin_skills);
|
||||
self
|
||||
}
|
||||
|
||||
/// Resolves the assigned skills for `agent` in manifest order.
|
||||
@ -342,6 +354,7 @@ impl AssignedSkillResolver {
|
||||
let snapshot = AssignedSkillSnapshot {
|
||||
skill_id: skill.id,
|
||||
scope: skill.scope,
|
||||
plugin: None,
|
||||
name: skill.name,
|
||||
description,
|
||||
kind: skill.kind,
|
||||
@ -353,6 +366,52 @@ impl AssignedSkillResolver {
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
if let Some(resolver) = &self.plugin_skills {
|
||||
let settings = resolver.settings(root).await?;
|
||||
if let Some(assignments) = settings.assignments_for(agent.id) {
|
||||
for (offset, reference) in assignments.skills.iter().enumerate() {
|
||||
let Some(manifest) = resolver
|
||||
.enabled_manifest(root, &reference.plugin_id)
|
||||
.await?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(skill) = manifest
|
||||
.contributes
|
||||
.skills
|
||||
.iter()
|
||||
.find(|s| s.id == reference.skill_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let content = match resolver
|
||||
.read_skill_body(&reference.plugin_id, &skill.path)
|
||||
.await
|
||||
{
|
||||
Ok(content) => content,
|
||||
Err(AppError::NotFound(_)) => continue,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
assigned.push((
|
||||
AssignedSkillSnapshot {
|
||||
// Native fields remain populated for backwards wire compatibility;
|
||||
// plugin identity is authoritative when `plugin` is present.
|
||||
skill_id: SkillId::from_uuid(uuid::Uuid::nil()),
|
||||
scope: SkillScope::Project,
|
||||
plugin: Some(domain::orchestrator::AssignedPluginSkillSource {
|
||||
plugin_id: reference.plugin_id.clone(),
|
||||
skill_id: reference.skill_id.clone(),
|
||||
}),
|
||||
name: skill.name.clone(),
|
||||
description: skill.description.clone(),
|
||||
kind: skill.kind,
|
||||
assignment_index: (agent.skills.len() + offset) as u32,
|
||||
},
|
||||
content,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(assigned)
|
||||
}
|
||||
|
||||
@ -382,6 +441,25 @@ impl AssignedSkillResolver {
|
||||
)))
|
||||
}
|
||||
};
|
||||
if let Some(plugin) = &selected.plugin {
|
||||
let resolver = self
|
||||
.plugin_skills
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::NotFound(format!("assigned plugin skill `{name}`")))?;
|
||||
let manifest = resolver
|
||||
.enabled_manifest(root, &plugin.plugin_id)
|
||||
.await?
|
||||
.ok_or_else(|| AppError::NotFound(format!("assigned plugin skill `{name}`")))?;
|
||||
let contribution = manifest
|
||||
.contributes
|
||||
.skills
|
||||
.iter()
|
||||
.find(|s| s.id == plugin.skill_id)
|
||||
.ok_or_else(|| AppError::NotFound(format!("assigned plugin skill `{name}`")))?;
|
||||
return resolver
|
||||
.read_skill_body(&plugin.plugin_id, &contribution.path)
|
||||
.await;
|
||||
}
|
||||
let skill = self
|
||||
.skills
|
||||
.get(selected.scope, root, selected.skill_id)
|
||||
|
||||
Reference in New Issue
Block a user