From fbd20ad6e488fb3b9efc1612fdffee6e672976c1 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sun, 14 Jun 2026 02:43:45 +0300 Subject: feat: add plugin v1 system Add manifest-driven plugin actions, event hooks, pane entrypoints, link handlers, and host APIs. Support plugin link, GitHub install, build commands, installed plugin registry, pane movement, runtime context, and plugin docs. --- src/app/mod.rs | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'src/app/mod.rs') diff --git a/src/app/mod.rs b/src/app/mod.rs index 5447a2de..356ecbab 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -56,6 +56,13 @@ use crate::events::AppEvent; pub use state::{AppState, Mode, ToastKind, ViewState}; +pub(crate) fn load_plugin_manifest( + path: &str, + enabled: bool, +) -> Result { + api::plugins::load_plugin_manifest(path, enabled) +} + /// Full application: AppState + runtime concerns (event channels, async I/O). #[derive(Debug, Clone)] pub(crate) struct OverlayPaneState { @@ -132,7 +139,7 @@ pub(crate) const APP_EVENT_DRAIN_LIMIT: usize = 64; pub(crate) enum LoopEvent { Timer, Internal(AppEvent), - Api(crate::api::ApiRequestMessage), + Api(Box), RawInput(crate::raw_input::RawInputEvent), InputClosed, RenderRequested, @@ -183,6 +190,20 @@ fn auto_updates_enabled(no_session: bool) -> bool { !no_session && !cfg!(debug_assertions) } +fn load_plugin_registry(no_session: bool) -> crate::app::state::InstalledPluginRegistry { + if no_session { + return std::collections::HashMap::new(); + } + let entries = crate::persist::plugin_registry::load(); + let entries = crate::persist::plugin_registry::reload_manifests(entries, |path, enabled| { + crate::app::api::plugins::load_plugin_manifest(path, enabled).map_err(|(_, msg)| msg) + }); + entries + .into_iter() + .map(|plugin| (plugin.plugin_id.clone(), plugin)) + .collect() +} + fn agent_panel_scope_from_config( scope: crate::config::AgentPanelScopeConfig, ) -> state::AgentPanelScope { @@ -542,6 +563,11 @@ impl App { agent_manifest_summaries, agent_manifest_update_status: crate::detect::manifest_update::load_status(), integration_install_messages: Vec::new(), + installed_plugins: load_plugin_registry(no_session), + plugin_panes: std::collections::HashMap::new(), + plugin_command_logs: Vec::new(), + next_plugin_command_log_id: 1, + plugin_commands_in_flight: 0, global_menu: state::MenuListState::new(0), host_terminal_theme: crate::terminal_theme::TerminalTheme::default(), session_dirty: false, @@ -895,7 +921,7 @@ impl App { let input_rx = self.input_rx.as_mut(); tokio::select! { maybe_api = self.api_rx.recv() => match maybe_api { - Some(msg) => LoopEvent::Api(msg), + Some(msg) => LoopEvent::Api(Box::new(msg)), None => LoopEvent::Timer, }, maybe_ev = self.event_rx.recv() => match maybe_ev { @@ -918,7 +944,7 @@ impl App { needs_render = true; } LoopEvent::Api(msg) => { - if self.handle_api_request_message(msg) { + if self.handle_api_request_message(*msg) { needs_render = true; } } @@ -3202,6 +3228,7 @@ mod tests { ratio: None, cwd: None, focus: false, + env: Default::default(), }), }); let response: serde_json::Value = serde_json::from_str(&response).unwrap(); @@ -3281,6 +3308,7 @@ mod tests { ratio: None, cwd: None, focus: true, + env: Default::default(), }), }); let response: serde_json::Value = serde_json::from_str(&response).unwrap(); @@ -3326,6 +3354,7 @@ mod tests { ratio: Some(0.333), cwd: None, focus: false, + env: Default::default(), }), }); let response: serde_json::Value = serde_json::from_str(&response).unwrap(); @@ -3371,6 +3400,7 @@ mod tests { ratio: None, cwd: None, focus: false, + env: Default::default(), }), }); let response: serde_json::Value = serde_json::from_str(&response).unwrap(); @@ -3412,6 +3442,7 @@ mod tests { split: Some(crate::api::schema::SplitDirection::Right), focus: true, argv: vec![exiting_test_command().into()], + env: Default::default(), }), }); let response: serde_json::Value = serde_json::from_str(&response).unwrap(); -- cgit v1.2.3