ai_agent/utils/plugins/
load_plugin_hooks.rs1#![allow(dead_code)]
3
4use std::collections::HashMap;
5use std::sync::Mutex;
6
7use once_cell::sync::Lazy;
8use serde_json::Value;
9
10use super::loader::load_all_plugins_cache_only;
11
12static PLUGIN_HOOK_CACHE: Lazy<Mutex<Option<HashMap<String, Vec<PluginHookMatcher>>>>> =
13 Lazy::new(|| Mutex::new(None));
14
15pub const HOOK_EVENTS: &[&str] = &[
17 "PreToolUse",
18 "PostToolUse",
19 "PostToolUseFailure",
20 "PermissionDenied",
21 "Notification",
22 "UserPromptSubmit",
23 "SessionStart",
24 "SessionEnd",
25 "Stop",
26 "StopFailure",
27 "SubagentStart",
28 "SubagentStop",
29 "PreCompact",
30 "PostCompact",
31 "PermissionRequest",
32 "Setup",
33 "TeammateIdle",
34 "TaskCreated",
35 "TaskCompleted",
36 "Elicitation",
37 "ElicitationResult",
38 "ConfigChange",
39 "WorktreeCreate",
40 "WorktreeRemove",
41 "InstructionsLoaded",
42 "CwdChanged",
43 "FileChanged",
44];
45
46#[derive(Clone, Debug)]
48pub struct PluginHookMatcher {
49 pub matcher: String,
50 pub hooks: Vec<String>,
51 pub plugin_root: String,
52 pub plugin_name: String,
53 pub plugin_id: String,
54}
55
56fn convert_plugin_hooks_to_matchers(
58 plugin: &crate::plugin::types::LoadedPlugin,
59) -> HashMap<String, Vec<PluginHookMatcher>> {
60 let mut plugin_matchers: HashMap<String, Vec<PluginHookMatcher>> = HashMap::new();
61
62 for &event in HOOK_EVENTS {
63 plugin_matchers.insert(event.to_string(), Vec::new());
64 }
65
66 let hooks_config = match &plugin.hooks_config {
67 Some(config) => config,
68 None => return plugin_matchers,
69 };
70
71 if let Some(obj) = hooks_config.as_object() {
72 for (event, matchers) in obj {
73 if let Some(matchers_array) = matchers.as_array() {
74 for matcher_entry in matchers_array {
75 if let Some(hooks) = matcher_entry.get("hooks").and_then(|h| h.as_array()) {
76 if !hooks.is_empty() {
77 let matcher = matcher_entry
78 .get("matcher")
79 .and_then(|m| m.as_str())
80 .unwrap_or("*")
81 .to_string();
82
83 let hook_list: Vec<String> = hooks
84 .iter()
85 .filter_map(|h| h.as_str().map(|s| s.to_string()))
86 .collect();
87
88 let entry = plugin_matchers.entry(event.clone()).or_default();
89 entry.push(PluginHookMatcher {
90 matcher,
91 hooks: hook_list,
92 plugin_root: plugin.path.clone(),
93 plugin_name: plugin.name.clone(),
94 plugin_id: plugin.source.clone(),
95 });
96 }
97 }
98 }
99 }
100 }
101 }
102
103 plugin_matchers
104}
105
106pub async fn load_plugin_hooks()
108-> Result<HashMap<String, Vec<PluginHookMatcher>>, Box<dyn std::error::Error + Send + Sync>> {
109 let plugin_result = load_all_plugins_cache_only().await?;
110 let mut all_plugin_hooks: HashMap<String, Vec<PluginHookMatcher>> = HashMap::new();
111
112 for &event in HOOK_EVENTS {
113 all_plugin_hooks.insert(event.to_string(), Vec::new());
114 }
115
116 for plugin in &plugin_result.enabled {
117 let plugin_matchers = convert_plugin_hooks_to_matchers(plugin);
118
119 for (event, matchers) in plugin_matchers {
120 all_plugin_hooks.entry(event).or_default().extend(matchers);
121 }
122 }
123
124 let total_hooks: usize = all_plugin_hooks
125 .values()
126 .map(|matchers| matchers.iter().map(|m| m.hooks.len()).sum::<usize>())
127 .sum();
128
129 log::debug!(
130 "Registered {} hooks from {} plugins",
131 total_hooks,
132 plugin_result.enabled.len()
133 );
134
135 {
136 let mut cache = PLUGIN_HOOK_CACHE.lock().unwrap();
137 *cache = Some(all_plugin_hooks.clone());
138 }
139
140 Ok(all_plugin_hooks)
141}
142
143pub fn clear_plugin_hook_cache() {
145 let mut cache = PLUGIN_HOOK_CACHE.lock().unwrap();
146 *cache = None;
147}
148
149pub async fn prune_removed_plugin_hooks() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
151 let plugin_result = load_all_plugins_cache_only().await?;
152 let enabled_roots: std::collections::HashSet<_> = plugin_result
153 .enabled
154 .iter()
155 .map(|p| p.path.clone())
156 .collect();
157
158 let cache = PLUGIN_HOOK_CACHE.lock().unwrap();
159 let current = match cache.as_ref() {
160 Some(c) => c.clone(),
161 None => return Ok(()),
162 };
163
164 let mut survivors: HashMap<String, Vec<PluginHookMatcher>> = HashMap::new();
165 for (event, matchers) in current {
166 let kept: Vec<PluginHookMatcher> = matchers
167 .into_iter()
168 .filter(|m| enabled_roots.contains(&m.plugin_root))
169 .collect();
170 if !kept.is_empty() {
171 survivors.insert(event, kept);
172 }
173 }
174
175 drop(cache);
176 {
177 let mut cache = PLUGIN_HOOK_CACHE.lock().unwrap();
178 *cache = Some(survivors);
179 }
180
181 Ok(())
182}
183
184pub fn reset_hot_reload_state() {
186 }