feat(backend): système de plugins — domaine, application, infrastructure (#43)
Lots B1-B4 : modèle de domaine des plugins (manifeste, capacités menus/layouts/MCP), port et registre applicatif, chargement/validation en infrastructure, exposition DTO et commandes Tauri. Tests cargo verts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -49,6 +49,11 @@ use crate::model_server::{
|
||||
HfModelRef, LocalModelServerConfig, ModelPath, ModelServerEndpoint, ModelServerStatus,
|
||||
};
|
||||
use crate::permission::ProjectPermissions;
|
||||
use crate::plugin::{
|
||||
ContentHash, PluginBundleUrl, PluginId, PluginManifest, PluginMcpServerSpec,
|
||||
PluginMcpStatusSet, PluginPackageRef, PluginRegistry, RelativePath, RemovalOutcome,
|
||||
StagedPluginPackage,
|
||||
};
|
||||
use crate::profile::{AgentProfile, EmbedderProfile};
|
||||
use crate::project::{Project, ProjectPath};
|
||||
use crate::remote::RemoteKind;
|
||||
@ -279,6 +284,166 @@ impl RemotePath {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque local path supplied by a driving adapter.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct LocalPath(pub String);
|
||||
|
||||
impl LocalPath {
|
||||
/// Wraps a raw path.
|
||||
#[must_use]
|
||||
pub fn new(p: impl Into<String>) -> Self {
|
||||
Self(p.into())
|
||||
}
|
||||
|
||||
/// Returns the path as a string slice.
|
||||
#[must_use]
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw manifest bytes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PluginManifestBytes {
|
||||
/// Bytes of `idea-plugin.json`.
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Plugin package store errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum PluginStoreError {
|
||||
/// Package was not found.
|
||||
#[error("plugin package not found")]
|
||||
NotFound,
|
||||
/// Invalid input/source.
|
||||
#[error("invalid plugin package: {0}")]
|
||||
Invalid(String),
|
||||
/// I/O error.
|
||||
#[error("plugin package I/O error: {0}")]
|
||||
Io(String),
|
||||
/// Serialization or archive error.
|
||||
#[error("plugin package format error: {0}")]
|
||||
Format(String),
|
||||
}
|
||||
|
||||
/// Plugin registry errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum PluginRegistryError {
|
||||
/// I/O error.
|
||||
#[error("plugin registry I/O error: {0}")]
|
||||
Io(String),
|
||||
/// Serialization error.
|
||||
#[error("plugin registry serialization error: {0}")]
|
||||
Serialization(String),
|
||||
}
|
||||
|
||||
/// Manifest validation errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum PluginManifestError {
|
||||
/// Invalid JSON.
|
||||
#[error("invalid plugin manifest JSON: {0}")]
|
||||
Json(String),
|
||||
/// Invalid manifest data.
|
||||
#[error("invalid plugin manifest: {0}")]
|
||||
Invalid(String),
|
||||
/// Incompatible engine.
|
||||
#[error("incompatible IdeA engine: {0}")]
|
||||
IncompatibleEngine(String),
|
||||
}
|
||||
|
||||
/// Plugin MCP errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum PluginMcpError {
|
||||
/// Process/supervisor failure.
|
||||
#[error("plugin MCP error: {0}")]
|
||||
Process(String),
|
||||
}
|
||||
|
||||
/// Store for installed plugin packages under the global app data directory.
|
||||
#[async_trait]
|
||||
pub trait PluginPackageStore: Send + Sync {
|
||||
/// Lists committed package roots.
|
||||
async fn list_installed(&self) -> Result<Vec<PluginPackageRef>, PluginStoreError>;
|
||||
|
||||
/// Reads `idea-plugin.json` from a package root.
|
||||
async fn read_manifest(
|
||||
&self,
|
||||
package: &PluginPackageRef,
|
||||
) -> Result<PluginManifestBytes, PluginStoreError>;
|
||||
|
||||
/// Stages an archive for validation.
|
||||
async fn install_from_archive(
|
||||
&self,
|
||||
archive: &LocalPath,
|
||||
) -> Result<StagedPluginPackage, PluginStoreError>;
|
||||
|
||||
/// Stages a directory snapshot for validation.
|
||||
async fn install_from_directory(
|
||||
&self,
|
||||
dir: &LocalPath,
|
||||
) -> Result<StagedPluginPackage, PluginStoreError>;
|
||||
|
||||
/// Commits a staged install into `installed/<pluginId>`.
|
||||
async fn commit_install(
|
||||
&self,
|
||||
staged: StagedPluginPackage,
|
||||
plugin_id: &PluginId,
|
||||
) -> Result<PluginPackageRef, PluginStoreError>;
|
||||
|
||||
/// Removes a committed package.
|
||||
async fn remove_package(
|
||||
&self,
|
||||
plugin_id: &PluginId,
|
||||
) -> Result<RemovalOutcome, PluginStoreError>;
|
||||
|
||||
/// Builds a protocol URL for a bundle/asset.
|
||||
fn bundle_url(
|
||||
&self,
|
||||
plugin_id: &PluginId,
|
||||
entry: &RelativePath,
|
||||
hash: &ContentHash,
|
||||
) -> Result<PluginBundleUrl, PluginStoreError>;
|
||||
|
||||
/// Returns the global application data directory label when the adapter can
|
||||
/// expose it for manifest variable substitution.
|
||||
fn app_data_dir_label(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Store for the global plugin registry.
|
||||
#[async_trait]
|
||||
pub trait PluginRegistryStore: Send + Sync {
|
||||
/// Loads the registry, returning an empty document if missing.
|
||||
async fn load_registry(&self) -> Result<PluginRegistry, PluginRegistryError>;
|
||||
|
||||
/// Persists the registry.
|
||||
async fn save_registry(&self, registry: &PluginRegistry) -> Result<(), PluginRegistryError>;
|
||||
}
|
||||
|
||||
/// Manifest parser and validator.
|
||||
pub trait PluginManifestValidator: Send + Sync {
|
||||
/// Validates raw manifest bytes.
|
||||
fn validate(
|
||||
&self,
|
||||
bytes: &[u8],
|
||||
package_root: &PluginPackageRef,
|
||||
) -> Result<PluginManifest, PluginManifestError>;
|
||||
}
|
||||
|
||||
/// Supervisor for external MCP servers declared by plugins.
|
||||
#[async_trait]
|
||||
pub trait PluginMcpSupervisor: Send + Sync {
|
||||
/// Reconciles the desired active server set.
|
||||
async fn reconcile(
|
||||
&self,
|
||||
active_servers: Vec<PluginMcpServerSpec>,
|
||||
) -> Result<PluginMcpStatusSet, PluginMcpError>;
|
||||
|
||||
/// Stops every server owned by one plugin.
|
||||
async fn stop_plugin(&self, plugin_id: &PluginId) -> Result<(), PluginMcpError>;
|
||||
}
|
||||
|
||||
/// A single directory entry returned by [`FileSystem::list`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DirEntry {
|
||||
|
||||
Reference in New Issue
Block a user