Download src/utils/plugins/refresh.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 8.54 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/utils/plugins/refresh.ts
- Command line
-
hf download hf://chenbhao/codev/src/utils/plugins/refresh.ts
-
curl -L -o refresh.ts https://huggingface.co/chenbhao/codev/resolve/main/src/utils/plugins/refresh.ts
8.54 kB
| /** | |
| * Layer-3 refresh primitive: swap active plugin components in the running session. | |
| * | |
| * Three-layer model (see reconciler.ts for Layer-2): | |
| * - Layer 1: intent (settings) | |
| * - Layer 2: materialization (~/.claude/plugins/) — reconcileMarketplaces() | |
| * - Layer 3: active components (AppState) — this file | |
| * | |
| * Called from: | |
| * - /reload-plugins command (interactive, user-initiated) | |
| * - print.ts refreshPluginState() (headless, auto before first query with SYNC_PLUGIN_INSTALL) | |
| * - performBackgroundPluginInstallations() (background, auto after new marketplace install) | |
| * | |
| * NOT called from: | |
| * - useManagePlugins needsRefresh effect — interactive mode shows a notification; | |
| * user explicitly runs /reload-plugins (PR 5c) | |
| * - /plugin menu — sets needsRefresh, user runs /reload-plugins (PR 5b) | |
| */ | |
| import { getOriginalCwd } from '../../bootstrap/state.js' | |
| import type { Command } from '../../commands.js' | |
| import { reinitializeLspServerManager } from '../../services/lsp/manager.js' | |
| import type { AppState } from '../../state/AppState.js' | |
| import type { AgentDefinitionsResult } from '../../tools/AgentTool/loadAgentsDir.js' | |
| import { getAgentDefinitionsWithOverrides } from '../../tools/AgentTool/loadAgentsDir.js' | |
| import type { PluginError } from '../../types/plugin.js' | |
| import { logForDebugging } from '../debug.js' | |
| import { errorMessage } from '../errors.js' | |
| import { logError } from '../log.js' | |
| import { clearAllCaches } from './cacheUtils.js' | |
| import { getPluginCommands } from './loadPluginCommands.js' | |
| import { loadPluginHooks } from './loadPluginHooks.js' | |
| import { loadPluginLspServers } from './lspPluginIntegration.js' | |
| import { loadPluginMcpServers } from './mcpPluginIntegration.js' | |
| import { clearPluginCacheExclusions } from './orphanedPluginFilter.js' | |
| import { loadAllPlugins } from './pluginLoader.js' | |
| type SetAppState = (updater: (prev: AppState) => AppState) => void | |
| export type RefreshActivePluginsResult = { | |
| enabled_count: number | |
| disabled_count: number | |
| command_count: number | |
| agent_count: number | |
| hook_count: number | |
| mcp_count: number | |
| /** LSP servers provided by enabled plugins. reinitializeLspServerManager() | |
| * is called unconditionally so the manager picks these up (no-op if | |
| * manager was never initialized). */ | |
| lsp_count: number | |
| error_count: number | |
| /** The refreshed agent definitions, for callers (e.g. print.ts) that also | |
| * maintain a local mutable reference outside AppState. */ | |
| agentDefinitions: AgentDefinitionsResult | |
| /** The refreshed plugin commands, same rationale as agentDefinitions. */ | |
| pluginCommands: Command[] | |
| } | |
| /** | |
| * Refresh all active plugin components: commands, agents, hooks, MCP-reconnect | |
| * trigger, AppState plugin arrays. Clears ALL plugin caches (unlike the old | |
| * needsRefresh path which only cleared loadAllPlugins and returned stale data | |
| * from downstream memoized loaders). | |
| * | |
| * Consumes plugins.needsRefresh (sets to false). | |
| * Increments mcp.pluginReconnectKey so useManageMCPConnections effects re-run | |
| * and pick up new plugin MCP servers. | |
| * | |
| * LSP: if plugins now contribute LSP servers, reinitializeLspServerManager() | |
| * re-reads config. Servers are lazy-started so this is just config parsing. | |
| */ | |
| export async function refreshActivePlugins( | |
| setAppState: SetAppState, | |
| ): Promise<RefreshActivePluginsResult> { | |
| logForDebugging('refreshActivePlugins: clearing all plugin caches') | |
| clearAllCaches() | |
| // Orphan exclusions are session-frozen by default, but /reload-plugins is | |
| // an explicit "disk changed, re-read it" signal — recompute them too. | |
| clearPluginCacheExclusions() | |
| // Sequence the full load before cache-only consumers. Before #23693 all | |
| // three shared loadAllPlugins()'s memoize promise so Promise.all was a | |
| // no-op race. After #23693 getPluginCommands/getAgentDefinitions call | |
| // loadAllPluginsCacheOnly (separate memoize) — racing them means they | |
| // read installed_plugins.json before loadAllPlugins() has cloned+cached | |
| // the plugin, returning plugin-cache-miss. loadAllPlugins warms the | |
| // cache-only memoize on completion, so the awaits below are ~free. | |
| const pluginResult = await loadAllPlugins() | |
| const [pluginCommands, agentDefinitions] = await Promise.all([ | |
| getPluginCommands(), | |
| getAgentDefinitionsWithOverrides(getOriginalCwd()), | |
| ]) | |
| const { enabled, disabled, errors } = pluginResult | |
| // Populate mcpServers/lspServers on each enabled plugin. These are lazy | |
| // cache slots NOT filled by loadAllPlugins() — they're written later by | |
| // extractMcpServersFromPlugins/getPluginLspServers, which races with this. | |
| // Loading here gives accurate metrics AND warms the cache slots so the MCP | |
| // connection manager (triggered by pluginReconnectKey bump) sees the servers | |
| // without re-parsing manifests. Errors are pushed to the shared errors array. | |
| const [mcpCounts, lspCounts] = await Promise.all([ | |
| Promise.all( | |
| enabled.map(async p => { | |
| if (p.mcpServers) return Object.keys(p.mcpServers).length | |
| const servers = await loadPluginMcpServers(p, errors) | |
| if (servers) p.mcpServers = servers | |
| return servers ? Object.keys(servers).length : 0 | |
| }), | |
| ), | |
| Promise.all( | |
| enabled.map(async p => { | |
| if (p.lspServers) return Object.keys(p.lspServers).length | |
| const servers = await loadPluginLspServers(p, errors) | |
| if (servers) p.lspServers = servers | |
| return servers ? Object.keys(servers).length : 0 | |
| }), | |
| ), | |
| ]) | |
| const mcp_count = mcpCounts.reduce((sum, n) => sum + n, 0) | |
| const lsp_count = lspCounts.reduce((sum, n) => sum + n, 0) | |
| setAppState(prev => ({ | |
| ...prev, | |
| plugins: { | |
| ...prev.plugins, | |
| enabled, | |
| disabled, | |
| commands: pluginCommands, | |
| errors: mergePluginErrors(prev.plugins.errors, errors), | |
| needsRefresh: false, | |
| }, | |
| agentDefinitions, | |
| mcp: { | |
| ...prev.mcp, | |
| pluginReconnectKey: prev.mcp.pluginReconnectKey + 1, | |
| }, | |
| })) | |
| // Re-initialize LSP manager so newly-loaded plugin LSP servers are picked | |
| // up. No-op if LSP was never initialized (headless subcommand path). | |
| // Unconditional so removing the last LSP plugin also clears stale config. | |
| // Fixes issue #15521: LSP manager previously read a stale memoized | |
| // loadAllPlugins() result from before marketplaces were reconciled. | |
| reinitializeLspServerManager() | |
| // clearAllCaches() prunes removed-plugin hooks; this does the FULL swap | |
| // (adds hooks from newly-enabled plugins too). Catching here so | |
| // hook_load_failed can feed error_count; a failure doesn't lose the | |
| // plugin/command/agent data above (hooks go to STATE.registeredHooks, not | |
| // AppState). | |
| let hook_load_failed = false | |
| try { | |
| await loadPluginHooks() | |
| } catch (e) { | |
| hook_load_failed = true | |
| logError(e) | |
| logForDebugging( | |
| `refreshActivePlugins: loadPluginHooks failed: ${errorMessage(e)}`, | |
| ) | |
| } | |
| const hook_count = enabled.reduce((sum, p) => { | |
| if (!p.hooksConfig) return sum | |
| return ( | |
| sum + | |
| Object.values(p.hooksConfig).reduce( | |
| (s, matchers) => | |
| s + (matchers?.reduce((h, m) => h + m.hooks.length, 0) ?? 0), | |
| 0, | |
| ) | |
| ) | |
| }, 0) | |
| logForDebugging( | |
| `refreshActivePlugins: ${enabled.length} enabled, ${pluginCommands.length} commands, ${agentDefinitions.allAgents.length} agents, ${hook_count} hooks, ${mcp_count} MCP, ${lsp_count} LSP`, | |
| ) | |
| return { | |
| enabled_count: enabled.length, | |
| disabled_count: disabled.length, | |
| command_count: pluginCommands.length, | |
| agent_count: agentDefinitions.allAgents.length, | |
| hook_count, | |
| mcp_count, | |
| lsp_count, | |
| error_count: errors.length + (hook_load_failed ? 1 : 0), | |
| agentDefinitions, | |
| pluginCommands, | |
| } | |
| } | |
| /** | |
| * Merge fresh plugin-load errors with existing errors, preserving LSP and | |
| * plugin-component errors that were recorded by other systems and | |
| * deduplicating. Same logic as refreshPlugins()/updatePluginState(), extracted | |
| * so refresh.ts doesn't leave those errors stranded. | |
| */ | |
| function mergePluginErrors( | |
| existing: PluginError[], | |
| fresh: PluginError[], | |
| ): PluginError[] { | |
| const preserved = existing.filter( | |
| e => e.source === 'lsp-manager' || e.source.startsWith('plugin:'), | |
| ) | |
| const freshKeys = new Set(fresh.map(errorKey)) | |
| const deduped = preserved.filter(e => !freshKeys.has(errorKey(e))) | |
| return [...deduped, ...fresh] | |
| } | |
| function errorKey(e: PluginError): string { | |
| return e.type === 'generic-error' | |
| ? `generic-error:${e.source}:${e.error}` | |
| : `${e.type}:${e.source}` | |
| } | |