Skip to content

Commit 4f86ef1

Browse files
committed
feat(mpp-idea): add IdeaToolConfigService for tool config state management
1. Create IdeaToolConfigService: - Project-level service for managing tool configuration state - Provides StateFlow for observing config changes - Uses configVersion counter to trigger UI recomposition - Centralized save/load with notification to listeners 2. Update IdeaToolLoadingStatusBar: - Add project parameter - Observe configVersion from IdeaToolConfigService - Recompute toolStatus when config version changes 3. Update IdeaAgentViewModel: - Use IdeaToolConfigService for loading tool config - Get fresh config from service in getToolLoadingStatus() 4. Update IdeaMcpConfigDialog: - Add project parameter to IdeaMcpConfigDialogContent - Use IdeaToolConfigService.saveAndUpdateConfig() for auto-save - Notify listeners when tools are toggled 5. Register service in plugin.xml This ensures the status bar updates when MCP tools are enabled/disabled in the configuration dialog.
1 parent c5379c2 commit 4f86ef1

File tree

6 files changed

+164
-8
lines changed

6 files changed

+164
-8
lines changed

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/status/IdeaToolLoadingStatusBar.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import androidx.compose.ui.Modifier
1212
import androidx.compose.ui.graphics.Color
1313
import androidx.compose.ui.unit.dp
1414
import androidx.compose.ui.unit.sp
15+
import cc.unitmesh.devins.idea.services.IdeaToolConfigService
1516
import cc.unitmesh.devins.idea.toolwindow.IdeaAgentViewModel
1617
import cc.unitmesh.devins.ui.compose.theme.AutoDevColors
18+
import com.intellij.openapi.project.Project
1719
import org.jetbrains.jewel.foundation.theme.JewelTheme
1820
import org.jetbrains.jewel.ui.component.Text
1921

@@ -23,12 +25,18 @@ import org.jetbrains.jewel.ui.component.Text
2325
@Composable
2426
fun IdeaToolLoadingStatusBar(
2527
viewModel: IdeaAgentViewModel,
28+
project: Project,
2629
modifier: Modifier = Modifier
2730
) {
2831
val mcpPreloadingMessage by viewModel.mcpPreloadingMessage.collectAsState()
2932
val mcpPreloadingStatus by viewModel.mcpPreloadingStatus.collectAsState()
30-
// Recompute when preloading status changes to make it reactive
31-
val toolStatus = remember(mcpPreloadingStatus) { viewModel.getToolLoadingStatus() }
33+
34+
// Observe tool config service for configuration changes
35+
val toolConfigService = remember { IdeaToolConfigService.getInstance(project) }
36+
val configVersion by toolConfigService.configVersion.collectAsState()
37+
38+
// Recompute when preloading status OR config version changes
39+
val toolStatus = remember(mcpPreloadingStatus, configVersion) { viewModel.getToolLoadingStatus() }
3240

3341
Row(
3442
modifier = modifier

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaMcpConfigDialog.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp
2525
import androidx.compose.ui.unit.sp
2626
import cc.unitmesh.agent.config.*
2727
import cc.unitmesh.agent.mcp.McpServerConfig
28+
import cc.unitmesh.devins.idea.services.IdeaToolConfigService
2829
import cc.unitmesh.devins.idea.toolwindow.IdeaComposeIcons
2930
import cc.unitmesh.devins.ui.config.ConfigManager
3031
import com.intellij.openapi.project.Project
@@ -108,7 +109,10 @@ class IdeaMcpConfigDialogWrapper(
108109

109110
override fun createCenterPanel(): JComponent {
110111
val dialogPanel = compose {
111-
IdeaMcpConfigDialogContent(onDismiss = { close(CANCEL_EXIT_CODE) })
112+
IdeaMcpConfigDialogContent(
113+
project = project,
114+
onDismiss = { close(CANCEL_EXIT_CODE) }
115+
)
112116
}
113117
dialogPanel.preferredSize = Dimension(850, 650)
114118
return dialogPanel
@@ -132,6 +136,7 @@ class IdeaMcpConfigDialogWrapper(
132136
*/
133137
@Composable
134138
fun IdeaMcpConfigDialogContent(
139+
project: Project?,
135140
onDismiss: () -> Unit
136141
) {
137142
var toolConfig by remember { mutableStateOf(ToolConfigFile.default()) }
@@ -148,6 +153,11 @@ fun IdeaMcpConfigDialogContent(
148153

149154
val scope = rememberCoroutineScope()
150155

156+
// Get tool config service for notifying state changes
157+
val toolConfigService = remember(project) {
158+
project?.let { IdeaToolConfigService.getInstance(it) }
159+
}
160+
151161
// Auto-save function
152162
fun scheduleAutoSave() {
153163
hasUnsavedChanges = true
@@ -168,7 +178,12 @@ fun IdeaMcpConfigDialogContent(
168178
mcpServers = newMcpServers
169179
)
170180

171-
ConfigManager.saveToolConfig(updatedConfig)
181+
// Use service to save and notify listeners
182+
if (toolConfigService != null) {
183+
toolConfigService.saveAndUpdateConfig(updatedConfig)
184+
} else {
185+
ConfigManager.saveToolConfig(updatedConfig)
186+
}
172187
toolConfig = updatedConfig
173188
hasUnsavedChanges = false
174189
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package cc.unitmesh.devins.idea.services
2+
3+
import cc.unitmesh.agent.config.ToolConfigFile
4+
import cc.unitmesh.devins.ui.config.ConfigManager
5+
import com.intellij.openapi.Disposable
6+
import com.intellij.openapi.components.Service
7+
import com.intellij.openapi.components.service
8+
import com.intellij.openapi.diagnostic.Logger
9+
import com.intellij.openapi.project.Project
10+
import kotlinx.coroutines.flow.MutableStateFlow
11+
import kotlinx.coroutines.flow.StateFlow
12+
import kotlinx.coroutines.flow.asStateFlow
13+
import kotlinx.coroutines.runBlocking
14+
15+
/**
16+
* Project-level service for managing tool configuration state.
17+
*
18+
* This service provides a centralized way to:
19+
* 1. Load and cache tool configuration
20+
* 2. Notify listeners when configuration changes
21+
* 3. Track enabled/disabled MCP tools count
22+
*
23+
* Components like IdeaToolLoadingStatusBar and IdeaAgentViewModel can observe
24+
* the toolConfigState to react to configuration changes.
25+
*/
26+
@Service(Service.Level.PROJECT)
27+
class IdeaToolConfigService(private val project: Project) : Disposable {
28+
29+
private val logger = Logger.getInstance(IdeaToolConfigService::class.java)
30+
31+
// Tool configuration state
32+
private val _toolConfigState = MutableStateFlow(ToolConfigState())
33+
val toolConfigState: StateFlow<ToolConfigState> = _toolConfigState.asStateFlow()
34+
35+
// Version counter to force recomposition when config changes
36+
private val _configVersion = MutableStateFlow(0L)
37+
val configVersion: StateFlow<Long> = _configVersion.asStateFlow()
38+
39+
init {
40+
// Load initial configuration
41+
reloadConfig()
42+
}
43+
44+
/**
45+
* Reload configuration from disk and update state.
46+
* Uses runBlocking since this is called from non-suspend context.
47+
*/
48+
fun reloadConfig() {
49+
try {
50+
val toolConfig = runBlocking { ConfigManager.loadToolConfig() }
51+
updateState(toolConfig)
52+
logger.debug("Tool configuration reloaded: ${toolConfig.enabledMcpTools.size} enabled tools")
53+
} catch (e: Exception) {
54+
logger.warn("Failed to reload tool configuration: ${e.message}")
55+
}
56+
}
57+
58+
/**
59+
* Update the tool configuration state.
60+
* Call this after saving configuration changes.
61+
*/
62+
fun updateState(toolConfig: ToolConfigFile) {
63+
val enabledMcpToolsCount = toolConfig.enabledMcpTools.size
64+
val mcpServersCount = toolConfig.mcpServers.filter { !it.value.disabled }.size
65+
66+
_toolConfigState.value = ToolConfigState(
67+
toolConfig = toolConfig,
68+
enabledMcpToolsCount = enabledMcpToolsCount,
69+
mcpServersCount = mcpServersCount,
70+
lastUpdated = System.currentTimeMillis()
71+
)
72+
73+
// Increment version to trigger recomposition
74+
_configVersion.value++
75+
76+
logger.debug("Tool config state updated: $enabledMcpToolsCount enabled tools, $mcpServersCount servers")
77+
}
78+
79+
/**
80+
* Save tool configuration and update state.
81+
* Uses runBlocking since this is called from non-suspend context.
82+
*/
83+
fun saveAndUpdateConfig(toolConfig: ToolConfigFile) {
84+
try {
85+
runBlocking { ConfigManager.saveToolConfig(toolConfig) }
86+
updateState(toolConfig)
87+
logger.debug("Tool configuration saved and state updated")
88+
} catch (e: Exception) {
89+
logger.error("Failed to save tool configuration: ${e.message}")
90+
}
91+
}
92+
93+
/**
94+
* Get the current tool configuration.
95+
*/
96+
fun getToolConfig(): ToolConfigFile {
97+
return _toolConfigState.value.toolConfig
98+
}
99+
100+
override fun dispose() {
101+
// Cleanup if needed
102+
}
103+
104+
companion object {
105+
fun getInstance(project: Project): IdeaToolConfigService = project.service()
106+
}
107+
}
108+
109+
/**
110+
* Data class representing the current tool configuration state.
111+
*/
112+
data class ToolConfigState(
113+
val toolConfig: ToolConfigFile = ToolConfigFile.default(),
114+
val enabledMcpToolsCount: Int = 0,
115+
val mcpServersCount: Int = 0,
116+
val lastUpdated: Long = 0L
117+
)
118+

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ fun IdeaAgentApp(
245245
// Tool loading status bar
246246
IdeaToolLoadingStatusBar(
247247
viewModel = viewModel,
248+
project = project,
248249
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp)
249250
)
250251
}

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import cc.unitmesh.agent.tool.schema.ToolCategory
1212
import cc.unitmesh.devins.compiler.service.DevInsCompilerService
1313
import cc.unitmesh.devins.idea.compiler.IdeaDevInsCompilerService
1414
import cc.unitmesh.devins.idea.renderer.JewelRenderer
15+
import cc.unitmesh.devins.idea.services.IdeaToolConfigService
1516
import cc.unitmesh.devins.ui.config.AutoDevConfigWrapper
1617
import cc.unitmesh.devins.ui.config.ConfigManager
1718
import cc.unitmesh.llm.KoogLLMService
@@ -156,7 +157,11 @@ class IdeaAgentViewModel(
156157
private suspend fun startMcpPreloading() {
157158
try {
158159
_mcpPreloadingMessage.value = "Loading MCP servers configuration..."
159-
val toolConfig = ConfigManager.loadToolConfig()
160+
161+
// Use IdeaToolConfigService to get and cache tool config
162+
val toolConfigService = IdeaToolConfigService.getInstance(project)
163+
toolConfigService.reloadConfig()
164+
val toolConfig = toolConfigService.getToolConfig()
160165
cachedToolConfig = toolConfig
161166

162167
if (toolConfig.mcpServers.isEmpty()) {
@@ -463,18 +468,25 @@ class IdeaAgentViewModel(
463468
/**
464469
* Get tool loading status.
465470
* Aligned with CodingAgentViewModel's getToolLoadingStatus().
471+
* Uses IdeaToolConfigService for up-to-date configuration.
466472
*/
467473
fun getToolLoadingStatus(): ToolLoadingStatus {
468-
val toolConfig = cachedToolConfig
474+
// Get fresh config from service to ensure we have latest changes
475+
val toolConfigService = IdeaToolConfigService.getInstance(project)
476+
val toolConfig = toolConfigService.getToolConfig()
477+
478+
// Update cached config
479+
cachedToolConfig = toolConfig
480+
469481
val subAgentTools = ToolType.byCategory(ToolCategory.SubAgent)
470482
val subAgentsEnabled = subAgentTools.size
471-
val mcpServersTotal = toolConfig?.mcpServers?.filter { !it.value.disabled }?.size ?: 0
483+
val mcpServersTotal = toolConfig.mcpServers.filter { !it.value.disabled }.size
472484
val mcpServersLoaded = _mcpPreloadingStatus.value.preloadedServers.size
473485

474486
val mcpToolsEnabled = if (McpToolConfigManager.isPreloading()) {
475487
0
476488
} else {
477-
val enabledMcpToolsCount = toolConfig?.enabledMcpTools?.size ?: 0
489+
val enabledMcpToolsCount = toolConfig.enabledMcpTools.size
478490
if (enabledMcpToolsCount > 0) enabledMcpToolsCount else 0
479491
}
480492

mpp-idea/src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
<extensions defaultExtensionNs="com.intellij">
5959
<!-- Project-level service for coroutine scope management -->
6060
<projectService serviceImplementation="cc.unitmesh.devins.idea.services.CoroutineScopeHolder"/>
61+
<!-- Project-level service for tool configuration state management -->
62+
<projectService serviceImplementation="cc.unitmesh.devins.idea.services.IdeaToolConfigService"/>
6163
<!-- ToolWindow for Agent UI with tab-based navigation (Advanced version) -->
6264
<toolWindow factoryClass="cc.unitmesh.devins.idea.toolwindow.IdeaAgentToolWindowFactory"
6365
id="AutoDev Agent"

0 commit comments

Comments
 (0)