Skip to main content

ai_agent/utils/plugins/
headless_plugin_install.rs

1// Source: ~/claudecode/openclaudecode/src/utils/plugins/headlessPluginInstall.ts
2#![allow(dead_code)]
3
4use std::sync::atomic::{AtomicBool, Ordering};
5
6/// Plugin installation for headless/CCR mode.
7
8static INSTALLING: AtomicBool = AtomicBool::new(false);
9
10/// Install plugins for headless/CCR mode.
11/// Returns true if any plugins were installed (caller should refresh MCP).
12pub async fn install_plugins_for_headless() -> Result<bool, Box<dyn std::error::Error + Send + Sync>>
13{
14    if !INSTALLING
15        .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
16        .is_ok()
17    {
18        return Ok(false);
19    }
20
21    let zip_cache_mode = super::zip_cache::is_plugin_zip_cache_enabled();
22    log::debug!(
23        "install_plugins_for_headless: starting{}",
24        if zip_cache_mode {
25            " (zip cache mode)"
26        } else {
27            ""
28        }
29    );
30
31    // Register seed marketplaces before diffing
32    let seed_changed = super::marketplace_manager::register_seed_marketplaces().await?;
33    if seed_changed {
34        super::marketplace_manager::clear_marketplaces_cache();
35        super::loader::clear_plugin_cache(Some(
36            "headless_plugin_install: seed marketplaces registered",
37        ));
38    }
39
40    // Ensure zip cache directory structure exists (stub)
41    if zip_cache_mode {
42        // Would create dirs in production
43        log::debug!("Zip cache mode: would create directory structure");
44    }
45
46    let declared_count = super::marketplace_manager::get_declared_marketplaces().len();
47    let mut metrics = HeadlessInstallMetrics::default();
48    let mut plugins_changed = seed_changed;
49
50    let result = (async {
51        if declared_count == 0 {
52            log::debug!("install_plugins_for_headless: no marketplaces declared");
53        } else {
54            let reconcile_result = super::reconciler::reconcile_marketplaces(
55                Some(super::reconciler::ReconcileOptions {
56                    skip: if zip_cache_mode {
57                        Some(Box::new(|_name: &str, source: &super::schemas::MarketplaceSource| {
58                            !super::zip_cache::is_marketplace_source_supported_by_zip_cache(source)
59                        }))
60                    } else {
61                        None
62                    },
63                    on_progress: Some(Box::new(|event: super::reconciler::ReconcileProgressEvent| {
64                        match event {
65                            super::reconciler::ReconcileProgressEvent::Installed { name, .. } => {
66                                log::debug!("install_plugins_for_headless: installed marketplace {}", name);
67                            }
68                            super::reconciler::ReconcileProgressEvent::Failed { name, error } => {
69                                log::debug!("install_plugins_for_headless: failed to install marketplace {}: {}", name, error);
70                            }
71                            _ => {}
72                        }
73                    })),
74                }),
75            )
76            .await?;
77
78            let marketplaces_changed = reconcile_result.installed.len() + reconcile_result.updated.len();
79            if marketplaces_changed > 0 {
80                super::marketplace_manager::clear_marketplaces_cache();
81                super::loader::clear_plugin_cache(Some("headless_plugin_install: marketplaces reconciled"));
82                plugins_changed = true;
83            }
84            metrics.marketplaces_installed = marketplaces_changed;
85        }
86
87        // Zip cache: save marketplace JSONs for offline access
88        if zip_cache_mode {
89            super::zip_cache_adapters::sync_marketplaces_to_zip_cache().await?;
90        }
91
92        // Delisting enforcement
93        let newly_delisted = super::plugin_blocklist::detect_and_uninstall_delisted_plugins().await?;
94        metrics.delisted_count = newly_delisted.len();
95        if !newly_delisted.is_empty() {
96            plugins_changed = true;
97        }
98
99        if plugins_changed {
100            super::loader::clear_plugin_cache(Some("headless_plugin_install: plugins changed"));
101        }
102
103        // Register session cleanup for extracted plugin temp dirs (stub)
104        if zip_cache_mode {
105            // Would register cleanup in production
106            log::debug!("Zip cache mode: would register session cleanup");
107        }
108
109        Ok(plugins_changed)
110    })
111    .await;
112
113    INSTALLING.store(false, Ordering::SeqCst);
114
115    // Log telemetry (stub)
116    // crate::services::analytics::log_event("tengu_headless_plugin_install", &serde_json::json!({
117    //     "marketplaces_installed": metrics.marketplaces_installed,
118    //     "delisted_count": metrics.delisted_count,
119    // }));
120
121    result
122}
123
124#[derive(Default)]
125struct HeadlessInstallMetrics {
126    marketplaces_installed: usize,
127    delisted_count: usize,
128}