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:
@ -236,14 +236,15 @@ pub use system_permissions::{
|
||||
};
|
||||
|
||||
pub use plugin::{
|
||||
ContentHash, CustomPluginLayout, PluginActivationScope, PluginBundleUrl, PluginCapability,
|
||||
PluginCommandId, PluginContributionSet, PluginDescriptor, PluginError, PluginId,
|
||||
PluginInstallSource, PluginLayoutContribution, PluginLayoutType, PluginLifecycleState,
|
||||
PluginManifest, PluginMcpServerContribution, PluginMcpServerId, PluginMcpServerSpec,
|
||||
PluginMcpStatus, PluginMcpStatusSet, PluginMenuItemContribution, PluginPackageRef,
|
||||
PluginRegistry, PluginRegistryEntry, PluginSlashCommandContribution,
|
||||
PluginTopLevelMenuContribution, PluginTrustLevel, PluginVersion, RelativePath, RemovalOutcome,
|
||||
StagedPluginPackage,
|
||||
AgentPluginAssignments, ContentHash, CustomPluginLayout, PluginActivationScope,
|
||||
PluginBundleUrl, PluginCapability, PluginCommandId, PluginContributionSet, PluginDescriptor,
|
||||
PluginError, PluginId, PluginInstallSource, PluginLayoutContribution, PluginLayoutType,
|
||||
PluginLifecycleState, PluginManifest, PluginMcpServerContribution, PluginMcpServerId,
|
||||
PluginMcpServerSpec, PluginMcpStatus, PluginMcpStatusSet, PluginMenuItemContribution,
|
||||
PluginPackageRef, PluginRegistry, PluginRegistryEntry, PluginSkillContribution, PluginSkillId,
|
||||
PluginSkillRef, PluginSlashCommandContribution, PluginToolRef, PluginTopLevelMenuContribution,
|
||||
PluginTrustLevel, PluginVersion, ProjectPluginActivation, ProjectPluginSettings, RelativePath,
|
||||
RemovalOutcome, StagedPluginPackage,
|
||||
};
|
||||
|
||||
pub use sandbox::{
|
||||
@ -270,8 +271,9 @@ pub use ports::{
|
||||
PluginManifestError, PluginManifestValidator, PluginMcpError, PluginMcpSupervisor,
|
||||
PluginPackageStore, PluginRegistryError, PluginRegistryStore, PluginStorageError,
|
||||
PluginStorageStore, PluginStoreError, PreparedContext, ProcessError, ProcessSpawner,
|
||||
ProfileStore, ProjectStore, ProviderModelCatalogue, PtyError, PtyHandle, PtyPort, RemoteError,
|
||||
RemoteHost, RemotePath, RuntimeError, RuntimePermissionProbe, ScheduledTask, Scheduler,
|
||||
SpawnSpec, SprintStore, SprintStoreError, StoreError, StructuredSessionEnvironment,
|
||||
StructuredSessionEnvironmentPreparer, SystemPermissionStore, TemplateStore, WindowStateStore,
|
||||
ProfileStore, ProjectPluginSettingsStore, ProjectStore, ProviderModelCatalogue, PtyError,
|
||||
PtyHandle, PtyPort, RemoteError, RemoteHost, RemotePath, RuntimeError, RuntimePermissionProbe,
|
||||
ScheduledTask, Scheduler, SpawnSpec, SprintStore, SprintStoreError, StoreError,
|
||||
StructuredSessionEnvironment, StructuredSessionEnvironmentPreparer, SystemPermissionStore,
|
||||
TemplateStore, WindowStateStore,
|
||||
};
|
||||
|
||||
@ -17,8 +17,20 @@ use crate::conversation::ConversationParty;
|
||||
use crate::ids::{AgentId, NodeId, SkillId};
|
||||
use crate::live_state::WorkStatus;
|
||||
use crate::mailbox::TicketId;
|
||||
use crate::plugin::{PluginId, PluginSkillId};
|
||||
use crate::skill::{SkillKind, SkillScope};
|
||||
|
||||
/// Plugin origin for a resolved assigned skill. `None` on
|
||||
/// [`AssignedSkillSnapshot::plugin`] denotes a native IdeA skill.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AssignedPluginSkillSource {
|
||||
/// Owning plugin.
|
||||
pub plugin_id: PluginId,
|
||||
/// Manifest-local skill id.
|
||||
pub skill_id: PluginSkillId,
|
||||
}
|
||||
|
||||
/// Current schema version for orchestration capabilities injected into an
|
||||
/// agent's effective runtime context.
|
||||
pub const ORCHESTRATION_CAPABILITY_SNAPSHOT_VERSION: u32 = 1;
|
||||
@ -35,6 +47,9 @@ pub struct AssignedSkillSnapshot {
|
||||
pub skill_id: SkillId,
|
||||
/// Store scope used to resolve the skill body.
|
||||
pub scope: SkillScope,
|
||||
/// Plugin origin. Absent for native IdeA skills.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub plugin: Option<AssignedPluginSkillSource>,
|
||||
/// Display name shown to the model and accepted by `idea_skill_read`.
|
||||
pub name: String,
|
||||
/// One-line affordance description shown in the model context.
|
||||
|
||||
@ -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")]
|
||||
|
||||
@ -446,6 +446,19 @@ pub trait PluginPackageStore: Send + Sync {
|
||||
package: &PluginPackageRef,
|
||||
) -> Result<PluginManifestBytes, PluginStoreError>;
|
||||
|
||||
/// Reads a package-relative file from an installed plugin, confined to its
|
||||
/// canonical package root.
|
||||
async fn read_installed_file(
|
||||
&self,
|
||||
plugin_id: &crate::PluginId,
|
||||
path: &crate::RelativePath,
|
||||
) -> Result<Vec<u8>, PluginStoreError> {
|
||||
let _ = (plugin_id, path);
|
||||
Err(PluginStoreError::Invalid(
|
||||
"installed package file reads are not supported by this adapter".to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Stages an archive for validation.
|
||||
async fn install_from_archive(
|
||||
&self,
|
||||
@ -496,6 +509,23 @@ pub trait PluginRegistryStore: Send + Sync {
|
||||
async fn save_registry(&self, registry: &PluginRegistry) -> Result<(), PluginRegistryError>;
|
||||
}
|
||||
|
||||
/// Store for one project's plugin activation and agent assignments.
|
||||
#[async_trait]
|
||||
pub trait ProjectPluginSettingsStore: Send + Sync {
|
||||
/// Loads settings, returning an empty version-1 document when missing.
|
||||
async fn load(
|
||||
&self,
|
||||
project_root: &crate::ProjectPath,
|
||||
) -> Result<crate::ProjectPluginSettings, StoreError>;
|
||||
|
||||
/// Atomically persists the complete document.
|
||||
async fn save(
|
||||
&self,
|
||||
project_root: &crate::ProjectPath,
|
||||
settings: &crate::ProjectPluginSettings,
|
||||
) -> Result<(), StoreError>;
|
||||
}
|
||||
|
||||
/// Manifest parser and validator.
|
||||
pub trait PluginManifestValidator: Send + Sync {
|
||||
/// Validates raw manifest bytes.
|
||||
|
||||
Reference in New Issue
Block a user