Expand description
sen-plugin-host: WASM plugin host runtime for sen-rs
This crate provides the runtime for loading and executing WASM plugins. It enables building plugin-extensible CLI applications with minimal effort.
§Architecture
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────┤
│ PluginRegistry │
│ ├─ load_plugin("./plugins/hello.wasm") │
│ ├─ execute("hello", &["World"]) │
│ └─ list_commands() │
├─────────────────────────────────────────────────────────────┤
│ HotReloadWatcher (optional) │
│ └─ Watches directories, auto-loads/unloads plugins │
├─────────────────────────────────────────────────────────────┤
│ PluginLoader │
│ ├─ Compiles WASM modules │
│ ├─ Validates API version │
│ └─ Creates sandboxed instances │
├─────────────────────────────────────────────────────────────┤
│ wasmtime (sandboxed execution) │
│ ├─ CPU limits (fuel) │
│ ├─ Stack limits (1MB) │
│ └─ Memory isolation │
└─────────────────────────────────────────────────────────────┘§Features
- Plugin Loading: Load and execute WASM plugins with sandboxed execution
- Hot Reload: Automatically reload plugins when files change
- Plugin Discovery: Scan directories for plugin files
- CPU Limits: Prevent infinite loops with fuel-based execution limits
- Stack Limits: Prevent stack overflow attacks (1MB limit)
sen-integration: Enable integration with sen-rs Router (addsbridgemodule)
§Security
Plugins run in a sandboxed WASM environment with multiple protections:
| Protection | Description |
|---|---|
| CPU Limit | 10M instructions per execution (fuel) |
| Stack Limit | 1MB maximum WASM stack |
| Memory Isolation | Each plugin has isolated linear memory |
| No System Access | No filesystem, network, or OS access |
| API Versioning | Rejects plugins with incompatible API versions |
§Quick Start
§Building Plugins
See the sen-plugin-sdk crate for detailed plugin development guide.
# Add the WASM target (one-time setup)
rustup target add wasm32-unknown-unknown
# Build plugin
cargo build --release --target wasm32-unknown-unknown§Loading and Executing Plugins
§Direct Loading
ⓘ
use sen_plugin_host::PluginLoader;
let loader = PluginLoader::new()?;
let wasm_bytes = std::fs::read("plugin.wasm")?;
let mut plugin = loader.load(&wasm_bytes)?;
// Check plugin metadata
println!("Command: {}", plugin.manifest.command.name);
println!("Description: {}", plugin.manifest.command.about);
// Execute
let result = plugin.instance.execute(&["World".to_string()])?;
match result {
ExecuteResult::Success(output) => println!("{}", output),
ExecuteResult::Error(err) => eprintln!("Error: {}", err.message),
}§Using Registry (Recommended)
ⓘ
use sen_plugin_host::PluginRegistry;
let registry = PluginRegistry::new()?;
// Load plugins
registry.load_plugin("./plugins/hello.wasm").await?;
registry.load_plugin("./plugins/greet.wasm").await?;
// List available commands
for cmd in registry.list_commands().await {
println!(" {}", cmd);
}
// Execute by command name
let result = registry.execute("hello", &["World".to_string()]).await?;§Hot Reload
Automatically load/reload/unload plugins when files change:
ⓘ
use sen_plugin_host::{PluginRegistry, HotReloadWatcher, WatcherConfig};
use std::time::Duration;
let registry = PluginRegistry::new()?;
// Watch directory for plugin changes
let _watcher = HotReloadWatcher::new(
registry.clone(),
vec!["./plugins"],
WatcherConfig {
debounce: Duration::from_millis(300),
load_existing: true, // Load existing plugins on start
},
).await?;
// Plugins are now automatically managed:
// - New .wasm files are loaded
// - Modified .wasm files are reloaded
// - Deleted .wasm files are unloaded
// Your application continues running...
loop {
let cmd = read_user_input();
if let Ok(result) = registry.execute(&cmd, &args).await {
// Handle result
}
}§Plugin Discovery
Scan directories for plugins without hot reload:
ⓘ
use sen_plugin_host::PluginScanner;
let scanner = PluginScanner::new()?;
let result = scanner.scan_directory("./plugins")?;
println!("Found {} plugins", result.plugins.len());
for plugin in &result.plugins {
println!(" - {} ({})", plugin.manifest.command.name, plugin.manifest.command.about);
}
if !result.failures.is_empty() {
eprintln!("Failed to load {} plugins:", result.failures.len());
for (path, error) in &result.failures {
eprintln!(" - {}: {}", path.display(), error);
}
}§sen-rs Router Integration
Enable the sen-integration feature to integrate plugins with sen-rs Router:
[dependencies]
sen-plugin-host = { version = "0.7", features = ["sen-integration"] }ⓘ
use sen::Router;
use sen_plugin_host::{PluginLoader, RouterPluginExt};
let loader = PluginLoader::new()?;
let plugin = loader.load(&wasm_bytes)?;
// Register plugin as a router command
let router = Router::new()
.plugin(plugin)
.with_state(MyState);
// Plugin command is now available via the router
let response = router.execute_with(&["app", "hello", "World"]).await;§Examples
See examples/wasm-cli/ for a complete example of a plugin-extensible CLI
with hot reload support.
Re-exports§
pub use discovery::default_plugin_dirs;pub use discovery::DiscoveryError;pub use discovery::DiscoveryResult;pub use discovery::PluginScanner;pub use loader::EffectHandler;pub use loader::LoadedPlugin;pub use loader::LoaderError;pub use loader::PluginInstance;pub use loader::PluginLoader;pub use registry::PluginRegistry;pub use registry::RegistryError;pub use watcher::HotReloadWatcher;pub use watcher::WatcherConfig;pub use watcher::WatcherError;pub use permission::PermissionConfig;pub use permission::PermissionConfigBuilder;pub use permission::PermissionPresets;
Modules§
- audit
- Audit system for tracking permission events
- discovery
- Plugin discovery and directory scanning
- loader
- Plugin loader using wasmtime
- permission
- Permission system for plugin capabilities
- registry
- Plugin registry with hot reload support
- watcher
- Hot reload file watcher for plugins
Structs§
- ArgSpec
- Argument specification
- Capabilities
- Plugin capability declarations
- Command
Spec - Command specification returned by plugin’s
manifest()function - Execute
Error - Error details from plugin execution
- Http
Response - HTTP response data
- NetPattern
- Network access pattern
- Path
Pattern - Filesystem path pattern
- Plugin
Manifest - Plugin manifest with API version
- Stdio
Capability - Standard I/O capability flags
Enums§
- Effect
- Effect request from plugin to host
- Effect
Result - Result of an effect, passed back to plugin via resume
- Execute
Result - Result of plugin execution