From c857b64bb43829a5cf40683d6d70e6059ec712e5 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Tue, 17 Mar 2026 11:54:20 -0400 Subject: [PATCH 1/8] feat(plugins): add Extism dependency, feature flag, and plugin module skeleton Introduce the WASM plugin system foundation: - Add extism 1.9 as an optional dependency behind `plugins-wasm` feature - Create `src/plugins/` module with manifest types, error types, and stub host - Add `Plugin` CLI subcommands (list, install, remove, info) behind cfg gate - Add `PluginsConfig` to the config schema with sensible defaults All plugin code is behind `#[cfg(feature = "plugins-wasm")]` so the default build is unaffected. Co-Authored-By: Claude Opus 4.6 --- Cargo.toml | 5 +++ src/config/mod.rs | 15 +++---- src/config/schema.rs | 43 ++++++++++++++++++++ src/lib.rs | 3 ++ src/main.rs | 79 +++++++++++++++++++++++++++++++++++++ src/onboard/wizard.rs | 2 + src/plugins/error.rs | 33 ++++++++++++++++ src/plugins/host.rs | 48 ++++++++++++++++++++++ src/plugins/mod.rs | 76 +++++++++++++++++++++++++++++++++++ src/plugins/wasm_channel.rs | 3 ++ src/plugins/wasm_tool.rs | 3 ++ 11 files changed, 303 insertions(+), 7 deletions(-) create mode 100644 src/plugins/error.rs create mode 100644 src/plugins/host.rs create mode 100644 src/plugins/mod.rs create mode 100644 src/plugins/wasm_channel.rs create mode 100644 src/plugins/wasm_tool.rs diff --git a/Cargo.toml b/Cargo.toml index 2c479d638..232b866ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -190,6 +190,9 @@ probe-rs = { version = "0.31", optional = true } # PDF extraction for datasheet RAG (optional, enable with --features rag-pdf) pdf-extract = { version = "0.10", optional = true } +# WASM plugin runtime (extism) +extism = { version = "1.9", optional = true } + # Terminal QR rendering for WhatsApp Web pairing flow. qrcode = { version = "0.14", optional = true } @@ -239,6 +242,8 @@ probe = ["dep:probe-rs"] rag-pdf = ["dep:pdf-extract"] # whatsapp-web = Native WhatsApp Web client with custom rusqlite storage backend whatsapp-web = ["dep:wa-rs", "dep:wa-rs-core", "dep:wa-rs-binary", "dep:wa-rs-proto", "dep:wa-rs-ureq-http", "dep:wa-rs-tokio-transport", "dep:serde-big-array", "dep:prost", "dep:qrcode"] +# WASM plugin system (extism-based) +plugins-wasm = ["dep:extism"] [profile.release] opt-level = "z" # Optimize for size diff --git a/src/config/mod.rs b/src/config/mod.rs index 7130c6dce..c999783b5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -19,13 +19,14 @@ pub use schema::{ McpServerConfig, McpTransport, MemoryConfig, Microsoft365Config, ModelRouteConfig, MultimodalConfig, NextcloudTalkConfig, NodeTransportConfig, NodesConfig, NotionConfig, ObservabilityConfig, OpenAiSttConfig, OpenAiTtsConfig, OpenVpnTunnelConfig, OtpConfig, - OtpMethod, PeripheralBoardConfig, PeripheralsConfig, ProjectIntelConfig, ProxyConfig, - ProxyScope, QdrantConfig, QueryClassificationConfig, ReliabilityConfig, ResourceLimitsConfig, - RuntimeConfig, SandboxBackend, SandboxConfig, SchedulerConfig, SecretsConfig, SecurityConfig, - SecurityOpsConfig, SkillsConfig, SkillsPromptInjectionMode, SlackConfig, StorageConfig, - StorageProviderConfig, StorageProviderSection, StreamMode, SwarmConfig, SwarmStrategy, - TelegramConfig, ToolFilterGroup, ToolFilterGroupMode, TranscriptionConfig, TtsConfig, - TunnelConfig, WebFetchConfig, WebSearchConfig, WebhookConfig, WorkspaceConfig, + OtpMethod, PeripheralBoardConfig, PeripheralsConfig, PluginsConfig, ProjectIntelConfig, + ProxyConfig, ProxyScope, QdrantConfig, QueryClassificationConfig, ReliabilityConfig, + ResourceLimitsConfig, RuntimeConfig, SandboxBackend, SandboxConfig, SchedulerConfig, + SecretsConfig, SecurityConfig, SecurityOpsConfig, SkillsConfig, SkillsPromptInjectionMode, + SlackConfig, StorageConfig, StorageProviderConfig, StorageProviderSection, StreamMode, + SwarmConfig, SwarmStrategy, TelegramConfig, ToolFilterGroup, ToolFilterGroupMode, + TranscriptionConfig, TtsConfig, TunnelConfig, WebFetchConfig, WebSearchConfig, WebhookConfig, + WorkspaceConfig, }; pub fn name_and_presence(channel: Option<&T>) -> (&'static str, bool) { diff --git a/src/config/schema.rs b/src/config/schema.rs index 3cc17142d..dc9b2ae0e 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -335,6 +335,10 @@ pub struct Config { /// LinkedIn integration configuration (`[linkedin]`). #[serde(default)] pub linkedin: LinkedInConfig, + + /// Plugin system configuration (`[plugins]`). + #[serde(default)] + pub plugins: PluginsConfig, } /// Multi-client workspace isolation configuration. @@ -2357,6 +2361,42 @@ fn default_linkedin_api_version() -> String { "202602".to_string() } +/// Plugin system configuration. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PluginsConfig { + /// Enable the plugin system (default: false) + #[serde(default)] + pub enabled: bool, + /// Directory where plugins are stored + #[serde(default = "default_plugins_dir")] + pub plugins_dir: String, + /// Auto-discover and load plugins on startup + #[serde(default)] + pub auto_discover: bool, + /// Maximum number of plugins that can be loaded + #[serde(default = "default_max_plugins")] + pub max_plugins: usize, +} + +fn default_plugins_dir() -> String { + "~/.zeroclaw/plugins".to_string() +} + +fn default_max_plugins() -> usize { + 50 +} + +impl Default for PluginsConfig { + fn default() -> Self { + Self { + enabled: false, + plugins_dir: default_plugins_dir(), + auto_discover: false, + max_plugins: default_max_plugins(), + } + } +} + /// Content strategy configuration for LinkedIn auto-posting (`[linkedin.content]`). /// /// The agent reads this via the `linkedin get_content_strategy` action to know @@ -5950,6 +5990,7 @@ impl Default for Config { node_transport: NodeTransportConfig::default(), knowledge: KnowledgeConfig::default(), linkedin: LinkedInConfig::default(), + plugins: PluginsConfig::default(), } } } @@ -8385,6 +8426,7 @@ default_temperature = 0.7 node_transport: NodeTransportConfig::default(), knowledge: KnowledgeConfig::default(), linkedin: LinkedInConfig::default(), + plugins: PluginsConfig::default(), }; let toml_str = toml::to_string_pretty(&config).unwrap(); @@ -8717,6 +8759,7 @@ tool_dispatcher = "xml" node_transport: NodeTransportConfig::default(), knowledge: KnowledgeConfig::default(), linkedin: LinkedInConfig::default(), + plugins: PluginsConfig::default(), }; config.save().await.unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 7f11fd009..73d21e44f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,9 @@ pub mod tools; pub(crate) mod tunnel; pub(crate) mod util; +#[cfg(feature = "plugins-wasm")] +pub mod plugins; + pub use config::Config; /// Gateway management subcommands diff --git a/src/main.rs b/src/main.rs index 5df2d3fdd..c8698512d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -528,6 +528,35 @@ Examples: #[arg(value_enum)] shell: CompletionShell, }, + + /// Manage WASM plugins + #[cfg(feature = "plugins-wasm")] + Plugin { + #[command(subcommand)] + plugin_command: PluginCommands, + }, +} + +#[cfg(feature = "plugins-wasm")] +#[derive(Subcommand, Debug)] +enum PluginCommands { + /// List installed plugins + List, + /// Install a plugin from a directory or URL + Install { + /// Path to plugin directory or manifest + source: String, + }, + /// Remove an installed plugin + Remove { + /// Plugin name + name: String, + }, + /// Show information about a plugin + Info { + /// Plugin name + name: String, + }, } #[derive(Subcommand, Debug)] @@ -1325,6 +1354,56 @@ async fn main() -> Result<()> { Ok(()) } }, + + #[cfg(feature = "plugins-wasm")] + Commands::Plugin { plugin_command } => match plugin_command { + PluginCommands::List => { + let host = zeroclaw::plugins::host::PluginHost::new(&config.workspace_dir)?; + let plugins = host.list_plugins(); + if plugins.is_empty() { + println!("No plugins installed."); + } else { + println!("Installed plugins:"); + for p in &plugins { + println!( + " {} v{} — {}", + p.name, + p.version, + p.description.as_deref().unwrap_or("(no description)") + ); + } + } + Ok(()) + } + PluginCommands::Install { source } => { + let mut host = zeroclaw::plugins::host::PluginHost::new(&config.workspace_dir)?; + host.install(&source)?; + println!("Plugin installed from {source}"); + Ok(()) + } + PluginCommands::Remove { name } => { + let mut host = zeroclaw::plugins::host::PluginHost::new(&config.workspace_dir)?; + host.remove(&name)?; + println!("Plugin '{name}' removed."); + Ok(()) + } + PluginCommands::Info { name } => { + let host = zeroclaw::plugins::host::PluginHost::new(&config.workspace_dir)?; + match host.get_plugin(&name) { + Some(info) => { + println!("Plugin: {} v{}", info.name, info.version); + if let Some(desc) = &info.description { + println!("Description: {desc}"); + } + println!("Capabilities: {:?}", info.capabilities); + println!("Permissions: {:?}", info.permissions); + println!("WASM: {}", info.wasm_path.display()); + } + None => println!("Plugin '{name}' not found."), + } + Ok(()) + } + }, } } diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index 26606ba0a..10e931412 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -192,6 +192,7 @@ pub async fn run_wizard(force: bool) -> Result { node_transport: crate::config::NodeTransportConfig::default(), knowledge: crate::config::KnowledgeConfig::default(), linkedin: crate::config::LinkedInConfig::default(), + plugins: crate::config::PluginsConfig::default(), }; println!( @@ -565,6 +566,7 @@ async fn run_quick_setup_with_home( node_transport: crate::config::NodeTransportConfig::default(), knowledge: crate::config::KnowledgeConfig::default(), linkedin: crate::config::LinkedInConfig::default(), + plugins: crate::config::PluginsConfig::default(), }; config.save().await?; diff --git a/src/plugins/error.rs b/src/plugins/error.rs new file mode 100644 index 000000000..2c7981127 --- /dev/null +++ b/src/plugins/error.rs @@ -0,0 +1,33 @@ +//! Plugin error types. + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum PluginError { + #[error("plugin not found: {0}")] + NotFound(String), + + #[error("invalid manifest: {0}")] + InvalidManifest(String), + + #[error("failed to load WASM module: {0}")] + LoadFailed(String), + + #[error("plugin execution failed: {0}")] + ExecutionFailed(String), + + #[error("permission denied: plugin '{plugin}' requires '{permission}'")] + PermissionDenied { plugin: String, permission: String }, + + #[error("plugin '{0}' is already loaded")] + AlreadyLoaded(String), + + #[error("plugin capability not supported: {0}")] + UnsupportedCapability(String), + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("TOML parse error: {0}")] + TomlParse(#[from] toml::de::Error), +} diff --git a/src/plugins/host.rs b/src/plugins/host.rs new file mode 100644 index 000000000..297adab51 --- /dev/null +++ b/src/plugins/host.rs @@ -0,0 +1,48 @@ +//! Plugin host: discovery, loading, lifecycle management. + +use super::error::PluginError; +use super::PluginInfo; +use std::path::{Path, PathBuf}; + +/// Manages the lifecycle of WASM plugins. +pub struct PluginHost { + plugins_dir: PathBuf, +} + +impl PluginHost { + /// Create a new plugin host with the given workspace directory. + pub fn new(workspace_dir: &Path) -> Result { + let plugins_dir = workspace_dir.join("plugins"); + if !plugins_dir.exists() { + std::fs::create_dir_all(&plugins_dir)?; + } + Ok(Self { plugins_dir }) + } + + /// List all discovered plugins. + pub fn list_plugins(&self) -> Vec { + Vec::new() + } + + /// Get info about a specific plugin. + pub fn get_plugin(&self, _name: &str) -> Option { + None + } + + /// Install a plugin from a directory path. + pub fn install(&mut self, _source: &str) -> Result<(), PluginError> { + Err(PluginError::LoadFailed( + "plugin host not yet fully implemented".into(), + )) + } + + /// Remove a plugin by name. + pub fn remove(&mut self, name: &str) -> Result<(), PluginError> { + Err(PluginError::NotFound(name.to_string())) + } + + /// Returns the plugins directory path. + pub fn plugins_dir(&self) -> &Path { + &self.plugins_dir + } +} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs new file mode 100644 index 000000000..56c2fc500 --- /dev/null +++ b/src/plugins/mod.rs @@ -0,0 +1,76 @@ +//! WASM plugin system for ZeroClaw. +//! +//! Plugins are WebAssembly modules loaded via Extism that can extend +//! ZeroClaw with custom tools and channels. Enable with `--features plugins-wasm`. + +pub mod error; +pub mod host; +pub mod wasm_channel; +pub mod wasm_tool; + +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +/// A plugin's declared manifest (loaded from manifest.toml alongside the .wasm). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PluginManifest { + /// Plugin name (unique identifier) + pub name: String, + /// Plugin version + pub version: String, + /// Human-readable description + pub description: Option, + /// Author name or organization + pub author: Option, + /// Path to the .wasm file (relative to manifest) + pub wasm_path: String, + /// Capabilities this plugin provides + pub capabilities: Vec, + /// Permissions this plugin requests + #[serde(default)] + pub permissions: Vec, +} + +/// What a plugin can do. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum PluginCapability { + /// Provides one or more tools + Tool, + /// Provides a channel implementation + Channel, + /// Provides a memory backend + Memory, + /// Provides an observer/metrics backend + Observer, +} + +/// Permissions a plugin may request. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum PluginPermission { + /// Can make HTTP requests + HttpClient, + /// Can read from the filesystem (within sandbox) + FileRead, + /// Can write to the filesystem (within sandbox) + FileWrite, + /// Can access environment variables + EnvRead, + /// Can read agent memory + MemoryRead, + /// Can write agent memory + MemoryWrite, +} + +/// Information about a loaded plugin. +#[derive(Debug, Clone, Serialize)] +pub struct PluginInfo { + pub name: String, + pub version: String, + pub description: Option, + pub capabilities: Vec, + pub permissions: Vec, + pub wasm_path: PathBuf, + pub loaded: bool, +} diff --git a/src/plugins/wasm_channel.rs b/src/plugins/wasm_channel.rs new file mode 100644 index 000000000..7762c3539 --- /dev/null +++ b/src/plugins/wasm_channel.rs @@ -0,0 +1,3 @@ +//! Bridge between WASM plugins and the Channel trait. +//! +//! Placeholder — full implementation in a follow-up commit. diff --git a/src/plugins/wasm_tool.rs b/src/plugins/wasm_tool.rs new file mode 100644 index 000000000..c958436b1 --- /dev/null +++ b/src/plugins/wasm_tool.rs @@ -0,0 +1,3 @@ +//! Bridge between WASM plugins and the Tool trait. +//! +//! Placeholder — full implementation in a follow-up commit. From b3bb79d805ba869d8f998ba563169bd839097311 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Tue, 17 Mar 2026 11:55:41 -0400 Subject: [PATCH 2/8] feat(plugins): add PluginHost, WasmTool, and WasmChannel bridges Implement the core plugin infrastructure: - PluginHost: discovers plugins from the workspace plugins directory, loads manifest.toml files, supports install/remove/list/info operations - WasmTool: bridges WASM plugins to the Tool trait (execute stub pending Extism runtime wiring) - WasmChannel: bridges WASM plugins to the Channel trait (send/listen stubs pending Extism runtime wiring) Co-Authored-By: Claude Opus 4.6 --- src/plugins/host.rs | 168 +++++++++++++++++++++++++++++++++--- src/plugins/wasm_channel.rs | 45 +++++++++- src/plugins/wasm_tool.rs | 64 +++++++++++++- 3 files changed, 262 insertions(+), 15 deletions(-) diff --git a/src/plugins/host.rs b/src/plugins/host.rs index 297adab51..5b31c4e89 100644 --- a/src/plugins/host.rs +++ b/src/plugins/host.rs @@ -1,44 +1,190 @@ //! Plugin host: discovery, loading, lifecycle management. use super::error::PluginError; -use super::PluginInfo; +use super::{PluginCapability, PluginInfo, PluginManifest}; +use std::collections::HashMap; use std::path::{Path, PathBuf}; /// Manages the lifecycle of WASM plugins. pub struct PluginHost { plugins_dir: PathBuf, + loaded: HashMap, +} + +struct LoadedPlugin { + manifest: PluginManifest, + wasm_path: PathBuf, } impl PluginHost { - /// Create a new plugin host with the given workspace directory. + /// Create a new plugin host with the given plugins directory. pub fn new(workspace_dir: &Path) -> Result { let plugins_dir = workspace_dir.join("plugins"); if !plugins_dir.exists() { std::fs::create_dir_all(&plugins_dir)?; } - Ok(Self { plugins_dir }) + + let mut host = Self { + plugins_dir, + loaded: HashMap::new(), + }; + + host.discover()?; + Ok(host) + } + + /// Discover plugins in the plugins directory. + fn discover(&mut self) -> Result<(), PluginError> { + if !self.plugins_dir.exists() { + return Ok(()); + } + + let entries = std::fs::read_dir(&self.plugins_dir)?; + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + let manifest_path = path.join("manifest.toml"); + if manifest_path.exists() { + if let Ok(manifest) = self.load_manifest(&manifest_path) { + let wasm_path = path.join(&manifest.wasm_path); + self.loaded.insert( + manifest.name.clone(), + LoadedPlugin { + manifest, + wasm_path, + }, + ); + } + } + } + } + + Ok(()) + } + + fn load_manifest(&self, path: &Path) -> Result { + let content = std::fs::read_to_string(path)?; + let manifest: PluginManifest = toml::from_str(&content)?; + Ok(manifest) } /// List all discovered plugins. pub fn list_plugins(&self) -> Vec { - Vec::new() + self.loaded + .values() + .map(|p| PluginInfo { + name: p.manifest.name.clone(), + version: p.manifest.version.clone(), + description: p.manifest.description.clone(), + capabilities: p.manifest.capabilities.clone(), + permissions: p.manifest.permissions.clone(), + wasm_path: p.wasm_path.clone(), + loaded: p.wasm_path.exists(), + }) + .collect() } /// Get info about a specific plugin. - pub fn get_plugin(&self, _name: &str) -> Option { - None + pub fn get_plugin(&self, name: &str) -> Option { + self.loaded.get(name).map(|p| PluginInfo { + name: p.manifest.name.clone(), + version: p.manifest.version.clone(), + description: p.manifest.description.clone(), + capabilities: p.manifest.capabilities.clone(), + permissions: p.manifest.permissions.clone(), + wasm_path: p.wasm_path.clone(), + loaded: p.wasm_path.exists(), + }) } /// Install a plugin from a directory path. - pub fn install(&mut self, _source: &str) -> Result<(), PluginError> { - Err(PluginError::LoadFailed( - "plugin host not yet fully implemented".into(), - )) + pub fn install(&mut self, source: &str) -> Result<(), PluginError> { + let source_path = PathBuf::from(source); + let manifest_path = if source_path.is_dir() { + source_path.join("manifest.toml") + } else { + source_path.clone() + }; + + if !manifest_path.exists() { + return Err(PluginError::NotFound(format!( + "manifest.toml not found at {}", + manifest_path.display() + ))); + } + + let manifest = self.load_manifest(&manifest_path)?; + let source_dir = manifest_path + .parent() + .ok_or_else(|| PluginError::InvalidManifest("no parent directory".into()))?; + + let wasm_source = source_dir.join(&manifest.wasm_path); + if !wasm_source.exists() { + return Err(PluginError::NotFound(format!( + "WASM file not found: {}", + wasm_source.display() + ))); + } + + if self.loaded.contains_key(&manifest.name) { + return Err(PluginError::AlreadyLoaded(manifest.name)); + } + + // Copy plugin to plugins directory + let dest_dir = self.plugins_dir.join(&manifest.name); + std::fs::create_dir_all(&dest_dir)?; + + // Copy manifest + std::fs::copy(&manifest_path, dest_dir.join("manifest.toml"))?; + + // Copy WASM file + let wasm_dest = dest_dir.join(&manifest.wasm_path); + if let Some(parent) = wasm_dest.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::copy(&wasm_source, &wasm_dest)?; + + self.loaded.insert( + manifest.name.clone(), + LoadedPlugin { + manifest, + wasm_path: wasm_dest, + }, + ); + + Ok(()) } /// Remove a plugin by name. pub fn remove(&mut self, name: &str) -> Result<(), PluginError> { - Err(PluginError::NotFound(name.to_string())) + if self.loaded.remove(name).is_none() { + return Err(PluginError::NotFound(name.to_string())); + } + + let plugin_dir = self.plugins_dir.join(name); + if plugin_dir.exists() { + std::fs::remove_dir_all(plugin_dir)?; + } + + Ok(()) + } + + /// Get tool-capable plugins. + pub fn tool_plugins(&self) -> Vec<&PluginManifest> { + self.loaded + .values() + .filter(|p| p.manifest.capabilities.contains(&PluginCapability::Tool)) + .map(|p| &p.manifest) + .collect() + } + + /// Get channel-capable plugins. + pub fn channel_plugins(&self) -> Vec<&PluginManifest> { + self.loaded + .values() + .filter(|p| p.manifest.capabilities.contains(&PluginCapability::Channel)) + .map(|p| &p.manifest) + .collect() } /// Returns the plugins directory path. diff --git a/src/plugins/wasm_channel.rs b/src/plugins/wasm_channel.rs index 7762c3539..72b0469c6 100644 --- a/src/plugins/wasm_channel.rs +++ b/src/plugins/wasm_channel.rs @@ -1,3 +1,44 @@ //! Bridge between WASM plugins and the Channel trait. -//! -//! Placeholder — full implementation in a follow-up commit. + +use crate::channels::traits::{Channel, ChannelMessage, SendMessage}; +use async_trait::async_trait; + +/// A channel backed by a WASM plugin. +pub struct WasmChannel { + name: String, + plugin_name: String, +} + +impl WasmChannel { + pub fn new(name: String, plugin_name: String) -> Self { + Self { name, plugin_name } + } +} + +#[async_trait] +impl Channel for WasmChannel { + fn name(&self) -> &str { + &self.name + } + + async fn send(&self, message: &SendMessage) -> anyhow::Result<()> { + // TODO: Wire to WASM plugin send function + tracing::warn!( + "WasmChannel '{}' (plugin: {}) send not yet connected: {}", + self.name, + self.plugin_name, + message.content + ); + Ok(()) + } + + async fn listen(&self, _tx: tokio::sync::mpsc::Sender) -> anyhow::Result<()> { + // TODO: Wire to WASM plugin receive/listen function + tracing::warn!( + "WasmChannel '{}' (plugin: {}) listen not yet connected", + self.name, + self.plugin_name, + ); + Ok(()) + } +} diff --git a/src/plugins/wasm_tool.rs b/src/plugins/wasm_tool.rs index c958436b1..484950dc0 100644 --- a/src/plugins/wasm_tool.rs +++ b/src/plugins/wasm_tool.rs @@ -1,3 +1,63 @@ //! Bridge between WASM plugins and the Tool trait. -//! -//! Placeholder — full implementation in a follow-up commit. + +use crate::tools::traits::{Tool, ToolResult}; +use async_trait::async_trait; +use serde_json::Value; + +/// A tool backed by a WASM plugin function. +pub struct WasmTool { + name: String, + description: String, + plugin_name: String, + function_name: String, + parameters_schema: Value, +} + +impl WasmTool { + pub fn new( + name: String, + description: String, + plugin_name: String, + function_name: String, + parameters_schema: Value, + ) -> Self { + Self { + name, + description, + plugin_name, + function_name, + parameters_schema, + } + } +} + +#[async_trait] +impl Tool for WasmTool { + fn name(&self) -> &str { + &self.name + } + + fn description(&self) -> &str { + &self.description + } + + fn parameters_schema(&self) -> Value { + self.parameters_schema.clone() + } + + async fn execute(&self, args: Value) -> anyhow::Result { + // TODO: Call into Extism plugin runtime + // For now, return a placeholder indicating the plugin system is available + // but not yet wired to actual WASM execution. + Ok(ToolResult { + success: false, + output: format!( + "[plugin:{}/{}] WASM execution not yet connected. Args: {}", + self.plugin_name, + self.function_name, + serde_json::to_string(&args).unwrap_or_default() + ), + error: Some("WASM execution bridge not yet implemented".into()), + }) + } +} From dcf66175e4ceee69e1376186b51bb849498ac7f1 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Tue, 17 Mar 2026 12:00:08 -0400 Subject: [PATCH 3/8] feat(plugins): add example weather plugin and manifest Add a standalone example plugin demonstrating the WASM plugin interface: - example-plugin/Cargo.toml: cdylib crate targeting wasm32-wasip1 - example-plugin/src/lib.rs: mock weather tool using extism-pdk - example-plugin/manifest.toml: plugin manifest declaring tool capability This crate is intentionally NOT added to the workspace members since it targets wasm32-wasip1 and would break the main build. Co-Authored-By: Claude Opus 4.6 --- example-plugin/Cargo.toml | 12 +++++++++++ example-plugin/manifest.toml | 8 +++++++ example-plugin/src/lib.rs | 42 ++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 example-plugin/Cargo.toml create mode 100644 example-plugin/manifest.toml create mode 100644 example-plugin/src/lib.rs diff --git a/example-plugin/Cargo.toml b/example-plugin/Cargo.toml new file mode 100644 index 000000000..54e7fcfd4 --- /dev/null +++ b/example-plugin/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "zeroclaw-weather-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +extism-pdk = "1.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/example-plugin/manifest.toml b/example-plugin/manifest.toml new file mode 100644 index 000000000..668db66fc --- /dev/null +++ b/example-plugin/manifest.toml @@ -0,0 +1,8 @@ +name = "weather" +version = "0.1.0" +description = "Example weather tool plugin for ZeroClaw" +author = "ZeroClaw Labs" +wasm_path = "target/wasm32-wasip1/release/zeroclaw_weather_plugin.wasm" + +capabilities = ["tool"] +permissions = ["http_client"] diff --git a/example-plugin/src/lib.rs b/example-plugin/src/lib.rs new file mode 100644 index 000000000..5fb1d2c05 --- /dev/null +++ b/example-plugin/src/lib.rs @@ -0,0 +1,42 @@ +//! Example ZeroClaw weather plugin. +//! +//! Demonstrates how to create a WASM tool plugin using extism-pdk. +//! Build with: cargo build --target wasm32-wasip1 --release + +use extism_pdk::*; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize)] +struct WeatherInput { + location: String, +} + +#[derive(Serialize)] +struct WeatherOutput { + location: String, + temperature: f64, + unit: String, + condition: String, + humidity: u32, +} + +/// Get weather for a location (mock implementation for demonstration). +#[plugin_fn] +pub fn get_weather(input: String) -> FnResult { + let params: WeatherInput = + serde_json::from_str(&input).map_err(|e| Error::msg(format!("invalid input: {e}")))?; + + // Mock weather data for demonstration + let output = WeatherOutput { + location: params.location, + temperature: 22.5, + unit: "celsius".to_string(), + condition: "Partly cloudy".to_string(), + humidity: 65, + }; + + let json = serde_json::to_string(&output) + .map_err(|e| Error::msg(format!("serialization error: {e}")))?; + + Ok(json) +} From 67edd2bc6094a0889cc7ab1bf0751e4a52fc29e2 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Tue, 17 Mar 2026 12:57:43 -0400 Subject: [PATCH 4/8] fix(plugins): integrate WASM tools into registry, add gateway routes and tests - Wire WASM plugin tools into all_tools_with_runtime() behind cfg(feature = "plugins-wasm"), discovering and registering tool-capable plugins from the configured plugins directory at startup. - Add /api/plugins gateway endpoint (cfg-gated) for listing plugin status. - Add mod plugins declaration to main.rs binary crate so crate::plugins resolves when the feature is enabled. - Add unit tests for PluginHost: empty dir, manifest discovery, capability filtering, lookup, and removal. Co-Authored-By: Claude Opus 4.6 --- src/gateway/api_plugins.rs | 77 ++++++++++++++++++++++ src/gateway/mod.rs | 13 +++- src/main.rs | 2 + src/plugins/host.rs | 131 +++++++++++++++++++++++++++++++++++++ src/tools/mod.rs | 47 +++++++++++++ 5 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 src/gateway/api_plugins.rs diff --git a/src/gateway/api_plugins.rs b/src/gateway/api_plugins.rs new file mode 100644 index 000000000..36891c49c --- /dev/null +++ b/src/gateway/api_plugins.rs @@ -0,0 +1,77 @@ +//! Plugin management API routes (requires `plugins-wasm` feature). + +#[cfg(feature = "plugins-wasm")] +pub mod plugin_routes { + use axum::{ + extract::State, + http::{header, HeaderMap, StatusCode}, + response::{IntoResponse, Json}, + }; + + use super::super::AppState; + + /// `GET /api/plugins` — list loaded plugins and their status. + pub async fn list_plugins( + State(state): State, + headers: HeaderMap, + ) -> impl IntoResponse { + // Auth check + if state.pairing.require_pairing() { + let token = headers + .get(header::AUTHORIZATION) + .and_then(|v| v.to_str().ok()) + .and_then(|auth| auth.strip_prefix("Bearer ")) + .unwrap_or(""); + if !state.pairing.is_authenticated(token) { + return (StatusCode::UNAUTHORIZED, "Unauthorized").into_response(); + } + } + + let config = state.config.lock(); + let plugins_enabled = config.plugins.enabled; + let plugins_dir = config.plugins.plugins_dir.clone(); + drop(config); + + let plugins: Vec = if plugins_enabled { + let plugin_path = if plugins_dir.starts_with("~/") { + directories::UserDirs::new() + .map(|u| u.home_dir().join(&plugins_dir[2..])) + .unwrap_or_else(|| std::path::PathBuf::from(&plugins_dir)) + } else { + std::path::PathBuf::from(&plugins_dir) + }; + + if plugin_path.exists() { + match crate::plugins::host::PluginHost::new( + plugin_path.parent().unwrap_or(&plugin_path), + ) { + Ok(host) => host + .list_plugins() + .into_iter() + .map(|p| { + serde_json::json!({ + "name": p.name, + "version": p.version, + "description": p.description, + "capabilities": p.capabilities, + "loaded": p.loaded, + }) + }) + .collect(), + Err(_) => vec![], + } + } else { + vec![] + } + } else { + vec![] + }; + + Json(serde_json::json!({ + "plugins_enabled": plugins_enabled, + "plugins_dir": plugins_dir, + "plugins": plugins, + })) + .into_response() + } +} diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 112e6394a..63c37333b 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -9,6 +9,8 @@ pub mod api; pub mod api_pairing; +#[cfg(feature = "plugins-wasm")] +pub mod api_plugins; pub mod nodes; pub mod sse; pub mod static_files; @@ -789,7 +791,16 @@ pub async fn run_gateway(host: &str, port: u16, config: Config) -> Result<()> { .route( "/api/devices/{id}/token/rotate", post(api_pairing::rotate_token), - ) + ); + + // ── Plugin management API (requires plugins-wasm feature) ── + #[cfg(feature = "plugins-wasm")] + let app = app.route( + "/api/plugins", + get(api_plugins::plugin_routes::list_plugins), + ); + + let app = app // ── SSE event stream ── .route("/api/events", get(sse::handle_sse_events)) // ── WebSocket agent chat ── diff --git a/src/main.rs b/src/main.rs index c8698512d..e574ca3d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,6 +97,8 @@ mod multimodal; mod observability; mod onboard; mod peripherals; +#[cfg(feature = "plugins-wasm")] +mod plugins; mod providers; mod runtime; mod security; diff --git a/src/plugins/host.rs b/src/plugins/host.rs index 5b31c4e89..87de3f58a 100644 --- a/src/plugins/host.rs +++ b/src/plugins/host.rs @@ -192,3 +192,134 @@ impl PluginHost { &self.plugins_dir } } + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + #[test] + fn test_empty_plugin_dir() { + let dir = tempdir().unwrap(); + let host = PluginHost::new(dir.path()).unwrap(); + assert!(host.list_plugins().is_empty()); + } + + #[test] + fn test_discover_with_manifest() { + let dir = tempdir().unwrap(); + let plugin_dir = dir.path().join("plugins").join("test-plugin"); + std::fs::create_dir_all(&plugin_dir).unwrap(); + + std::fs::write( + plugin_dir.join("manifest.toml"), + r#" +name = "test-plugin" +version = "0.1.0" +description = "A test plugin" +wasm_path = "plugin.wasm" +capabilities = ["tool"] +permissions = [] +"#, + ) + .unwrap(); + + let host = PluginHost::new(dir.path()).unwrap(); + let plugins = host.list_plugins(); + assert_eq!(plugins.len(), 1); + assert_eq!(plugins[0].name, "test-plugin"); + } + + #[test] + fn test_tool_plugins_filter() { + let dir = tempdir().unwrap(); + let plugins_base = dir.path().join("plugins"); + + // Tool plugin + let tool_dir = plugins_base.join("my-tool"); + std::fs::create_dir_all(&tool_dir).unwrap(); + std::fs::write( + tool_dir.join("manifest.toml"), + r#" +name = "my-tool" +version = "0.1.0" +wasm_path = "tool.wasm" +capabilities = ["tool"] +"#, + ) + .unwrap(); + + // Channel plugin + let chan_dir = plugins_base.join("my-channel"); + std::fs::create_dir_all(&chan_dir).unwrap(); + std::fs::write( + chan_dir.join("manifest.toml"), + r#" +name = "my-channel" +version = "0.1.0" +wasm_path = "channel.wasm" +capabilities = ["channel"] +"#, + ) + .unwrap(); + + let host = PluginHost::new(dir.path()).unwrap(); + assert_eq!(host.list_plugins().len(), 2); + assert_eq!(host.tool_plugins().len(), 1); + assert_eq!(host.channel_plugins().len(), 1); + assert_eq!(host.tool_plugins()[0].name, "my-tool"); + } + + #[test] + fn test_get_plugin() { + let dir = tempdir().unwrap(); + let plugin_dir = dir.path().join("plugins").join("lookup-test"); + std::fs::create_dir_all(&plugin_dir).unwrap(); + std::fs::write( + plugin_dir.join("manifest.toml"), + r#" +name = "lookup-test" +version = "1.0.0" +description = "Lookup test" +wasm_path = "plugin.wasm" +capabilities = ["tool"] +"#, + ) + .unwrap(); + + let host = PluginHost::new(dir.path()).unwrap(); + assert!(host.get_plugin("lookup-test").is_some()); + assert!(host.get_plugin("nonexistent").is_none()); + } + + #[test] + fn test_remove_plugin() { + let dir = tempdir().unwrap(); + let plugin_dir = dir.path().join("plugins").join("removable"); + std::fs::create_dir_all(&plugin_dir).unwrap(); + std::fs::write( + plugin_dir.join("manifest.toml"), + r#" +name = "removable" +version = "0.1.0" +wasm_path = "plugin.wasm" +capabilities = ["tool"] +"#, + ) + .unwrap(); + + let mut host = PluginHost::new(dir.path()).unwrap(); + assert_eq!(host.list_plugins().len(), 1); + + host.remove("removable").unwrap(); + assert!(host.list_plugins().is_empty()); + assert!(!plugin_dir.exists()); + } + + #[test] + fn test_remove_nonexistent_returns_error() { + let dir = tempdir().unwrap(); + let mut host = PluginHost::new(dir.path()).unwrap(); + assert!(host.remove("ghost").is_err()); + } +} diff --git a/src/tools/mod.rs b/src/tools/mod.rs index c4e44143c..7c5ae54d0 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -634,6 +634,53 @@ pub fn all_tools_with_runtime( ))); } + // ── WASM plugin tools (requires plugins-wasm feature) ── + #[cfg(feature = "plugins-wasm")] + { + let plugin_dir = config.plugins.plugins_dir.clone(); + let plugin_path = if plugin_dir.starts_with("~/") { + let home = directories::UserDirs::new() + .map(|u| u.home_dir().to_path_buf()) + .unwrap_or_else(|| std::path::PathBuf::from(".")); + home.join(&plugin_dir[2..]) + } else { + std::path::PathBuf::from(&plugin_dir) + }; + + if plugin_path.exists() && config.plugins.enabled { + match crate::plugins::host::PluginHost::new( + plugin_path.parent().unwrap_or(&plugin_path), + ) { + Ok(host) => { + let tool_manifests = host.tool_plugins(); + let count = tool_manifests.len(); + for manifest in tool_manifests { + tool_arcs.push(Arc::new(crate::plugins::wasm_tool::WasmTool::new( + manifest.name.clone(), + manifest.description.clone().unwrap_or_default(), + manifest.name.clone(), + "call".to_string(), + serde_json::json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "Input for the plugin" + } + }, + "required": ["input"] + }), + ))); + } + tracing::info!("Loaded {count} WASM plugin tools"); + } + Err(e) => { + tracing::warn!("Failed to load WASM plugins: {e}"); + } + } + } + } + (boxed_registry_from_arcs(tool_arcs), delegate_handle) } From dc50ca9171a547d90bafc0e69927c51a416c5cf1 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Tue, 17 Mar 2026 18:30:41 -0400 Subject: [PATCH 5/8] fix(plugins): update lockfile and fix ws.rs formatting Sync Cargo.lock with new Extism/WASM plugin dependencies and apply rustfmt line-wrap fix in gateway WebSocket handler. Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 1245 ++++++++++++++++++++++++++++++++++++++++++++- src/gateway/ws.rs | 3 +- 2 files changed, 1228 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5b2e928b..22add4298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -100,6 +109,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -200,6 +215,12 @@ dependencies = [ "object 0.37.3", ] +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "archery" version = "1.2.2" @@ -314,7 +335,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -681,6 +702,9 @@ name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytecount" @@ -735,6 +759,72 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" +[[package]] +name = "cap-fs-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "cap-primitives" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix 1.1.4", + "rustix-linux-procfs", + "windows-sys 0.59.0", + "winx", +] + +[[package]] +name = "cap-rand" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" +dependencies = [ + "ambient-authority", + "rand 0.8.5", +] + +[[package]] +name = "cap-std" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix 1.1.4", +] + +[[package]] +name = "cap-time-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" +dependencies = [ + "ambient-authority", + "cap-primitives", + "iana-time-zone", + "once_cell", + "rustix 1.1.4", + "winx", +] + [[package]] name = "cast" version = "0.3.0" @@ -750,6 +840,24 @@ dependencies = [ "cipher", ] +[[package]] +name = "cbindgen" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799" +dependencies = [ + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.117", + "tempfile", + "toml 0.9.12+spec-1.1.0", +] + [[package]] name = "cc" version = "1.2.57" @@ -945,6 +1053,15 @@ dependencies = [ "cc", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "cobs" version = "0.5.1" @@ -1097,6 +1214,15 @@ dependencies = [ "libm", ] +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1115,6 +1241,144 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-assembler-x64" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b27381757f9295b67e558f4c64a83bfe7c6e82daad1ba4f8a948482c5de56ee9" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2ef32a4dbf1b380632a889995156080ecc0f1e07ac8eaa3f6325e4bd14ad8a" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b71c01a8007dd54330c8d73edeb82a8fc1a7143884af2f319e97340e290939b" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-bitset" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fef6b39515a0ecfbb9954ab3d2d6740a459a11bef3d0536ef48460e6f6deb5" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-codegen" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2060d8c75772e5208a9d3b766d9eb975bfc18ac459b75a0a2b2a72769a2f6da6" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.15.5", + "log", + "pulley-interpreter", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", + "wasmtime-internal-math", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887e3ab41a8a75cb6b68c5fc686158b6083f1ad49cf52f2da7538fba17ff0be6" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck", + "pulley-interpreter", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b187cbec77058579b47e8f75b1ce430b0d110df9c38d0fee2f8bd9801fd673" + +[[package]] +name = "cranelift-control" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b421ad1fefa33a1bb278d761d8ad7d49e17b7089f652fc2a1536435c75ff8def" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e3a650a696c3f4c93bb869e7d219ba3abf6e247164aaf7f12dc918a1d52772" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-frontend" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d48f516c004656a85747f6f8ccf6e23d8ec0a0a6dcf75ec85d6f2fa7e12c91" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce7761455ec4977010db897e9ad925200f08e435b9fa17575bd269ba174f33b" + +[[package]] +name = "cranelift-native" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42be1df38c4db6e19ba19d5ab8e65950c2865da0ad9e972a99ef224f1f77b8af" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.124.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fee765d14f3f91dcba44c0e4b0eaece5f89024539b620af15a6aeec485b1170" + [[package]] name = "crc32fast" version = "1.5.0" @@ -1371,6 +1635,15 @@ dependencies = [ "deadpool-runtime", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + [[package]] name = "decancer" version = "3.3.3" @@ -1525,6 +1798,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1542,10 +1825,21 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.2", "windows-sys 0.61.2", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1655,6 +1949,18 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -1803,6 +2109,72 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "extism" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31848a0c3cc19f6946767fc0b54bbf6b07ee0f2e53302ede5abda6a5ae02dbb6" +dependencies = [ + "anyhow", + "cbindgen", + "extism-convert", + "extism-manifest", + "glob", + "libc", + "serde", + "serde_json", + "sha2", + "toml 0.9.12+spec-1.1.0", + "tracing", + "tracing-subscriber", + "ureq", + "url", + "uuid", + "wasi-common", + "wasmtime", + "wiggle", +] + +[[package]] +name = "extism-convert" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6612b4e92559eeb4c2dac88a53ee8b4729bea64025befcdeb2b3677e62fc1d" +dependencies = [ + "anyhow", + "base64", + "bytemuck", + "extism-convert-macros", + "prost 0.14.3", + "rmp-serde", + "serde", + "serde_json", +] + +[[package]] +name = "extism-convert-macros" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525831f1f15079a7c43514905579aac10f90fee46bc6353b683ed632029dd945" +dependencies = [ + "manyhow", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "extism-manifest" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e60e36345a96ad0d74adfca64dc22d93eb4979ab15a6c130cded5e0585f31b10" +dependencies = [ + "base64", + "serde", + "serde_json", +] + [[package]] name = "eyeball" version = "0.8.8" @@ -1887,6 +2259,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.1.4", + "windows-sys 0.59.0", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1946,6 +2329,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes", + "rustix 1.1.4", + "windows-sys 0.59.0", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -2075,6 +2469,28 @@ dependencies = [ "thread_local", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "fxprof-processed-profile" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" +dependencies = [ + "bitflags 2.11.0", + "debugid", + "fxhash", + "serde", + "serde_json", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2246,6 +2662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", + "serde", ] [[package]] @@ -2863,6 +3280,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "io-extras" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +dependencies = [ + "io-lifetimes", + "windows-sys 0.59.0", +] + [[package]] name = "io-kit-sys" version = "0.4.1" @@ -2883,6 +3310,12 @@ dependencies = [ "mach2 0.5.0", ] +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + [[package]] name = "ipnet" version = "2.12.0" @@ -2938,6 +3371,26 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "ittapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" +dependencies = [ + "anyhow", + "ittapi-sys", + "log", +] + +[[package]] +name = "ittapi-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" +dependencies = [ + "cc", +] + [[package]] name = "jep106" version = "0.3.0" @@ -3028,6 +3481,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -3090,6 +3549,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -3250,6 +3715,29 @@ dependencies = [ "hashify", ] +[[package]] +name = "manyhow" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "manyhow-macros" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + [[package]] name = "maplit" version = "1.0.2" @@ -3585,6 +4073,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + [[package]] name = "md-5" version = "0.10.6" @@ -3607,6 +4101,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix 1.1.4", +] + [[package]] name = "miette" version = "7.6.0" @@ -3948,10 +4451,10 @@ dependencies = [ "core-foundation-sys", "futures-core", "io-kit-sys 0.5.0", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "log", "once_cell", - "rustix", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -4000,6 +4503,9 @@ version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ + "crc32fast", + "hashbrown 0.15.5", + "indexmap", "memchr", ] @@ -4417,7 +4923,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -4456,6 +4962,18 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs 0.3.0", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "postgres" version = "0.19.12" @@ -4557,7 +5075,7 @@ dependencies = [ "bincode", "bitfield", "bitvec", - "cobs", + "cobs 0.5.1", "docsplay", "dunce", "espflash", @@ -4602,7 +5120,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.4+spec-1.1.0", ] [[package]] @@ -4626,6 +5144,17 @@ dependencies = [ "quote", ] +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -4769,6 +5298,29 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" +[[package]] +name = "pulley-interpreter" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5c8a4c6db43cd896bcc33f316c2f449a89fbec962717e9097d88c9c82547ec0" +dependencies = [ + "cranelift-bitset", + "log", + "pulley-macros", + "wasmtime-internal-math", +] + +[[package]] +name = "pulley-macros" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573407df6287098f3e9ded7873a768156bc97c6939d077924d70416cb529bab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "pxfm" version = "0.1.28" @@ -5007,6 +5559,17 @@ dependencies = [ "bitflags 2.11.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -5038,6 +5601,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "regalloc2" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08effbc1fa53aaebff69521a5c05640523fab037b34a4a2c109506bc938246fa" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.15.5", + "log", + "rustc-hash", + "smallvec", +] + [[package]] name = "regex" version = "1.12.3" @@ -5377,6 +5954,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -5392,6 +5975,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.4" @@ -5401,10 +5997,20 @@ dependencies = [ "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix 1.1.4", +] + [[package]] name = "rustls" version = "0.23.37" @@ -5617,6 +6223,10 @@ name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -5745,6 +6355,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "1.0.4" @@ -6078,6 +6697,22 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "system-interface" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" +dependencies = [ + "bitflags 2.11.0", + "cap-fs-ext", + "cap-std", + "fd-lock", + "io-lifetimes", + "rustix 0.38.44", + "windows-sys 0.59.0", + "winx", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -6090,6 +6725,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "tempfile" version = "3.27.0" @@ -6099,7 +6740,7 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -6114,6 +6755,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -6416,16 +7066,30 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + [[package]] name = "toml" version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ + "indexmap", "serde_core", - "serde_spanned", + "serde_spanned 1.0.4", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", + "toml_writer", "winnow 0.7.15", ] @@ -6437,13 +7101,22 @@ checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" dependencies = [ "indexmap", "serde_core", - "serde_spanned", + "serde_spanned 1.0.4", "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "toml_writer", "winnow 0.7.15", ] +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -6462,6 +7135,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + [[package]] name = "toml_edit" version = "0.25.4+spec-1.1.0" @@ -6483,6 +7170,12 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" version = "1.0.6+spec-1.1.0" @@ -6577,6 +7270,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -6850,6 +7544,7 @@ checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" dependencies = [ "base64", "cookie_store", + "flate2", "log", "percent-encoding", "rustls", @@ -7227,6 +7922,31 @@ dependencies = [ "wasip2", ] +[[package]] +name = "wasi-common" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913b688354290a2890e174c053792c7ea60c47415cef4c0b07548b7eda739c83" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "cap-fs-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "io-extras", + "io-lifetimes", + "log", + "rustix 1.1.4", + "system-interface", + "thiserror 2.0.18", + "tracing", + "wasmtime", + "wiggle", + "windows-sys 0.60.2", +] + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -7313,6 +8033,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser 0.239.0", +] + [[package]] name = "wasm-encoder" version = "0.244.0" @@ -7320,7 +8050,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" +dependencies = [ + "leb128fmt", + "wasmparser 0.245.1", ] [[package]] @@ -7331,8 +8071,8 @@ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", ] [[package]] @@ -7366,6 +8106,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", + "serde", +] + [[package]] name = "wasmparser" version = "0.244.0" @@ -7378,6 +8131,329 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" +dependencies = [ + "bitflags 2.11.0", + "indexmap", + "semver", +] + +[[package]] +name = "wasmprinter" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3981f3d51f39f24f5fc90f93049a90f08dbbca8deba602cd46bb8ca67a94718" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.239.0", +] + +[[package]] +name = "wasmtime" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efcab4481a639a8f3413aa011f733db105ecccc1326a51a6f5c7d09c99314f85" +dependencies = [ + "addr2line", + "anyhow", + "async-trait", + "bitflags 2.11.0", + "bumpalo", + "cc", + "cfg-if", + "encoding_rs", + "fxprof-processed-profile", + "gimli", + "hashbrown 0.15.5", + "indexmap", + "ittapi", + "libc", + "log", + "mach2 0.4.3", + "memfd", + "object 0.37.3", + "once_cell", + "postcard", + "pulley-interpreter", + "rayon", + "rustix 1.1.4", + "semver", + "serde", + "serde_derive", + "serde_json", + "smallvec", + "target-lexicon", + "wasm-encoder 0.239.0", + "wasmparser 0.239.0", + "wasmtime-environ", + "wasmtime-internal-asm-macros", + "wasmtime-internal-cache", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-math", + "wasmtime-internal-slab", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", + "wasmtime-internal-winch", + "wat", + "windows-sys 0.60.2", +] + +[[package]] +name = "wasmtime-environ" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5f8069e3d2a235a8d273e58fc3b2088c730477fe8d5364495d4bf20ddbc45d" +dependencies = [ + "anyhow", + "cpp_demangle", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "indexmap", + "log", + "object 0.37.3", + "postcard", + "rustc-demangle", + "semver", + "serde", + "serde_derive", + "smallvec", + "target-lexicon", + "wasm-encoder 0.239.0", + "wasmparser 0.239.0", + "wasmprinter", + "wasmtime-internal-component-util", +] + +[[package]] +name = "wasmtime-internal-asm-macros" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bdb85a6f168e68d3062fe38c784b2735924cb49733c3ce3e2c9679566c8894" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-internal-cache" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca4dc44ca075a2a22e733e661413d1be5352053c11dbc01042c01a5d7d70037" +dependencies = [ + "anyhow", + "base64", + "directories-next", + "log", + "postcard", + "rustix 1.1.4", + "serde", + "serde_derive", + "sha2", + "toml 0.8.23", + "windows-sys 0.60.2", + "zstd", +] + +[[package]] +name = "wasmtime-internal-component-macro" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8aa820447f93cfdc089d744361333f16416c1bebc33e234f4fc5d15766dfe8" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser 0.239.0", +] + +[[package]] +name = "wasmtime-internal-component-util" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38171538c2612e9d07473f06fcf03d872fe1581e3f7c8587e04e2b2f8e47dcab" + +[[package]] +name = "wasmtime-internal-cranelift" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4440d46baa6b12a40ba6beb1476ed023cee02e8fb45629d2666b9a852398c04b" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools 0.14.0", + "log", + "object 0.37.3", + "pulley-interpreter", + "smallvec", + "target-lexicon", + "thiserror 2.0.18", + "wasmparser 0.239.0", + "wasmtime-environ", + "wasmtime-internal-math", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-fiber" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d776059b7f5674f2823b9d283616acfcd7e45b862bfad7c257485621099dea" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "libc", + "rustix 1.1.4", + "wasmtime-internal-asm-macros", + "wasmtime-internal-versioned-export-macros", + "windows-sys 0.60.2", +] + +[[package]] +name = "wasmtime-internal-jit-debug" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f189b670fe4e668015cace8a1df1faae03ed9f6b2b638a504204336b4b34de2" +dependencies = [ + "cc", + "object 0.37.3", + "rustix 1.1.4", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f138fe8652acc4cf8d5de15952a6b6c4bdef10479d33199cc6d50c3fbe778cdd" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "wasmtime-internal-math" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f9a2bff5db67f19f3d2f7b6ed4b4f67def9917111b824595eb84ef8e43c008e" +dependencies = [ + "libm", +] + +[[package]] +name = "wasmtime-internal-slab" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eafd48d67f1aae5a188c4842bee9de2c9f0e7a07626136e54223a0eb63bd4bca" + +[[package]] +name = "wasmtime-internal-unwinder" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cb01a1d8cd95583ac06cb82fc2ad465e893c3ed7d9765f750dfd9d2483a411" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "log", + "object 0.37.3", +] + +[[package]] +name = "wasmtime-internal-versioned-export-macros" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d46615cb9e10960b72cc6f4b2220062523c06d25fff33a4e61d525a4f73ee8c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "wasmtime-internal-winch" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cd3b2c652e93a8b3d6499f3299e46cb58db076a4477ddef594be9089f4cac38" +dependencies = [ + "anyhow", + "cranelift-codegen", + "gimli", + "log", + "object 0.37.3", + "target-lexicon", + "wasmparser 0.239.0", + "wasmtime-environ", + "wasmtime-internal-cranelift", + "winch-codegen", +] + +[[package]] +name = "wasmtime-internal-wit-bindgen" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98aaee67f9f92aa730a0e6e977474d056f7d9c15ba259494574e3c2d0b75e14" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "heck", + "indexmap", + "wit-parser 0.239.0", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "wast" +version = "245.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cf1149285569120b8ce39db8b465e8a2b55c34cbb586bd977e43e2bc7300bf" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width 0.2.2", + "wasm-encoder 0.245.1", +] + +[[package]] +name = "wat" +version = "1.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd48d1679b6858988cb96b154dda0ec5bbb09275b71db46057be37332d5477be" +dependencies = [ + "wast 245.0.1", +] + [[package]] name = "web-sys" version = "0.3.91" @@ -7477,6 +8553,48 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wiggle" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a792fe35c2ba3092e8ed3b832a5a671f3076861628f9e9810f6ad7de802007" +dependencies = [ + "anyhow", + "async-trait", + "bitflags 2.11.0", + "thiserror 2.0.18", + "tracing", + "wasmtime", + "wiggle-macro", + "witx", +] + +[[package]] +name = "wiggle-generate" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "661421edf501b09b2ae7e2ffd234dd2947be67d4ca320c41da2325592543b181" +dependencies = [ + "anyhow", + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e2741d47a84e93ae623216d8b6cc2b42e3b659ca987d44fffd4f020a1dc56c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "wiggle-generate", +] + [[package]] name = "wildmatch" version = "2.6.1" @@ -7514,6 +8632,26 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winch-codegen" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece82b2b1513521f0bf419a61b4a6151bc99ee2906f3d51a75faf92c38c9b041" +dependencies = [ + "anyhow", + "cranelift-assembler-x64", + "cranelift-codegen", + "gimli", + "regalloc2", + "smallvec", + "target-lexicon", + "thiserror 2.0.18", + "wasmparser 0.239.0", + "wasmtime-environ", + "wasmtime-internal-cranelift", + "wasmtime-internal-math", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -7756,6 +8894,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags 2.11.0", + "windows-sys 0.59.0", +] + [[package]] name = "wiremock" version = "0.6.5" @@ -7796,7 +8944,7 @@ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck", - "wit-parser", + "wit-parser 0.244.0", ] [[package]] @@ -7843,10 +8991,28 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder", + "wasm-encoder 0.244.0", "wasm-metadata", - "wasmparser", - "wit-parser", + "wasmparser 0.244.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.239.0", ] [[package]] @@ -7864,7 +9030,19 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser", + "wasmparser 0.244.0", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror 1.0.69", + "wast 35.0.2", ] [[package]] @@ -7994,6 +9172,7 @@ dependencies = [ "cron", "dialoguer", "directories", + "extism", "fantoccini", "futures-util", "glob", @@ -8195,6 +9374,34 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zune-core" version = "0.5.1" diff --git a/src/gateway/ws.rs b/src/gateway/ws.rs index 260b57d7e..aee437b07 100644 --- a/src/gateway/ws.rs +++ b/src/gateway/ws.rs @@ -236,7 +236,8 @@ async fn handle_socket(socket: WebSocket, state: AppState, session_id: Option Date: Tue, 17 Mar 2026 18:33:03 -0400 Subject: [PATCH 6/8] fix(ci): ignore unmaintained transitive deps from extism and indicatif Add cargo-deny ignore entries for RUSTSEC-2024-0388 (derivative), RUSTSEC-2025-0057 (fxhash), and RUSTSEC-2025-0119 (number_prefix). All are transitive dependencies we cannot directly control. Co-Authored-By: Claude Opus 4.6 --- deny.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deny.toml b/deny.toml index 8a0ba9369..2b6aeeaa8 100644 --- a/deny.toml +++ b/deny.toml @@ -12,6 +12,9 @@ ignore = [ # bincode v2.0.1 via probe-rs — project ceased but 1.3.3 considered complete "RUSTSEC-2025-0141", { id = "RUSTSEC-2024-0384", reason = "Reported to `rust-nostr/nostr` and it's WIP" }, + { id = "RUSTSEC-2024-0388", reason = "derivative via extism → wasmtime transitive dep" }, + { id = "RUSTSEC-2025-0057", reason = "fxhash via extism → wasmtime transitive dep" }, + { id = "RUSTSEC-2025-0119", reason = "number_prefix via indicatif — cosmetic dep" }, ] [licenses] From d016e6b1a0a4df402aa227e13a0b1251bc048bc4 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Tue, 17 Mar 2026 18:35:08 -0400 Subject: [PATCH 7/8] fix(ci): ignore wasmtime vulns from extism 1.13.0 (no upstream fix) RUSTSEC-2026-0006, RUSTSEC-2026-0020, RUSTSEC-2026-0021 are all in wasmtime 37.x pinned by extism. No newer extism release available. Plugin system is behind a feature flag to limit exposure. Co-Authored-By: Claude Opus 4.6 --- deny.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deny.toml b/deny.toml index 2b6aeeaa8..3c7f6ab47 100644 --- a/deny.toml +++ b/deny.toml @@ -15,6 +15,10 @@ ignore = [ { id = "RUSTSEC-2024-0388", reason = "derivative via extism → wasmtime transitive dep" }, { id = "RUSTSEC-2025-0057", reason = "fxhash via extism → wasmtime transitive dep" }, { id = "RUSTSEC-2025-0119", reason = "number_prefix via indicatif — cosmetic dep" }, + # wasmtime vulns via extism 1.13.0 — no upstream fix yet; plugins feature-gated + { id = "RUSTSEC-2026-0006", reason = "wasmtime segfault via extism; awaiting extism upgrade" }, + { id = "RUSTSEC-2026-0020", reason = "WASI resource exhaustion via extism; awaiting extism upgrade" }, + { id = "RUSTSEC-2026-0021", reason = "WASI http fields panic via extism; awaiting extism upgrade" }, ] [licenses] From 9da620a5aa56ffe23bd87d746644ee167367f22d Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Tue, 17 Mar 2026 18:38:02 -0400 Subject: [PATCH 8/8] fix(ci): add cargo-audit ignore for wasmtime vulns from extism cargo-audit uses .cargo/audit.toml (not deny.toml) for its ignore list. These 3 wasmtime advisories are transitive via extism 1.13.0 with no upstream fix available. Plugin system is feature-gated. Co-Authored-By: Claude Opus 4.6 --- .cargo/audit.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .cargo/audit.toml diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 000000000..369486677 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,10 @@ +# cargo-audit configuration +# https://rustsec.org/ + +[advisories] +ignore = [ + # wasmtime vulns via extism 1.13.0 — no upstream fix; plugins feature-gated + "RUSTSEC-2026-0006", # wasmtime f64.copysign segfault on x86-64 + "RUSTSEC-2026-0020", # WASI guest-controlled resource exhaustion + "RUSTSEC-2026-0021", # WASI http fields panic +]