Skip to main content

ai_agent/utils/plugins/
plugin_flagging.rs

1// Source: ~/claudecode/openclaudecode/src/utils/plugins/pluginFlagging.ts
2#![allow(dead_code)]
3
4use std::collections::HashMap;
5use std::path::PathBuf;
6use std::sync::Mutex;
7
8use once_cell::sync::Lazy;
9use serde::{Deserialize, Serialize};
10
11use super::plugin_directories::get_plugins_directory;
12
13const FLAGGED_PLUGINS_FILENAME: &str = "flagged-plugins.json";
14const SEEN_EXPIRY_MS: u64 = 48 * 60 * 60 * 1000; // 48 hours
15
16/// Flagged plugin tracking data.
17#[derive(Serialize, Deserialize, Debug, Clone)]
18pub struct FlaggedPlugin {
19    #[serde(rename = "flaggedAt")]
20    pub flagged_at: String,
21    #[serde(rename = "seenAt", skip_serializing_if = "Option::is_none")]
22    pub seen_at: Option<String>,
23}
24
25static CACHE: Lazy<Mutex<Option<HashMap<String, FlaggedPlugin>>>> = Lazy::new(|| Mutex::new(None));
26
27fn get_flagged_plugins_path() -> PathBuf {
28    PathBuf::from(get_plugins_directory()).join(FLAGGED_PLUGINS_FILENAME)
29}
30
31/// Load flagged plugins from disk into the module cache.
32pub async fn load_flagged_plugins() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
33    let all = read_from_disk().await.unwrap_or_default();
34    let now = std::time::SystemTime::now()
35        .duration_since(std::time::UNIX_EPOCH)
36        .unwrap_or_default()
37        .as_millis() as u64;
38
39    let mut changed = false;
40    let mut filtered = HashMap::new();
41
42    for (id, entry) in all {
43        if let Some(ref seen_at) = entry.seen_at {
44            let seen_time = chrono::DateTime::parse_from_rfc3339(seen_at)
45                .map(|dt| dt.timestamp_millis() as u64)
46                .unwrap_or(0);
47
48            if now.saturating_sub(seen_time) >= SEEN_EXPIRY_MS {
49                changed = true;
50                continue;
51            }
52        }
53        filtered.insert(id, entry);
54    }
55
56    if changed {
57        write_to_disk(&filtered).await?;
58    }
59
60    {
61        let mut cache = CACHE.lock().unwrap();
62        *cache = Some(filtered);
63    }
64
65    Ok(())
66}
67
68async fn read_from_disk()
69-> Result<HashMap<String, FlaggedPlugin>, Box<dyn std::error::Error + Send + Sync>> {
70    let path = get_flagged_plugins_path();
71    let content = tokio::fs::read_to_string(&path).await?;
72    let data: serde_json::Value = serde_json::from_str(&content)?;
73
74    if let Some(plugins) = data.get("plugins").and_then(|p| p.as_object()) {
75        let mut result = HashMap::new();
76        for (id, entry) in plugins {
77            if let Some(flagged_at) = entry.get("flaggedAt").and_then(|v| v.as_str()) {
78                let seen_at = entry
79                    .get("seenAt")
80                    .and_then(|v| v.as_str())
81                    .map(|s| s.to_string());
82                result.insert(
83                    id.clone(),
84                    FlaggedPlugin {
85                        flagged_at: flagged_at.to_string(),
86                        seen_at,
87                    },
88                );
89            }
90        }
91        Ok(result)
92    } else {
93        Ok(HashMap::new())
94    }
95}
96
97async fn write_to_disk(
98    plugins: &HashMap<String, FlaggedPlugin>,
99) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
100    let path = get_flagged_plugins_path();
101    let temp_path = PathBuf::from(format!("{}.{}.tmp", path.display(), rand::random::<u64>()));
102
103    tokio::fs::create_dir_all(get_plugins_directory()).await?;
104
105    let data = serde_json::json!({ "plugins": plugins });
106    let content = serde_json::to_string_pretty(&data)?;
107    tokio::fs::write(&temp_path, content).await?;
108    tokio::fs::rename(&temp_path, &path).await?;
109
110    {
111        let mut cache = CACHE.lock().unwrap();
112        *cache = Some(plugins.clone());
113    }
114
115    Ok(())
116}
117
118/// Get all flagged plugins from the in-memory cache.
119pub fn get_flagged_plugins() -> HashMap<String, FlaggedPlugin> {
120    let cache = CACHE.lock().unwrap();
121    cache.clone().unwrap_or_default()
122}
123
124/// Add a plugin to the flagged list.
125pub async fn add_flagged_plugin(
126    plugin_id: &str,
127) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
128    let mut cache = CACHE.lock().unwrap();
129    if cache.is_none() {
130        *cache = Some(read_from_disk().await.unwrap_or_default());
131    }
132
133    let now = chrono::Utc::now().to_rfc3339();
134    if let Some(ref mut plugins) = *cache {
135        plugins.insert(
136            plugin_id.to_string(),
137            FlaggedPlugin {
138                flagged_at: now,
139                seen_at: None,
140            },
141        );
142
143        write_to_disk(plugins).await?;
144        log::debug!("Flagged plugin: {}", plugin_id);
145    }
146
147    Ok(())
148}
149
150/// Mark flagged plugins as seen.
151pub async fn mark_flagged_plugins_seen(
152    plugin_ids: &[String],
153) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
154    let mut cache = CACHE.lock().unwrap();
155    if cache.is_none() {
156        *cache = Some(read_from_disk().await.unwrap_or_default());
157    }
158
159    let now = chrono::Utc::now().to_rfc3339();
160    let mut changed = false;
161
162    if let Some(ref mut plugins) = *cache {
163        for id in plugin_ids {
164            if let Some(entry) = plugins.get_mut(id) {
165                if entry.seen_at.is_none() {
166                    entry.seen_at = Some(now.clone());
167                    changed = true;
168                }
169            }
170        }
171    }
172
173    if changed {
174        if let Some(ref plugins) = *cache {
175            write_to_disk(plugins).await?;
176        }
177    }
178
179    Ok(())
180}
181
182/// Remove a plugin from the flagged list.
183pub async fn remove_flagged_plugin(
184    plugin_id: &str,
185) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
186    let mut cache = CACHE.lock().unwrap();
187    if cache.is_none() {
188        *cache = Some(read_from_disk().await.unwrap_or_default());
189    }
190
191    if let Some(ref mut plugins) = *cache {
192        if plugins.remove(plugin_id).is_some() {
193            write_to_disk(plugins).await?;
194        }
195    }
196
197    Ok(())
198}