source dump of claude code
at main 184 lines 6.0 kB view raw
1/** 2 * Background plugin and marketplace installation manager 3 * 4 * This module handles automatic installation of plugins and marketplaces 5 * from trusted sources (repository and user settings) without blocking startup. 6 */ 7 8import type { AppState } from '../../state/AppState.js' 9import { logForDebugging } from '../../utils/debug.js' 10import { logForDiagnosticsNoPII } from '../../utils/diagLogs.js' 11import { logError } from '../../utils/log.js' 12import { 13 clearMarketplacesCache, 14 getDeclaredMarketplaces, 15 loadKnownMarketplacesConfig, 16} from '../../utils/plugins/marketplaceManager.js' 17import { clearPluginCache } from '../../utils/plugins/pluginLoader.js' 18import { 19 diffMarketplaces, 20 reconcileMarketplaces, 21} from '../../utils/plugins/reconciler.js' 22import { refreshActivePlugins } from '../../utils/plugins/refresh.js' 23import { logEvent } from '../analytics/index.js' 24 25type SetAppState = (f: (prevState: AppState) => AppState) => void 26 27/** 28 * Update marketplace installation status in app state 29 */ 30function updateMarketplaceStatus( 31 setAppState: SetAppState, 32 name: string, 33 status: 'pending' | 'installing' | 'installed' | 'failed', 34 error?: string, 35): void { 36 setAppState(prevState => ({ 37 ...prevState, 38 plugins: { 39 ...prevState.plugins, 40 installationStatus: { 41 ...prevState.plugins.installationStatus, 42 marketplaces: prevState.plugins.installationStatus.marketplaces.map( 43 m => (m.name === name ? { ...m, status, error } : m), 44 ), 45 }, 46 }, 47 })) 48} 49 50/** 51 * Perform background plugin startup checks and installations. 52 * 53 * This is a thin wrapper around reconcileMarketplaces() that maps onProgress 54 * events to AppState updates for the REPL UI. After marketplaces are 55 * reconciled: 56 * - New installs → auto-refresh plugins (fixes "plugin-not-found" errors 57 * from the initial cache-only load on fresh homespace/cleared cache) 58 * - Updates only → set needsRefresh, show notification for /reload-plugins 59 */ 60export async function performBackgroundPluginInstallations( 61 setAppState: SetAppState, 62): Promise<void> { 63 logForDebugging('performBackgroundPluginInstallations called') 64 65 try { 66 // Compute diff upfront for initial UI status (pending spinners) 67 const declared = getDeclaredMarketplaces() 68 const materialized = await loadKnownMarketplacesConfig().catch(() => ({})) 69 const diff = diffMarketplaces(declared, materialized) 70 71 const pendingNames = [ 72 ...diff.missing, 73 ...diff.sourceChanged.map(c => c.name), 74 ] 75 76 // Initialize AppState with pending status. No per-plugin pending status — 77 // plugin load is fast (cache hit or local copy); marketplace clone is the 78 // slow part worth showing progress for. 79 setAppState(prev => ({ 80 ...prev, 81 plugins: { 82 ...prev.plugins, 83 installationStatus: { 84 marketplaces: pendingNames.map(name => ({ 85 name, 86 status: 'pending' as const, 87 })), 88 plugins: [], 89 }, 90 }, 91 })) 92 93 if (pendingNames.length === 0) { 94 return 95 } 96 97 logForDebugging( 98 `Installing ${pendingNames.length} marketplace(s) in background`, 99 ) 100 101 const result = await reconcileMarketplaces({ 102 onProgress: event => { 103 switch (event.type) { 104 case 'installing': 105 updateMarketplaceStatus(setAppState, event.name, 'installing') 106 break 107 case 'installed': 108 updateMarketplaceStatus(setAppState, event.name, 'installed') 109 break 110 case 'failed': 111 updateMarketplaceStatus( 112 setAppState, 113 event.name, 114 'failed', 115 event.error, 116 ) 117 break 118 } 119 }, 120 }) 121 122 const metrics = { 123 installed_count: result.installed.length, 124 updated_count: result.updated.length, 125 failed_count: result.failed.length, 126 up_to_date_count: result.upToDate.length, 127 } 128 logEvent('tengu_marketplace_background_install', metrics) 129 logForDiagnosticsNoPII( 130 'info', 131 'tengu_marketplace_background_install', 132 metrics, 133 ) 134 135 if (result.installed.length > 0) { 136 // New marketplaces were installed — auto-refresh plugins. This fixes 137 // "Plugin not found in marketplace" errors from the initial cache-only 138 // load (e.g., fresh homespace where marketplace cache was empty). 139 // refreshActivePlugins clears all caches, reloads plugins, and bumps 140 // pluginReconnectKey so MCP connections are re-established. 141 clearMarketplacesCache() 142 logForDebugging( 143 `Auto-refreshing plugins after ${result.installed.length} new marketplace(s) installed`, 144 ) 145 try { 146 await refreshActivePlugins(setAppState) 147 } catch (refreshError) { 148 // If auto-refresh fails, fall back to needsRefresh notification so 149 // the user can manually run /reload-plugins to recover. 150 logError(refreshError) 151 logForDebugging( 152 `Auto-refresh failed, falling back to needsRefresh: ${refreshError}`, 153 { level: 'warn' }, 154 ) 155 clearPluginCache( 156 'performBackgroundPluginInstallations: auto-refresh failed', 157 ) 158 setAppState(prev => { 159 if (prev.plugins.needsRefresh) return prev 160 return { 161 ...prev, 162 plugins: { ...prev.plugins, needsRefresh: true }, 163 } 164 }) 165 } 166 } else if (result.updated.length > 0) { 167 // Existing marketplaces updated — notify user to run /reload-plugins. 168 // Updates are less urgent and the user should choose when to apply them. 169 clearMarketplacesCache() 170 clearPluginCache( 171 'performBackgroundPluginInstallations: marketplaces reconciled', 172 ) 173 setAppState(prev => { 174 if (prev.plugins.needsRefresh) return prev 175 return { 176 ...prev, 177 plugins: { ...prev.plugins, needsRefresh: true }, 178 } 179 }) 180 } 181 } catch (error) { 182 logError(error) 183 } 184}