( setAppState: SetAppState, )
| 70 | * re-reads config. Servers are lazy-started so this is just config parsing. |
| 71 | */ |
| 72 | export async function refreshActivePlugins( |
| 73 | setAppState: SetAppState, |
| 74 | ): Promise<RefreshActivePluginsResult> { |
| 75 | logForDebugging('refreshActivePlugins: clearing all plugin caches') |
| 76 | clearAllCaches() |
| 77 | // Orphan exclusions are session-frozen by default, but /reload-plugins is |
| 78 | // an explicit "disk changed, re-read it" signal — recompute them too. |
| 79 | clearPluginCacheExclusions() |
| 80 | |
| 81 | // Sequence the full load before cache-only consumers. Before #23693 all |
| 82 | // three shared loadAllPlugins()'s memoize promise so Promise.all was a |
| 83 | // no-op race. After #23693 getPluginCommands/getAgentDefinitions call |
| 84 | // loadAllPluginsCacheOnly (separate memoize) — racing them means they |
| 85 | // read installed_plugins.json before loadAllPlugins() has cloned+cached |
| 86 | // the plugin, returning plugin-cache-miss. loadAllPlugins warms the |
| 87 | // cache-only memoize on completion, so the awaits below are ~free. |
| 88 | const pluginResult = await loadAllPlugins() |
| 89 | const [pluginCommands, agentDefinitions] = await Promise.all([ |
| 90 | getPluginCommands(), |
| 91 | getAgentDefinitionsWithOverrides(getOriginalCwd()), |
| 92 | ]) |
| 93 | |
| 94 | const { enabled, disabled, errors } = pluginResult |
| 95 | |
| 96 | // Populate mcpServers/lspServers on each enabled plugin. These are lazy |
| 97 | // cache slots NOT filled by loadAllPlugins() — they're written later by |
| 98 | // extractMcpServersFromPlugins/getPluginLspServers, which races with this. |
| 99 | // Loading here gives accurate metrics AND warms the cache slots so the MCP |
| 100 | // connection manager (triggered by pluginReconnectKey bump) sees the servers |
| 101 | // without re-parsing manifests. Errors are pushed to the shared errors array. |
| 102 | const [mcpCounts, lspCounts] = await Promise.all([ |
| 103 | Promise.all( |
| 104 | enabled.map(async p => { |
| 105 | if (p.mcpServers) return Object.keys(p.mcpServers).length |
| 106 | const servers = await loadPluginMcpServers(p, errors) |
| 107 | if (servers) p.mcpServers = servers |
| 108 | return servers ? Object.keys(servers).length : 0 |
| 109 | }), |
| 110 | ), |
| 111 | Promise.all( |
| 112 | enabled.map(async p => { |
| 113 | if (p.lspServers) return Object.keys(p.lspServers).length |
| 114 | const servers = await loadPluginLspServers(p, errors) |
| 115 | if (servers) p.lspServers = servers |
| 116 | return servers ? Object.keys(servers).length : 0 |
| 117 | }), |
| 118 | ), |
| 119 | ]) |
| 120 | const mcp_count = mcpCounts.reduce((sum, n) => sum + n, 0) |
| 121 | const lsp_count = lspCounts.reduce((sum, n) => sum + n, 0) |
| 122 | |
| 123 | setAppState(prev => ({ |
| 124 | ...prev, |
| 125 | plugins: { |
| 126 | ...prev.plugins, |
| 127 | enabled, |
| 128 | disabled, |
| 129 | commands: pluginCommands, |
no test coverage detected