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:
2026-09-08 15:48:57 +02:00
parent bdc20a134a
commit 86ed0aa00b
38 changed files with 2127 additions and 89 deletions

View File

@ -370,6 +370,41 @@ impl PluginMcpServerId {
}
}
/// Stable id of a skill contributed by a plugin manifest.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct PluginSkillId(String);
impl PluginSkillId {
/// Validates and creates a plugin skill id.
pub fn new(raw: impl Into<String>) -> Result<Self, PluginError> {
PluginCommandId::new(raw).map(|v| Self(v.0))
}
/// Returns the raw id.
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
/// Read-only skill contribution whose body lives inside the plugin package.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PluginSkillContribution {
/// Stable id within the plugin.
pub id: PluginSkillId,
/// Agent-facing name accepted by `idea_skill_read`.
pub name: String,
/// Short affordance shown in the injected skill catalogue.
pub description: String,
/// Capability nature.
#[serde(default)]
pub kind: crate::SkillKind,
/// Package-relative Markdown file.
pub path: RelativePath,
}
/// Top-level menu contribution.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -484,6 +519,9 @@ pub struct PluginMcpServerContribution {
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PluginContributionSet {
/// Read-only agent skills shipped in the package.
#[serde(default)]
pub skills: Vec<PluginSkillContribution>,
/// Top-level menus.
#[serde(default)]
pub menus: Vec<PluginTopLevelMenuContribution>,
@ -501,6 +539,94 @@ pub struct PluginContributionSet {
pub mcp_servers: Vec<PluginMcpServerContribution>,
}
/// Project-scoped plugin activation entry. It is retained when a plugin is
/// temporarily uninstalled so reinstalling does not lose user intent.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProjectPluginActivation {
/// Globally-installed plugin id.
pub plugin_id: PluginId,
/// Whether this project may expose the plugin's capabilities.
pub enabled: bool,
}
/// Reference to one plugin skill assigned to an agent.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PluginSkillRef {
/// Owning plugin.
pub plugin_id: PluginId,
/// Skill id within that plugin.
pub skill_id: PluginSkillId,
}
/// Stable namespace of one externally-provided MCP tool.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PluginToolRef {
/// Owning plugin.
pub plugin_id: PluginId,
/// MCP server id within that plugin.
pub server_id: PluginMcpServerId,
/// Tool name advertised by the server.
pub tool_name: String,
}
/// Plugin capabilities explicitly assigned to one project agent.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentPluginAssignments {
/// Agent receiving the capabilities.
pub agent_id: crate::AgentId,
/// Assigned plugin skills, in injection order.
#[serde(default)]
pub skills: Vec<PluginSkillRef>,
/// Assigned external MCP tools.
#[serde(default)]
pub tools: Vec<PluginToolRef>,
}
/// Versioned project-owned plugin settings persisted under
/// `.ideai/project-plugins.json`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProjectPluginSettings {
/// Schema version.
pub version: u32,
/// Explicit project activation choices.
#[serde(default)]
pub plugins: Vec<ProjectPluginActivation>,
/// Per-agent assignments. Dangling entries are intentional tombstones.
#[serde(default)]
pub agents: Vec<AgentPluginAssignments>,
}
impl Default for ProjectPluginSettings {
fn default() -> Self {
Self {
version: 1,
plugins: Vec::new(),
agents: Vec::new(),
}
}
}
impl ProjectPluginSettings {
/// Whether a plugin is explicitly enabled for this project.
#[must_use]
pub fn is_enabled(&self, plugin_id: &PluginId) -> bool {
self.plugins
.iter()
.any(|p| &p.plugin_id == plugin_id && p.enabled)
}
/// Assignments for an agent, if any.
#[must_use]
pub fn assignments_for(&self, agent_id: crate::AgentId) -> Option<&AgentPluginAssignments> {
self.agents.iter().find(|a| a.agent_id == agent_id)
}
}
/// Validated plugin manifest.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]