Skip to content

Commit 6d15e84

Browse files
committed
feat(ui): add ACP agent configuration dialog and page #535
- Add AcpAgentConfigDialog for managing external ACP agents (Kimi CLI, Claude CLI, etc.) - Add AcpAgentPage as main UI for interacting with external ACP agents - Support agent configuration, connection status, and streaming response rendering - Integrate with existing config system and timeline rendering
1 parent 7a42188 commit 6d15e84

File tree

18 files changed

+1766
-3
lines changed

18 files changed

+1766
-3
lines changed

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package cc.unitmesh.agent
1010
* - KNOWLEDGE: Document reader mode for AI-native document reading
1111
* - CHAT_DB: Database chat mode for text-to-SQL interactions
1212
* - REMOTE: Remote agent connected to mpp-server
13+
* - CUSTOM_AGENT: External ACP (Agent Client Protocol) agent (e.g., Kimi CLI, Claude CLI)
1314
*/
1415
enum class AgentType {
1516
/**
@@ -51,7 +52,14 @@ enum class AgentType {
5152
* Artifact mode - generate reversible, executable artifacts (HTML/JS, Python scripts)
5253
* Similar to Claude's Artifacts system
5354
*/
54-
ARTIFACT;
55+
ARTIFACT,
56+
57+
/**
58+
* Custom ACP agent mode - connects to external ACP-compliant agents via stdio.
59+
* Examples: Kimi CLI, Claude CLI (--acp), Gemini CLI (--acp), or any ACP agent.
60+
* When active, all interaction goes through the external agent; local LLM is not used.
61+
*/
62+
CUSTOM_AGENT;
5563

5664
fun getDisplayName(): String = when (this) {
5765
LOCAL_CHAT -> "Chat"
@@ -62,6 +70,7 @@ enum class AgentType {
6270
REMOTE -> "Remote"
6371
WEB_EDIT -> "WebEdit"
6472
ARTIFACT -> "Artifact"
73+
CUSTOM_AGENT -> "Custom Agent"
6574
}
6675

6776
companion object {
@@ -75,6 +84,7 @@ enum class AgentType {
7584
"chatdb", "database" -> CHAT_DB
7685
"webedit", "web" -> WEB_EDIT
7786
"artifact", "unit" -> ARTIFACT
87+
"customagent", "custom_agent", "acp" -> CUSTOM_AGENT
7888
else -> LOCAL_CHAT
7989
}
8090
}

mpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigFile.kt

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ import kotlinx.serialization.Serializable
2929
* url: "http://localhost:8080"
3030
* enabled: false
3131
* useServerConfig: false
32+
* acpAgents:
33+
* kimi:
34+
* name: "Kimi CLI"
35+
* command: "kimi"
36+
* args: "--acp"
37+
* env: ""
38+
* claude:
39+
* name: "Claude CLI"
40+
* command: "claude"
41+
* args: "--acp"
42+
* env: ""
43+
* activeAcpAgent: kimi
3244
* ```
3345
*/
3446
@Serializable
@@ -41,7 +53,9 @@ public data class ConfigFile(
4153
val agentType: String? = "Local", // "Local" or "Remote" - which agent mode to use
4254
val lastWorkspace: WorkspaceInfo? = null, // Last opened workspace information
4355
val issueTracker: IssueTrackerConfig? = null, // Issue tracker configuration
44-
val cloudStorage: CloudStorageConfig? = null // Cloud storage for multimodal image upload
56+
val cloudStorage: CloudStorageConfig? = null, // Cloud storage for multimodal image upload
57+
val acpAgents: Map<String, AcpAgentConfig>? = null, // ACP agent configurations
58+
val activeAcpAgent: String? = null // Currently active ACP agent key
4559
)
4660

4761
/**
@@ -118,6 +132,58 @@ data class CloudStorageConfig(
118132
}
119133
}
120134

135+
/**
136+
* ACP (Agent Client Protocol) agent configuration.
137+
*
138+
* Defines an external ACP-compliant agent that can be spawned as a child process
139+
* and communicated with via JSON-RPC over stdio.
140+
*
141+
* Example config.yaml:
142+
* ```yaml
143+
* acpAgents:
144+
* kimi:
145+
* name: "Kimi CLI"
146+
* command: "kimi"
147+
* args: "--acp"
148+
* env: "KIMI_API_KEY=xxx"
149+
* claude:
150+
* name: "Claude CLI"
151+
* command: "claude"
152+
* args: "--acp"
153+
* env: ""
154+
* activeAcpAgent: kimi
155+
* ```
156+
*/
157+
@Serializable
158+
data class AcpAgentConfig(
159+
val name: String = "",
160+
val command: String = "",
161+
val args: String = "", // Space-separated arguments (e.g., "--acp --verbose")
162+
val env: String = "" // Environment variables, one per line: "KEY=VALUE"
163+
) {
164+
fun isConfigured(): Boolean {
165+
return command.isNotBlank()
166+
}
167+
168+
fun getArgsList(): List<String> {
169+
return args.trim().split("\\s+".toRegex()).filter { it.isNotBlank() }
170+
}
171+
172+
fun getEnvMap(): Map<String, String> {
173+
val result = mutableMapOf<String, String>()
174+
env.lines().forEach { line ->
175+
val trimmed = line.trim()
176+
if (trimmed.isEmpty() || trimmed.startsWith("#")) return@forEach
177+
val idx = trimmed.indexOf('=')
178+
if (idx <= 0) return@forEach
179+
val key = trimmed.substring(0, idx).trim()
180+
val value = trimmed.substring(idx + 1).trim()
181+
result[key] = value
182+
}
183+
return result
184+
}
185+
}
186+
121187
/**
122188
* Configuration file wrapper with utility methods
123189
*/
@@ -210,6 +276,19 @@ class AutoDevConfigWrapper(val configFile: ConfigFile) {
210276
return configFile.cloudStorage?.isConfigured() == true
211277
}
212278

279+
fun getAcpAgents(): Map<String, AcpAgentConfig> {
280+
return configFile.acpAgents ?: emptyMap()
281+
}
282+
283+
fun getActiveAcpAgent(): AcpAgentConfig? {
284+
val key = configFile.activeAcpAgent ?: return null
285+
return configFile.acpAgents?.get(key)
286+
}
287+
288+
fun getActiveAcpAgentKey(): String? {
289+
return configFile.activeAcpAgent
290+
}
291+
213292
companion object {
214293
/**
215294
* Save agent type preference to config file
@@ -227,6 +306,45 @@ class AutoDevConfigWrapper(val configFile: ConfigFile) {
227306
throw e
228307
}
229308
}
309+
310+
/**
311+
* Save ACP agent configurations to config file.
312+
*
313+
* @param acpAgents Map of agent key to AcpAgentConfig
314+
* @param activeKey The key of the currently active ACP agent (null to keep unchanged)
315+
*/
316+
suspend fun saveAcpAgents(
317+
acpAgents: Map<String, AcpAgentConfig>,
318+
activeKey: String? = null
319+
) {
320+
try {
321+
val currentConfig = ConfigManager.load()
322+
val updatedConfig = currentConfig.configFile.copy(
323+
acpAgents = acpAgents,
324+
activeAcpAgent = activeKey ?: currentConfig.configFile.activeAcpAgent
325+
)
326+
ConfigManager.save(updatedConfig)
327+
println("ACP agent configurations saved: ${acpAgents.keys}")
328+
} catch (e: Exception) {
329+
println("Failed to save ACP agent configurations: ${e.message}")
330+
throw e
331+
}
332+
}
333+
334+
/**
335+
* Save the active ACP agent key.
336+
*/
337+
suspend fun saveActiveAcpAgent(key: String) {
338+
try {
339+
val currentConfig = ConfigManager.load()
340+
val updatedConfig = currentConfig.configFile.copy(activeAcpAgent = key)
341+
ConfigManager.save(updatedConfig)
342+
println("Active ACP agent saved: $key")
343+
} catch (e: Exception) {
344+
println("Failed to save active ACP agent: ${e.message}")
345+
throw e
346+
}
347+
}
230348
}
231349
}
232350

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ private fun getAgentTypeColor(type: AgentType): Color = when (type) {
261261
AgentType.LOCAL_CHAT -> JewelTheme.globalColors.text.normal
262262
AgentType.WEB_EDIT -> IdeaAutoDevColors.Blue.c400
263263
AgentType.ARTIFACT -> IdeaAutoDevColors.Indigo.c400
264+
AgentType.CUSTOM_AGENT -> IdeaAutoDevColors.Amber.c400
264265
}
265266

266267
/**
@@ -275,5 +276,6 @@ private fun getAgentTypeIcon(type: AgentType): ImageVector = when (type) {
275276
AgentType.LOCAL_CHAT -> IdeaComposeIcons.Chat
276277
AgentType.WEB_EDIT -> IdeaComposeIcons.Web
277278
AgentType.ARTIFACT -> IdeaComposeIcons.Description
279+
AgentType.CUSTOM_AGENT -> IdeaComposeIcons.Code
278280
}
279281

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,13 @@ fun IdeaAgentApp(
442442
IdeaEmptyStateMessage("ChatDB Agent coming soon...")
443443
}
444444
}
445+
AgentType.CUSTOM_AGENT -> {
446+
// Custom ACP agents are handled via the ACP tab in Remote mode
447+
// In the IDEA plugin, users configure ACP agents through the Remote panel
448+
Box(modifier = Modifier.fillMaxWidth().weight(1f)) {
449+
IdeaEmptyStateMessage("Custom ACP Agent - Use Remote > ACP tab to configure")
450+
}
451+
}
445452
}
446453

447454
// Tool loading status bar
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package cc.unitmesh.devins.ui.compose.agent.acp
2+
3+
actual fun createAcpConnection(): AcpConnection? = null
4+
5+
actual fun isAcpSupported(): Boolean = false

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cc.unitmesh.devins.ui.compose.agent
33
import androidx.compose.runtime.Composable
44
import androidx.compose.ui.Modifier
55
import cc.unitmesh.agent.AgentType
6+
import cc.unitmesh.devins.ui.compose.agent.acp.AcpAgentPage
67
import cc.unitmesh.devins.ui.compose.agent.artifact.ArtifactPage
78
import cc.unitmesh.devins.ui.compose.agent.chatdb.ChatDBPage
89
import cc.unitmesh.devins.ui.compose.agent.codereview.CodeReviewPage
@@ -20,6 +21,7 @@ import cc.unitmesh.agent.artifact.ArtifactBundle
2021
* - CODING: Full-featured coding agent with tools
2122
* - CODE_REVIEW: Dedicated code review interface
2223
* - REMOTE: Remote agent connected to mpp-server
24+
* - CUSTOM_AGENT: External ACP agent (e.g., Kimi CLI, Claude CLI)
2325
*/
2426
@Composable
2527
fun AgentInterfaceRouter(
@@ -158,6 +160,16 @@ fun AgentInterfaceRouter(
158160
)
159161
}
160162

163+
AgentType.CUSTOM_AGENT -> {
164+
AcpAgentPage(
165+
modifier = modifier,
166+
onBack = {
167+
onAgentTypeChange(AgentType.CODING)
168+
},
169+
onNotification = onNotification
170+
)
171+
}
172+
161173
AgentType.LOCAL_CHAT,
162174
AgentType.CODING -> {
163175
CodingAgentPage(

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,11 @@ fun CodingAgentPage(
365365
// ARTIFACT has its own full-page interface (ArtifactPage)
366366
// It should not reach here - handled by AgentInterfaceRouter
367367
}
368+
369+
AgentType.CUSTOM_AGENT -> {
370+
// CUSTOM_AGENT has its own full-page interface (AcpAgentPage)
371+
// It should not reach here - handled by AgentInterfaceRouter
372+
}
368373
}
369374

370375
ToolLoadingStatusBar(

0 commit comments

Comments
 (0)