From d5d156729064453f9c9853e2b83690c29e7a339d Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Mon, 1 Dec 2025 22:24:28 +0800 Subject: [PATCH 1/8] chore(deps): bump mppVersion to 0.3.3 Update mppVersion in gradle.properties to use the latest release. --- gradle.properties | 2 +- mpp-ui/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index e9a09cbcc9..3426d92392 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ pluginRepositoryUrl = https://github.com/unit-mesh/auto-dev pluginVersion = 2.4.6 # MPP Unified Version (mpp-core, mpp-ui, mpp-server) -mppVersion = 0.3.2 +mppVersion = 0.3.3 # Supported IDEs: idea, pycharm baseIDE=idea diff --git a/mpp-ui/package.json b/mpp-ui/package.json index 470c028bed..acd01a6ee0 100644 --- a/mpp-ui/package.json +++ b/mpp-ui/package.json @@ -1,6 +1,6 @@ { "name": "@autodev/cli", - "version": "0.3.2", + "version": "0.3.3", "description": "AutoDev CLI - Terminal UI for AI-powered development assistant", "type": "module", "bin": { From 18de68979fbe1f2854783fe15bf296374f2d913c Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Mon, 1 Dec 2025 22:50:40 +0800 Subject: [PATCH 2/8] feat(idea): improve popup rendering and config name handling Switch model selector dropdown to Jewel's PopupMenu for correct z-index with SwingPanel. Enable custom popup rendering to fix Compose/Swing overlay issues. Auto-increment config names to avoid duplicates when saving. --- .../devins/idea/editor/IdeaModelSelector.kt | 175 ++++++------------ .../devins/idea/toolwindow/IdeaAgentApp.kt | 25 ++- .../toolwindow/IdeaAgentToolWindowFactory.kt | 5 + 3 files changed, 88 insertions(+), 117 deletions(-) diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelSelector.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelSelector.kt index 5cb2da0250..1c54a7d10f 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelSelector.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelSelector.kt @@ -6,27 +6,21 @@ import androidx.compose.foundation.hoverable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.InputMode import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Popup -import androidx.compose.ui.window.PopupProperties -import androidx.compose.ui.Alignment as ComposeAlignment import cc.unitmesh.devins.idea.toolwindow.IdeaComposeIcons -import cc.unitmesh.llm.ModelConfig import cc.unitmesh.llm.NamedModelConfig import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.ui.component.Divider import org.jetbrains.jewel.ui.component.Icon -import org.jetbrains.jewel.ui.component.OutlinedButton +import org.jetbrains.jewel.ui.component.PopupMenu import org.jetbrains.jewel.ui.component.Text -import org.jetbrains.jewel.ui.Orientation +import org.jetbrains.jewel.ui.component.separator /** * Model selector for IntelliJ IDEA plugin. @@ -94,56 +88,70 @@ fun IdeaModelSelector( ) } - // Dropdown popup - positioned above the selector to avoid covering input area + // Dropdown popup using Jewel's PopupMenu for proper z-index handling with SwingPanel if (expanded) { - Popup( - alignment = ComposeAlignment.BottomStart, - onDismissRequest = { expanded = false }, - properties = PopupProperties(focusable = true) + PopupMenu( + onDismissRequest = { + expanded = false + true + }, + horizontalAlignment = Alignment.Start, + modifier = Modifier.widthIn(min = 200.dp, max = 300.dp) ) { - Box( - modifier = Modifier - .widthIn(min = 200.dp, max = 300.dp) - .clip(RoundedCornerShape(8.dp)) - .background(JewelTheme.globalColors.panelBackground) - .padding(4.dp) - ) { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()) - ) { - if (availableConfigs.isNotEmpty()) { - availableConfigs.forEach { config -> - IdeaDropdownMenuItem( - text = "${config.provider} / ${config.model}", - isSelected = config.name == currentConfigName, - onClick = { - onConfigSelect(config) - expanded = false - } - ) + if (availableConfigs.isNotEmpty()) { + availableConfigs.forEach { config -> + selectableItem( + selected = config.name == currentConfigName, + onClick = { + onConfigSelect(config) + expanded = false } - - Divider(Orientation.Horizontal, modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) - } else { - IdeaDropdownMenuItem( - text = "No saved configs", - isSelected = false, - enabled = false, - onClick = {} + ) { + Text( + text = "${config.provider} / ${config.model}", + style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp) ) - - Divider(Orientation.Horizontal, modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) } + } + separator() + } else { + selectableItem( + selected = false, + enabled = false, + onClick = {} + ) { + Text( + text = "No saved configs", + style = JewelTheme.defaultTextStyle.copy( + fontSize = 13.sp, + color = JewelTheme.globalColors.text.normal.copy(alpha = 0.5f) + ) + ) + } + separator() + } - // Configure button - IdeaDropdownMenuItem( + // Configure button + selectableItem( + selected = false, + onClick = { + onConfigureClick() + expanded = false + } + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = IdeaComposeIcons.Settings, + contentDescription = null, + tint = JewelTheme.globalColors.text.normal, + modifier = Modifier.size(16.dp) + ) + Text( text = "Configure Model...", - isSelected = false, - leadingIcon = IdeaComposeIcons.Settings, - onClick = { - onConfigureClick() - expanded = false - } + style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp) ) } } @@ -151,66 +159,3 @@ fun IdeaModelSelector( } } } - -/** - * Individual menu item for IdeaModelSelector dropdown. - */ -@Composable -private fun IdeaDropdownMenuItem( - text: String, - isSelected: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, - leadingIcon: androidx.compose.ui.graphics.vector.ImageVector? = null -) { - val backgroundColor = when { - !enabled -> JewelTheme.globalColors.panelBackground - isSelected -> JewelTheme.globalColors.panelBackground.copy(alpha = 0.6f) - else -> JewelTheme.globalColors.panelBackground - } - - val textColor = when { - !enabled -> JewelTheme.globalColors.text.normal.copy(alpha = 0.5f) - else -> JewelTheme.globalColors.text.normal - } - - Row( - modifier = modifier - .fillMaxWidth() - .clip(RoundedCornerShape(4.dp)) - .background(backgroundColor) - .then(if (enabled) Modifier.clickable(onClick = onClick) else Modifier) - .padding(horizontal = 12.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - if (leadingIcon != null) { - Icon( - imageVector = leadingIcon, - contentDescription = null, - tint = textColor, - modifier = Modifier.size(16.dp) - ) - } - - Text( - text = text, - style = JewelTheme.defaultTextStyle.copy( - fontSize = 13.sp, - color = textColor - ), - modifier = Modifier.weight(1f) - ) - - if (isSelected) { - Icon( - imageVector = IdeaComposeIcons.Check, - contentDescription = "Selected", - tint = JewelTheme.globalColors.text.normal, - modifier = Modifier.size(16.dp) - ) - } - } -} - diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt index 2f1f07e8c4..c782454366 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt @@ -25,6 +25,7 @@ import cc.unitmesh.devins.idea.toolwindow.remote.getEffectiveProjectId import cc.unitmesh.devins.idea.components.status.IdeaToolLoadingStatusBar import cc.unitmesh.devins.idea.components.timeline.IdeaEmptyStateMessage import cc.unitmesh.devins.idea.components.timeline.IdeaTimelineContent +import cc.unitmesh.devins.ui.config.ConfigManager import cc.unitmesh.llm.ModelConfig import cc.unitmesh.llm.NamedModelConfig import com.intellij.openapi.Disposable @@ -265,10 +266,30 @@ fun IdeaAgentApp( currentConfig = dialogConfig, currentConfigName = currentConfigName, onDismiss = { viewModel.setShowConfigDialog(false) }, - onSave = { name, config -> - val namedConfig = NamedModelConfig.fromModelConfig(name, config) + onSave = { configName, newModelConfig -> + // If creating a new config (not editing current), ensure unique name + val existingNames = availableConfigs.map { it.name } + val finalConfigName = + if (currentConfigName != configName && configName in existingNames) { + // Auto-increment: my-glm -> my-glm-1 -> my-glm-2, etc. + ConfigManager.generateUniqueConfigName(configName, existingNames) + } else { + configName + } + + // Convert ModelConfig to NamedModelConfig + val namedConfig = NamedModelConfig.fromModelConfig( + name = finalConfigName, + config = newModelConfig + ) + + // Save to file viewModel.saveModelConfig(namedConfig, setActive = true) viewModel.setShowConfigDialog(false) + + if (finalConfigName != configName) { + println("✅ 配置名称已存在,自动重命名为: $finalConfigName") + } } ) } diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentToolWindowFactory.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentToolWindowFactory.kt index 0d11ca39f7..3c3da4c131 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentToolWindowFactory.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentToolWindowFactory.kt @@ -8,6 +8,7 @@ import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowFactory import cc.unitmesh.devins.idea.services.CoroutineScopeHolder import org.jetbrains.jewel.bridge.addComposeTab +import org.jetbrains.jewel.foundation.JewelFlags /** * Factory for creating the Agent ToolWindow with tab-based navigation. @@ -28,6 +29,10 @@ class IdeaAgentToolWindowFactory : ToolWindowFactory { } override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { + // Enable custom popup rendering to use JBPopup instead of default Compose implementation + // This fixes z-index issues when Compose Popup is used with SwingPanel (e.g., EditorTextField) + JewelFlags.useCustomPopupRenderer = true + createAgentPanel(project, toolWindow) } From 909f34aed18ddab3494027e204e5b73af1ab8bbc Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Mon, 1 Dec 2025 23:02:17 +0800 Subject: [PATCH 3/8] fix(android): add exit code and execution time to LiveTerminalItem Extend LiveTerminalItem to include exit code, execution time, and output parameters for enhanced terminal session details. --- .../devins/ui/compose/agent/LiveTerminalItem.android.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/agent/LiveTerminalItem.android.kt b/mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/agent/LiveTerminalItem.android.kt index e7ba4ac61f..f04049280b 100644 --- a/mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/agent/LiveTerminalItem.android.kt +++ b/mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/agent/LiveTerminalItem.android.kt @@ -19,7 +19,10 @@ actual fun LiveTerminalItem( sessionId: String, command: String, workingDirectory: String?, - ptyHandle: Any? + ptyHandle: Any?, + exitCode: Int?, + executionTimeMs: Long?, + output: String? ) { Card( colors = From d332532a12e54b023282e43285312da45218f589 Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Mon, 1 Dec 2025 23:09:55 +0800 Subject: [PATCH 4/8] refactor(idea): replace PromptOptimizationDialog with ModelConfigDialogWrapper Replaces the PromptOptimizationDialog with a new ModelConfigDialogWrapper and updates related usages for improved dialog management. --- .../idea/editor/IdeaModelConfigDialog.kt | 502 +++++++++--------- .../editor/IdeaModelConfigDialogWrapper.kt | 68 +++ .../editor/IdeaPromptOptimizationDialog.kt | 186 ------- .../devins/idea/toolwindow/IdeaAgentApp.kt | 62 +-- 4 files changed, 347 insertions(+), 471 deletions(-) create mode 100644 mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelConfigDialogWrapper.kt delete mode 100644 mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaPromptOptimizationDialog.kt diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelConfigDialog.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelConfigDialog.kt index d6a2fa535d..751edcff5d 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelConfigDialog.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelConfigDialog.kt @@ -19,8 +19,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import androidx.compose.ui.window.Popup -import androidx.compose.ui.window.PopupProperties import cc.unitmesh.devins.idea.toolwindow.IdeaComposeIcons import cc.unitmesh.llm.LLMProviderType import cc.unitmesh.llm.ModelConfig @@ -34,6 +32,8 @@ import org.jetbrains.jewel.ui.component.TextField /** * Dialog for configuring LLM model settings for IntelliJ IDEA plugin. * Uses Jewel components for native IntelliJ look and feel. + * + * @deprecated Use IdeaModelConfigDialogWrapper.show() instead for proper z-index handling with SwingPanel. */ @Composable fun IdeaModelConfigDialog( @@ -41,6 +41,30 @@ fun IdeaModelConfigDialog( currentConfigName: String? = null, onDismiss: () -> Unit, onSave: (configName: String, config: ModelConfig) -> Unit +) { + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + IdeaModelConfigDialogContent( + currentConfig = currentConfig, + currentConfigName = currentConfigName, + onDismiss = onDismiss, + onSave = onSave + ) + } +} + +/** + * Content for the model configuration dialog. + * This is extracted to be used both in Compose Dialog and DialogWrapper. + */ +@Composable +fun IdeaModelConfigDialogContent( + currentConfig: ModelConfig, + currentConfigName: String? = null, + onDismiss: () -> Unit, + onSave: (configName: String, config: ModelConfig) -> Unit ) { // Use TextFieldState for Jewel TextField val configNameState = rememberTextFieldState(currentConfigName ?: "") @@ -55,221 +79,230 @@ fun IdeaModelConfigDialog( var modelExpanded by remember { mutableStateOf(false) } var showAdvanced by remember { mutableStateOf(false) } - Dialog( - onDismissRequest = onDismiss, - properties = DialogProperties(usePlatformDefaultWidth = false) + Box( + modifier = Modifier + .width(500.dp) + .heightIn(max = 600.dp) + .clip(RoundedCornerShape(12.dp)) + .background(JewelTheme.globalColors.panelBackground) + .onKeyEvent { event -> + if (event.key == Key.Escape) { + onDismiss() + true + } else false + } ) { - Box( + Column( modifier = Modifier - .width(500.dp) - .heightIn(max = 600.dp) - .clip(RoundedCornerShape(12.dp)) - .background(JewelTheme.globalColors.panelBackground) - .onKeyEvent { event -> - if (event.key == Key.Escape) { - onDismiss() - true - } else false - } + .padding(24.dp) + .verticalScroll(rememberScrollState()) ) { - Column( - modifier = Modifier - .padding(24.dp) - .verticalScroll(rememberScrollState()) - ) { - // Title - Text( - text = "Model Configuration", - style = JewelTheme.defaultTextStyle.copy(fontSize = 18.sp) + // Title + Text( + text = "Model Configuration", + style = JewelTheme.defaultTextStyle.copy(fontSize = 18.sp) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Config Name + IdeaConfigFormField(label = "Config Name") { + TextField( + state = configNameState, + placeholder = { Text("e.g., my-gpt4") }, + modifier = Modifier.fillMaxWidth() ) + } - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(12.dp)) - // Config Name - IdeaConfigFormField(label = "Config Name") { - TextField( - state = configNameState, - placeholder = { Text("e.g., my-gpt4") }, - modifier = Modifier.fillMaxWidth() - ) - } + // Provider Selector + IdeaConfigFormField(label = "Provider") { + IdeaProviderSelector( + provider = provider, + expanded = providerExpanded, + onExpandedChange = { providerExpanded = it }, + onProviderSelect = { selectedProvider -> + provider = selectedProvider + val defaultModels = ModelRegistry.getAvailableModels(selectedProvider) + if (defaultModels.isNotEmpty()) { + modelNameState.edit { replace(0, length, defaultModels[0]) } + } + if (selectedProvider == LLMProviderType.OLLAMA && baseUrlState.text.isEmpty()) { + baseUrlState.edit { replace(0, length, "http://localhost:11434") } + } + providerExpanded = false + } + ) + } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - // Provider Selector - IdeaConfigFormField(label = "Provider") { - IdeaProviderSelector( - provider = provider, - expanded = providerExpanded, - onExpandedChange = { providerExpanded = it }, - onProviderSelect = { selectedProvider -> - provider = selectedProvider - val defaultModels = ModelRegistry.getAvailableModels(selectedProvider) - if (defaultModels.isNotEmpty()) { - modelNameState.edit { replace(0, length, defaultModels[0]) } - } - if (selectedProvider == LLMProviderType.OLLAMA && baseUrlState.text.isEmpty()) { - baseUrlState.edit { replace(0, length, "http://localhost:11434") } - } - providerExpanded = false + // Model Name + val availableModels = remember(provider) { ModelRegistry.getAvailableModels(provider) } + IdeaConfigFormField(label = "Model") { + if (availableModels.isNotEmpty()) { + IdeaModelNameSelector( + modelNameState = modelNameState, + availableModels = availableModels, + expanded = modelExpanded, + onExpandedChange = { modelExpanded = it }, + onModelSelect = { selectedModel -> + modelNameState.edit { replace(0, length, selectedModel) } + modelExpanded = false } ) + } else { + TextField( + state = modelNameState, + placeholder = { Text("Enter model name") }, + modifier = Modifier.fillMaxWidth() + ) } + } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - // Model Name - val availableModels = remember(provider) { ModelRegistry.getAvailableModels(provider) } - IdeaConfigFormField(label = "Model") { - if (availableModels.isNotEmpty()) { - IdeaModelNameSelector( - modelNameState = modelNameState, - availableModels = availableModels, - expanded = modelExpanded, - onExpandedChange = { modelExpanded = it }, - onModelSelect = { selectedModel -> - modelNameState.edit { replace(0, length, selectedModel) } - modelExpanded = false - } - ) - } else { - TextField( - state = modelNameState, - placeholder = { Text("Enter model name") }, - modifier = Modifier.fillMaxWidth() + // API Key + IdeaConfigFormField(label = "API Key") { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + TextField( + state = apiKeyState, + placeholder = { Text("Enter API key") }, + modifier = Modifier.weight(1f) + ) + IconButton( + onClick = { showApiKey = !showApiKey }, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = if (showApiKey) IdeaComposeIcons.VisibilityOff else IdeaComposeIcons.Visibility, + contentDescription = if (showApiKey) "Hide" else "Show", + tint = JewelTheme.globalColors.text.normal, + modifier = Modifier.size(18.dp) ) } } + } + // Base URL (for certain providers) + val needsBaseUrl = provider in listOf( + LLMProviderType.OLLAMA, LLMProviderType.GLM, LLMProviderType.QWEN, + LLMProviderType.KIMI, LLMProviderType.CUSTOM_OPENAI_BASE + ) + if (needsBaseUrl) { Spacer(modifier = Modifier.height(12.dp)) - - // API Key - IdeaConfigFormField(label = "API Key") { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - TextField( - state = apiKeyState, - placeholder = { Text("Enter API key") }, - modifier = Modifier.weight(1f) - ) - IconButton( - onClick = { showApiKey = !showApiKey }, - modifier = Modifier.size(32.dp) - ) { - Icon( - imageVector = if (showApiKey) IdeaComposeIcons.VisibilityOff else IdeaComposeIcons.Visibility, - contentDescription = if (showApiKey) "Hide" else "Show", - tint = JewelTheme.globalColors.text.normal, - modifier = Modifier.size(18.dp) - ) - } - } + IdeaConfigFormField(label = "Base URL") { + TextField( + state = baseUrlState, + placeholder = { Text("e.g., http://localhost:11434") }, + modifier = Modifier.fillMaxWidth() + ) } + } + + Spacer(modifier = Modifier.height(16.dp)) - // Base URL (for certain providers) - val needsBaseUrl = provider in listOf( - LLMProviderType.OLLAMA, LLMProviderType.GLM, LLMProviderType.QWEN, - LLMProviderType.KIMI, LLMProviderType.CUSTOM_OPENAI_BASE + // Advanced Settings Toggle + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { showAdvanced = !showAdvanced } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = if (showAdvanced) IdeaComposeIcons.ExpandLess else IdeaComposeIcons.ExpandMore, + contentDescription = null, + tint = JewelTheme.globalColors.text.normal, + modifier = Modifier.size(20.dp) ) - if (needsBaseUrl) { - Spacer(modifier = Modifier.height(12.dp)) - IdeaConfigFormField(label = "Base URL") { - TextField( - state = baseUrlState, - placeholder = { Text("e.g., http://localhost:11434") }, - modifier = Modifier.fillMaxWidth() - ) - } - } + Text( + text = "Advanced Settings", + style = JewelTheme.defaultTextStyle.copy(fontSize = 14.sp) + ) + } - Spacer(modifier = Modifier.height(16.dp)) + if (showAdvanced) { + Spacer(modifier = Modifier.height(8.dp)) - // Advanced Settings Toggle - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { showAdvanced = !showAdvanced } - .padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon( - imageVector = if (showAdvanced) IdeaComposeIcons.ExpandLess else IdeaComposeIcons.ExpandMore, - contentDescription = null, - tint = JewelTheme.globalColors.text.normal, - modifier = Modifier.size(20.dp) - ) - Text( - text = "Advanced Settings", - style = JewelTheme.defaultTextStyle.copy(fontSize = 14.sp) + // Temperature + IdeaConfigFormField(label = "Temperature") { + TextField( + state = temperatureState, + placeholder = { Text("0.0 - 2.0") }, + modifier = Modifier.fillMaxWidth() ) } - if (showAdvanced) { - Spacer(modifier = Modifier.height(8.dp)) - - // Temperature - IdeaConfigFormField(label = "Temperature") { - TextField( - state = temperatureState, - placeholder = { Text("0.0 - 2.0") }, - modifier = Modifier.fillMaxWidth() - ) - } - - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - // Max Tokens - IdeaConfigFormField(label = "Max Tokens") { - TextField( - state = maxTokensState, - placeholder = { Text("e.g., 128000") }, - modifier = Modifier.fillMaxWidth() - ) - } + // Max Tokens + IdeaConfigFormField(label = "Max Tokens") { + TextField( + state = maxTokensState, + placeholder = { Text("e.g., 128000") }, + modifier = Modifier.fillMaxWidth() + ) } + } - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(24.dp)) - // Action Buttons - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically + // Action Buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedButton(onClick = onDismiss) { + Text("Cancel") + } + Spacer(modifier = Modifier.width(12.dp)) + DefaultButton( + onClick = { + val modelName = modelNameState.text.toString() + val apiKey = apiKeyState.text.toString() + val config = ModelConfig( + provider = provider, + modelName = modelName, + apiKey = apiKey, + temperature = temperatureState.text.toString().toDoubleOrNull() ?: 0.0, + maxTokens = maxTokensState.text.toString().toIntOrNull() ?: 128000, + baseUrl = baseUrlState.text.toString() + ) + val configName = configNameState.text.toString() + val name = configName.ifEmpty { "${provider.name.lowercase()}-${modelName}" } + onSave(name, config) + }, + enabled = modelNameState.text.isNotEmpty() && apiKeyState.text.isNotEmpty() ) { - OutlinedButton(onClick = onDismiss) { - Text("Cancel") - } - Spacer(modifier = Modifier.width(12.dp)) - DefaultButton( - onClick = { - val modelName = modelNameState.text.toString() - val apiKey = apiKeyState.text.toString() - val config = ModelConfig( - provider = provider, - modelName = modelName, - apiKey = apiKey, - temperature = temperatureState.text.toString().toDoubleOrNull() ?: 0.0, - maxTokens = maxTokensState.text.toString().toIntOrNull() ?: 128000, - baseUrl = baseUrlState.text.toString() - ) - val configName = configNameState.text.toString() - val name = configName.ifEmpty { "${provider.name.lowercase()}-${modelName}" } - onSave(name, config) - }, - enabled = modelNameState.text.isNotEmpty() && apiKeyState.text.isNotEmpty() - ) { - Text("Save") - } + Text("Save") } } } } } +/** + * Deprecated: Use IdeaModelConfigDialogWrapper.show() instead. + */ +@Deprecated("Use IdeaModelConfigDialogWrapper.show() for proper z-index handling") +@Composable +fun IdeaModelConfigDialogLegacy( + currentConfig: ModelConfig, + currentConfigName: String? = null, + onDismiss: () -> Unit, + onSave: (configName: String, config: ModelConfig) -> Unit +) { + IdeaModelConfigDialog(currentConfig, currentConfigName, onDismiss, onSave) +} + /** * Form field wrapper with label */ @@ -289,7 +322,7 @@ private fun IdeaConfigFormField( } /** - * Provider selector dropdown + * Provider selector dropdown using Jewel's PopupMenu for proper z-index handling */ @Composable private fun IdeaProviderSelector( @@ -319,43 +352,23 @@ private fun IdeaProviderSelector( } if (expanded) { - Popup( - onDismissRequest = { onExpandedChange(false) }, - properties = PopupProperties(focusable = true) + PopupMenu( + onDismissRequest = { + onExpandedChange(false) + true + }, + horizontalAlignment = Alignment.Start, + modifier = Modifier.widthIn(min = 200.dp, max = 300.dp) ) { - Box( - modifier = Modifier - .widthIn(min = 200.dp, max = 300.dp) - .heightIn(max = 300.dp) - .clip(RoundedCornerShape(8.dp)) - .background(JewelTheme.globalColors.panelBackground) - .padding(4.dp) - ) { - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - LLMProviderType.entries.forEach { providerType -> - Row( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(4.dp)) - .clickable { onProviderSelect(providerType) } - .padding(horizontal = 12.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = providerType.name, - style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp), - modifier = Modifier.weight(1f) - ) - if (providerType == provider) { - Icon( - imageVector = IdeaComposeIcons.Check, - contentDescription = "Selected", - tint = JewelTheme.globalColors.text.normal, - modifier = Modifier.size(16.dp) - ) - } - } - } + LLMProviderType.entries.forEach { providerType -> + selectableItem( + selected = providerType == provider, + onClick = { onProviderSelect(providerType) } + ) { + Text( + text = providerType.name, + style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp) + ) } } } @@ -364,7 +377,7 @@ private fun IdeaProviderSelector( } /** - * Model name selector with dropdown for known models + * Model name selector with dropdown for known models using Jewel's PopupMenu */ @Composable private fun IdeaModelNameSelector( @@ -401,47 +414,26 @@ private fun IdeaModelNameSelector( } if (expanded) { - Popup( - onDismissRequest = { onExpandedChange(false) }, - properties = PopupProperties(focusable = true) + PopupMenu( + onDismissRequest = { + onExpandedChange(false) + true + }, + horizontalAlignment = Alignment.Start, + modifier = Modifier.widthIn(min = 200.dp, max = 400.dp) ) { - Box( - modifier = Modifier - .widthIn(min = 200.dp, max = 400.dp) - .heightIn(max = 250.dp) - .clip(RoundedCornerShape(8.dp)) - .background(JewelTheme.globalColors.panelBackground) - .padding(4.dp) - ) { - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - availableModels.forEach { model -> - Row( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(4.dp)) - .clickable { onModelSelect(model) } - .padding(horizontal = 12.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = model, - style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp), - modifier = Modifier.weight(1f) - ) - if (model == modelName) { - Icon( - imageVector = IdeaComposeIcons.Check, - contentDescription = "Selected", - tint = JewelTheme.globalColors.text.normal, - modifier = Modifier.size(16.dp) - ) - } - } - } + availableModels.forEach { model -> + selectableItem( + selected = model == modelName, + onClick = { onModelSelect(model) } + ) { + Text( + text = model, + style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp) + ) } } } } } } - diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelConfigDialogWrapper.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelConfigDialogWrapper.kt new file mode 100644 index 0000000000..e6d635342c --- /dev/null +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelConfigDialogWrapper.kt @@ -0,0 +1,68 @@ +package cc.unitmesh.devins.idea.editor + +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.util.ui.JBUI +import cc.unitmesh.llm.ModelConfig +import org.jetbrains.jewel.bridge.compose +import java.awt.Dimension +import javax.swing.JComponent + +/** + * DialogWrapper for model configuration that uses IntelliJ's native dialog system. + * This ensures proper z-index handling when used alongside SwingPanel components. + */ +class IdeaModelConfigDialogWrapper( + private val project: Project?, + private val currentConfig: ModelConfig, + private val currentConfigName: String? = null, + private val onSaveCallback: (configName: String, config: ModelConfig) -> Unit +) : DialogWrapper(project) { + + init { + title = "Model Configuration" + init() + contentPanel.border = JBUI.Borders.empty() + rootPane.border = JBUI.Borders.empty() + } + + override fun createSouthPanel(): JComponent? = null + + override fun createCenterPanel(): JComponent { + val dialogPanel = compose { + IdeaModelConfigDialogContent( + currentConfig = currentConfig, + currentConfigName = currentConfigName, + onDismiss = { close(CANCEL_EXIT_CODE) }, + onSave = { configName, config -> + onSaveCallback(configName, config) + close(OK_EXIT_CODE) + } + ) + } + dialogPanel.preferredSize = Dimension(500, 500) + return dialogPanel + } + + companion object { + /** + * Show the model configuration dialog. + * @return true if the dialog was closed with OK, false otherwise + */ + fun show( + project: Project?, + currentConfig: ModelConfig, + currentConfigName: String? = null, + onSave: (configName: String, config: ModelConfig) -> Unit + ): Boolean { + val dialog = IdeaModelConfigDialogWrapper( + project = project, + currentConfig = currentConfig, + currentConfigName = currentConfigName, + onSaveCallback = onSave + ) + return dialog.showAndGet() + } + } +} + diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaPromptOptimizationDialog.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaPromptOptimizationDialog.kt deleted file mode 100644 index c5f6f77723..0000000000 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaPromptOptimizationDialog.kt +++ /dev/null @@ -1,186 +0,0 @@ -package cc.unitmesh.devins.idea.editor - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.input.rememberTextFieldState -import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import cc.unitmesh.llm.KoogLLMService -import cc.unitmesh.llm.PromptEnhancer -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.launch -import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.ui.component.* - -/** - * Prompt Optimization Dialog for IntelliJ IDEA. - * - * Features: - * - Display original and enhanced prompts side by side - * - Real-time enhancement using PromptEnhancer - * - Apply or cancel the enhancement - * - * Migrated from mpp-ui/DevInEditorInput.kt prompt enhancement functionality. - */ -@Composable -fun IdeaPromptOptimizationDialog( - originalText: String, - enhancer: PromptEnhancer?, - onApply: (String) -> Unit, - onDismiss: () -> Unit -) { - var enhancedText by remember { mutableStateOf("") } - var isEnhancing by remember { mutableStateOf(false) } - var errorMessage by remember { mutableStateOf(null) } - - val scope = rememberCoroutineScope() - - // Auto-enhance on dialog open - LaunchedEffect(Unit) { - if (enhancer != null && originalText.isNotBlank()) { - isEnhancing = true - errorMessage = null - try { - val enhanced = enhancer.enhance(originalText.trim(), "zh") - if (enhanced.isNotEmpty() && enhanced != originalText.trim()) { - enhancedText = enhanced - } else { - enhancedText = originalText - errorMessage = "No enhancement needed or enhancement failed" - } - } catch (e: Exception) { - errorMessage = "Enhancement failed: ${e.message}" - enhancedText = originalText - } finally { - isEnhancing = false - } - } else { - enhancedText = originalText - if (enhancer == null) { - errorMessage = "Enhancer not available. Please configure LLM settings." - } - } - } - - Dialog(onDismissRequest = onDismiss) { - Column( - modifier = Modifier - .width(700.dp) - .height(500.dp) - .padding(16.dp) - ) { - // Header - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text("Prompt Optimization (Ctrl+P)") - IconButton(onClick = onDismiss) { - Text("×") - } - } - - Spacer(modifier = Modifier.height(12.dp)) - - // Error message - errorMessage?.let { error -> - Text(error, color = JewelTheme.globalColors.text.error) - Spacer(modifier = Modifier.height(8.dp)) - } - - // Content area - Row( - modifier = Modifier.weight(1f).fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - // Original text - Column(modifier = Modifier.weight(1f)) { - Text("Original") - Spacer(modifier = Modifier.height(4.dp)) - BasicTextField( - value = originalText, - onValueChange = {}, - readOnly = true, - modifier = Modifier.fillMaxSize(), - textStyle = TextStyle( - fontFamily = FontFamily.Monospace, - color = JewelTheme.globalColors.text.normal - ), - cursorBrush = SolidColor(JewelTheme.globalColors.text.normal) - ) - } - - // Enhanced text - Column(modifier = Modifier.weight(1f)) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text("Enhanced") - if (isEnhancing) { - Text("(Enhancing...)", color = JewelTheme.globalColors.text.info) - } - } - Spacer(modifier = Modifier.height(4.dp)) - - val enhancedTextState = rememberTextFieldState(enhancedText) - - LaunchedEffect(enhancedText) { - if (enhancedTextState.text.toString() != enhancedText) { - enhancedTextState.setTextAndPlaceCursorAtEnd(enhancedText) - } - } - - LaunchedEffect(Unit) { - snapshotFlow { enhancedTextState.text.toString() } - .distinctUntilChanged() - .collect { newText -> - if (newText != enhancedText) { - enhancedText = newText - } - } - } - - BasicTextField( - state = enhancedTextState, - modifier = Modifier.fillMaxSize(), - textStyle = TextStyle( - fontFamily = FontFamily.Monospace, - color = JewelTheme.globalColors.text.normal - ), - cursorBrush = SolidColor(JewelTheme.globalColors.text.normal) - ) - } - } - - Spacer(modifier = Modifier.height(12.dp)) - - // Footer buttons - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically - ) { - OutlinedButton(onClick = onDismiss) { - Text("Cancel") - } - Spacer(modifier = Modifier.width(8.dp)) - DefaultButton( - onClick = { onApply(enhancedText) }, - enabled = !isEnhancing && enhancedText.isNotBlank() - ) { - Text("Apply") - } - } - } - } -} - diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt index c782454366..656fe58752 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt @@ -12,7 +12,7 @@ import cc.unitmesh.devins.idea.editor.IdeaBottomToolbar import cc.unitmesh.devins.idea.editor.IdeaDevInInput import cc.unitmesh.devins.idea.editor.IdeaInputListener import cc.unitmesh.devins.idea.editor.IdeaInputTrigger -import cc.unitmesh.devins.idea.editor.IdeaModelConfigDialog +import cc.unitmesh.devins.idea.editor.IdeaModelConfigDialogWrapper import cc.unitmesh.devins.idea.toolwindow.codereview.IdeaCodeReviewContent import cc.unitmesh.devins.idea.toolwindow.codereview.IdeaCodeReviewViewModel import cc.unitmesh.devins.idea.components.header.IdeaAgentTabsHeader @@ -259,39 +259,41 @@ fun IdeaAgentApp( ) } - // Model Configuration Dialog - if (showConfigDialog) { - val dialogConfig = currentModelConfig ?: ModelConfig() - IdeaModelConfigDialog( - currentConfig = dialogConfig, - currentConfigName = currentConfigName, - onDismiss = { viewModel.setShowConfigDialog(false) }, - onSave = { configName, newModelConfig -> - // If creating a new config (not editing current), ensure unique name - val existingNames = availableConfigs.map { it.name } - val finalConfigName = - if (currentConfigName != configName && configName in existingNames) { - // Auto-increment: my-glm -> my-glm-1 -> my-glm-2, etc. - ConfigManager.generateUniqueConfigName(configName, existingNames) - } else { - configName - } + // Model Configuration Dialog using DialogWrapper for proper z-index handling + LaunchedEffect(showConfigDialog) { + if (showConfigDialog) { + val dialogConfig = currentModelConfig ?: ModelConfig() + IdeaModelConfigDialogWrapper.show( + project = project, + currentConfig = dialogConfig, + currentConfigName = currentConfigName, + onSave = { configName, newModelConfig -> + // If creating a new config (not editing current), ensure unique name + val existingNames = availableConfigs.map { it.name } + val finalConfigName = + if (currentConfigName != configName && configName in existingNames) { + // Auto-increment: my-glm -> my-glm-1 -> my-glm-2, etc. + ConfigManager.generateUniqueConfigName(configName, existingNames) + } else { + configName + } - // Convert ModelConfig to NamedModelConfig - val namedConfig = NamedModelConfig.fromModelConfig( - name = finalConfigName, - config = newModelConfig - ) + // Convert ModelConfig to NamedModelConfig + val namedConfig = NamedModelConfig.fromModelConfig( + name = finalConfigName, + config = newModelConfig + ) - // Save to file - viewModel.saveModelConfig(namedConfig, setActive = true) - viewModel.setShowConfigDialog(false) + // Save to file + viewModel.saveModelConfig(namedConfig, setActive = true) - if (finalConfigName != configName) { - println("✅ 配置名称已存在,自动重命名为: $finalConfigName") + if (finalConfigName != configName) { + println("✅ 配置名称已存在,自动重命名为: $finalConfigName") + } } - } - ) + ) + viewModel.setShowConfigDialog(false) + } } } From 205a12ab0855b989f359e1abac059b9decd3fa78 Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Mon, 1 Dec 2025 23:10:43 +0800 Subject: [PATCH 5/8] docs(agents): update AGENTS.md documentation Refine and update agent documentation for clarity and accuracy. --- AGENTS.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index ec0054da51..341d469459 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,6 +64,35 @@ cd mpp-idea && ../gradlew buildPlugin - `IdeaAgentViewModelTest` requires IntelliJ Platform Test Framework - `JewelRendererTest` can run standalone with JUnit 5 +**Swing/Compose Z-Index Issues (SwingPanel blocking Compose popups):** + +When using `SwingPanel` to embed Swing components (e.g., `EditorTextField`) in Compose, Swing components render on top of Compose popups, causing z-index issues. + +**Solution 1: For Popup/Dropdown menus** +1. Enable Jewel's custom popup renderer in `IdeaAgentToolWindowFactory`: + ```kotlin + JewelFlags.useCustomPopupRenderer = true + ``` +2. Use Jewel's `PopupMenu` instead of `androidx.compose.ui.window.Popup`: + ```kotlin + PopupMenu( + onDismissRequest = { expanded = false; true }, + horizontalAlignment = Alignment.Start + ) { + selectableItem(selected = ..., onClick = { ... }) { Text("Item") } + } + ``` + +**Solution 2: For Dialogs** +Use IntelliJ's `DialogWrapper` with `org.jetbrains.jewel.bridge.compose` instead of `androidx.compose.ui.window.Dialog`: +```kotlin +class MyDialogWrapper(project: Project?) : DialogWrapper(project) { + override fun createCenterPanel(): JComponent = compose { + // Compose content here + } +} +``` + ## Release 1. modify version in `gradle.properties` From 6f491e2a2996b6dca7bb7c4f89ceb41b205a9d89 Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Mon, 1 Dec 2025 23:16:16 +0800 Subject: [PATCH 6/8] feat(agent): rewrite CodingAgentTemplate prompts for clarity Revamp English and Chinese system prompts for CodingAgentTemplate with improved structure, clearer guidelines, and more concise instructions, inspired by Augment Agent's design. This enhances usability and agent behavior consistency. --- .../cc/unitmesh/agent/CodingAgentTemplate.kt | 363 +++++++----------- 1 file changed, 144 insertions(+), 219 deletions(-) diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt index 858aeac88f..bbf3b33593 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt @@ -2,16 +2,17 @@ package cc.unitmesh.agent /** * Template for Coding Agent system prompt - * Similar to sketch.vm in JetBrains plugin + * Inspired by Augment Agent's prompt design */ object CodingAgentTemplate { /** * English version of the coding agent system prompt + * Based on Augment Agent's prompt structure */ - const val EN = """You are AutoDev, an autonomous AI coding agent designed to complete development tasks. + const val EN = """You are AutoDev, an autonomous AI coding agent with access to the developer's codebase through powerful tools and integrations. -## Environment Information +# Environment - OS: ${'$'}{osInfo} - Project Path: ${'$'}{projectPath} - Current Time: ${'$'}{timestamp} @@ -19,155 +20,112 @@ object CodingAgentTemplate { - Build Tool: ${'$'}{buildTool} - Shell: ${'$'}{shell} -## Available Tools -You have access to the following tools through DevIns commands. Each tool uses JSON Schema for parameter validation: +# Available Tools + +You have access to the following tools through DevIns commands: ${'$'}{toolList} ## Tool Usage Format All tools use the DevIns format with JSON parameters: -``` + /tool-name ```json -{"parameter": "value", "optional_param": 123} +{"parameter": "value"} ``` -``` - -Each tool's parameters are validated against its JSON Schema. Refer to the schema for required fields, types, and constraints. - -## Task Execution Guidelines - -1. **Gather Context First**: Before making changes understand the codebase -2. **Plan Your Approach**: Think step-by-step about what needs to be done -3. **Make Incremental Changes**: Make one change at a time and verify it works -4. **Test Your Changes**: Run tests or build commands to verify changes -5. **Handle Errors Gracefully**: When a tool fails, analyze the error and try alternative approaches + -## Smart File Search Guidelines +# Preliminary Tasks -When searching for files, use **specific and targeted patterns** to avoid overwhelming context: +Before starting to execute a task, make sure you have a clear understanding of the task and the codebase. +Use `/read-file` and `/grep` to gather the necessary information. -**DO:** -- ✅ Use specific patterns: `src/**/*.kt`, `**/test/**/*.java`, `**/config/*.yml` -- ✅ Target specific directories: `/glob pattern="*.ts" path="src/main"` -- ✅ Use grep with specific patterns to narrow down first -- ✅ For broad exploration, use `/ask-agent` to get a summary instead +# Information-Gathering Strategy -**DON'T:** -- ❌ Avoid `**/*` or overly broad patterns (returns too many files, wastes context) -- ❌ Don't glob the entire codebase without a specific goal +Make sure to use the appropriate tool depending on the type of information you need: -**Smart Strategy:** -1. If you need to understand the project structure, use grep for specific keywords first -2. Use targeted glob patterns based on what you found -3. For very large result sets (100+ files), the system will automatically invoke a SummaryAgent to provide a concise overview +## When to use `/grep` +- When you want to find specific text patterns in files +- When you want to find all references of a specific symbol +- When you want to find usages or definitions of a symbol -## Agent Communication & Collaboration +## When to use `/glob` +- When you need to find files matching a pattern +- When you want to explore directory structure +- Use **specific patterns** like `src/**/*.kt`, `**/test/**/*.java` +- **Avoid** overly broad patterns like `**/*` -When dealing with complex information or large content, you can **communicate with specialized SubAgents** to get focused analysis: +## When to use `/read-file` +- When you need to read a specific file's content +- When you have specific lines of code in mind -**Available SubAgents:** -- `analysis-agent`: Analyzes and summarizes any content (logs, file lists, code, data) -- `error-agent`: Analyzes errors and provides recovery suggestions -- `code-agent`: Deep codebase investigation and architectural analysis +# Making Edits -**When to Use `/ask-agent`:** -1. **After automatic summarization**: When a tool (like glob) triggers auto-summarization, you can ask follow-up questions - ``` - /ask-agent - ```json - {"agentName": "analysis-agent", "question": "What are the main patterns in the file structure you analyzed?"} - ``` - ``` +When making changes, be very conservative and respect the codebase. -2. **For specific insights**: Ask targeted questions about previously analyzed content - ``` - /ask-agent - ```json - {"agentName": "analysis-agent", "question": "Which files are most likely related to authentication?"} - ``` - ``` +## Before Editing +- **ALWAYS** read the file or section you want to modify first +- Confirm existence and signatures of any classes/functions you are going to use +- Do an exhaustive search before planning or making edits -3. **To avoid re-reading large content**: If you need different perspectives on the same data - ``` - /ask-agent - ```json - {"agentName": "analysis-agent", "question": "Can you identify the main dependencies in the files you saw?"} - ``` - ``` +## Edit Guidelines +- Use `/edit-file` for modifying existing files - do NOT just write a new file +- Use `/write-file` only for creating new files +- Add all necessary import statements and dependencies +- **NEVER generate extremely long hashes or non-textual code (like binary)** +- When refactoring, create the new code first, then update the old references -**Example Workflow:** -1. `/glob pattern="**/*.kt"` → Auto-triggers AnalysisAgent (returns summary) -2. Review the summary, then ask: `/ask-agent` to get specific insights -3. Based on insights, use targeted `/read-file` or `/grep` commands +# Following Instructions -This approach keeps your context efficient while getting deep insights from specialized agents! +Focus on doing what the user asks you to do. +- Do NOT do more than the user asked +- If you think there is a clear follow-up task, ASK the user +- The more potentially damaging the action, the more conservative you should be -## Task Progress Communication +# Testing -For complex multi-step tasks (5+ steps), use `/task-boundary` to help users understand your progress: +You are very good at writing unit tests and making them work. +- If you write code, suggest testing the code by writing tests and running them +- Before running tests, make sure you know how tests should be run +- Work diligently on iterating on tests until they pass -**When to use:** -- At the start of a complex task: Set status to PLANNING and describe what you're about to do -- When switching major phases: Update to WORKING when you start implementation -- At completion: Mark as COMPLETED with a summary of what was done -- If blocked: Mark as BLOCKED and explain why - -**Example for a complex task:** - -/task-boundary -```json -{"taskName": "Implement User Authentication System", "status": "PLANNING", "summary": "Analyzing requirements and existing code structure"} -``` - - -Then after several implementation steps: - -/task-boundary -```json -{"taskName": "Implement User Authentication System", "status": "WORKING", "summary": "Creating User entity, JWT service, and authentication endpoints"} -``` - - -**Keep it concise** - one update per major phase is enough. Focus on high-level progress, not individual tool calls. - -## Error Handling Guidelines +# Error Handling When a tool execution fails: - -1. **Read the Error Message Carefully**: Look for specific error patterns, file paths, and error codes -2. **Analyze the Context**: Consider what you were trying to do and what might have gone wrong -3. **Use Error Recovery**: The system will automatically provide error analysis and recovery suggestions -4. **Try Alternative Approaches**: If one method fails, consider different tools or approaches -5. **Check Prerequisites**: Ensure required files, dependencies, or permissions exist -6. **Verify Paths and Parameters**: Double-check file paths, parameter values, and syntax - -Common error scenarios and solutions: -- **File not found**: Use /glob to verify the file exists and check the correct path -- **Permission denied**: Check file permissions or try alternative locations -- **Build failures**: Read build logs carefully, check dependencies and configuration files +1. **Read the Error Message Carefully**: Look for specific error patterns and codes +2. **Analyze the Context**: Consider what might have gone wrong +3. **Try Alternative Approaches**: If one method fails, consider different tools +4. **Check Prerequisites**: Ensure required files and dependencies exist +5. **Verify Paths and Parameters**: Double-check file paths and syntax + +Common error scenarios: +- **File not found**: Use `/glob` to verify the file exists +- **Build failures**: Read build logs carefully, check dependencies - **Syntax errors**: Review recent changes and validate code syntax -- **Tool not available**: Verify the tool is installed or use alternative tools -## IMPORTANT: One Tool Per Response +# Recovering from Difficulties + +If you notice yourself going around in circles, or going down a rabbit hole (e.g., calling the same tool in similar ways multiple times), stop and reconsider your approach. + +# IMPORTANT: One Tool Per Response -**You MUST execute ONLY ONE tool per response.** Do not include multiple tool calls in a single response. +**You MUST execute ONLY ONE tool per response.** - ✅ CORRECT: One block with ONE tool call - ❌ WRONG: Multiple blocks or multiple tools in one block After each tool execution, you will see the result and can decide the next step. -## Response Format +# Response Format For each step, respond with: -1. Your reasoning about what to do next (explain your thinking) +1. Your reasoning about what to do next (brief explanation) 2. **EXACTLY ONE** DevIns command (wrapped in tags) 3. What you expect to happen Example: -I need to check the existing implementation first to understand the current code structure. +I need to check the existing implementation first. /read-file ```json @@ -176,19 +134,8 @@ I need to check the existing implementation first to understand the current code I expect to see the main entry point of the application. -## Making Code Changes - -When modifying code: -- **DO NOT output code to the user unless explicitly requested**. Use code editing tools instead. -- Before editing, **read the file or section you want to modify** (unless it's a simple append or new file). -- Add all necessary import statements, dependencies, and endpoints required to run the code. -- If creating a codebase from scratch, provide a dependency management file (e.g., `requirements.txt`) with package versions and a helpful README. -- If building a web app from scratch, design a **modern, beautiful UI with best UX practices**. -- **NEVER generate extremely long hashes or non-textual code (like binary)**. These are unhelpful and expensive. -- When refactoring code, create the new code first, then update the old references. - #if (${'$'}{agentRules}) -## Project-Specific Rules +# Project-Specific Rules ${'$'}{agentRules} #end @@ -197,10 +144,11 @@ Remember: You are autonomous. Keep working until the task is complete or you enc /** * Chinese version of the coding agent system prompt + * Based on Augment Agent's prompt structure */ - const val ZH = """You are AutoDev, 一个由 Unit Mesh 设计的开源自主 AI 编程代理。 + const val ZH = """你是 AutoDev,一个自主 AI 编程代理,可以通过强大的工具和集成访问开发者的代码库。 -## 环境信息 +# 环境 - OS: ${'$'}{osInfo} - 项目路径: ${'$'}{projectPath} - 当前时间: ${'$'}{timestamp} @@ -208,145 +156,122 @@ Remember: You are autonomous. Keep working until the task is complete or you enc - 构建工具: ${'$'}{buildTool} - Shell: ${'$'}{shell} -## 项目结构 -${'$'}{projectStructure} +# 可用工具 -## 可用工具 你可以通过 DevIns 命令访问以下工具: ${'$'}{toolList} -## 任务执行指南 +## 工具使用格式 -1. **先获取上下文**: 在进行更改之前,先来了解代码库 -2. **规划你的方法**: 逐步思考需要做什么 -3. **增量更改**: 一次做一个更改并验证其有效性 -4. **测试更改**: 运行测试或构建命令来验证更改 +所有工具都使用 DevIns 格式和 JSON 参数: + +/tool-name +```json +{"parameter": "value"} +``` + -## 智能文件搜索指南 +# 前置任务 -搜索文件时,使用**具体且有针对性的模式**以避免上下文超载: +在开始执行任务之前,确保你对任务和代码库有清晰的理解。 +使用 `/read-file` 和 `/grep` 来收集必要的信息。 -**应该做:** -- ✅ 使用具体的模式:`src/**/*.kt`、`**/test/**/*.java`、`**/config/*.yml` -- ✅ 针对特定目录:`/glob pattern="*.ts" path="src/main"` -- ✅ 先使用 grep 配合具体模式来缩小范围 -- ✅ 对于广泛探索,使用 `/ask-agent` 获取摘要 +# 信息收集策略 -**不应该做:** -- ❌ 避免 `**/*` 或过于宽泛的模式(返回太多文件,浪费上下文) -- ❌ 不要在没有明确目标的情况下 glob 整个代码库 +根据你需要的信息类型使用适当的工具: -**智能策略:** -1. 如果需要了解项目结构,先使用 grep 搜索特定关键词 -2. 根据发现的内容使用有针对性的 glob 模式 -3. 对于非常大的结果集(100+ 文件),系统会自动调用 SummaryAgent 提供简洁概述 +## 何时使用 `/grep` +- 当你想在文件中查找特定文本模式时 +- 当你想查找某个符号的所有引用时 +- 当你想查找符号的用法或定义时 -## Agent 通信与协作 +## 何时使用 `/glob` +- 当你需要查找匹配模式的文件时 +- 当你想探索目录结构时 +- 使用**具体的模式**如 `src/**/*.kt`、`**/test/**/*.java` +- **避免**过于宽泛的模式如 `**/*` -处理复杂信息或大量内容时,你可以**与专业的 SubAgent 通信**来获取专注的分析: +## 何时使用 `/read-file` +- 当你需要读取特定文件的内容时 +- 当你有特定的代码行需要查看时 -**可用的 SubAgent:** -- `analysis-agent`: 分析和总结任何内容(日志、文件列表、代码、数据) -- `error-agent`: 分析错误并提供恢复建议 -- `code-agent`: 深度代码库调查和架构分析 +# 进行编辑 -**何时使用 `/ask-agent`:** -1. **自动总结之后**: 当工具(如 glob)触发自动总结后,你可以询问后续问题 - ``` - /ask-agent - ```json - {"agentName": "analysis-agent", "question": "你分析的文件结构中有哪些主要模式?"} - ``` - ``` +进行更改时,要保守并尊重代码库。 -2. **获取特定见解**: 就之前分析的内容提出针对性问题 - ``` - /ask-agent - ```json - {"agentName": "analysis-agent", "question": "哪些文件最可能与身份验证相关?"} - ``` - ``` +## 编辑前 +- **始终**先读取你要修改的文件或部分 +- 确认你要使用的任何类/函数的存在和签名 +- 在规划或进行编辑之前进行详尽的搜索 -3. **避免重复读取大内容**: 需要从不同角度看待相同数据时 - ``` - /ask-agent - ```json - {"agentName": "analysis-agent", "question": "你能识别出文件中的主要依赖关系吗?"} - ``` - ``` +## 编辑指南 +- 使用 `/edit-file` 修改现有文件 - 不要直接写新文件 +- 仅使用 `/write-file` 创建新文件 +- 添加所有必要的导入语句和依赖项 +- **绝不生成极长的哈希值或非文本代码(如二进制)** +- 重构时,先创建新代码,然后更新旧引用 -**示例工作流:** -1. `/glob pattern="**/*.kt"` → 自动触发 AnalysisAgent(返回摘要) -2. 查看摘要,然后询问:`/ask-agent` 获取特定见解 -3. 基于见解,使用有针对性的 `/read-file` 或 `/grep` 命令 +# 遵循指令 -这种方法既保持上下文高效,又能从专业 Agent 获得深度见解! +专注于做用户要求你做的事情。 +- 不要做超出用户要求的事情 +- 如果你认为有明确的后续任务,询问用户 +- 行动越有潜在破坏性,你就应该越保守 -## 任务进度沟通 +# 测试 -对于复杂的多步骤任务(5+ 步骤),使用 `/task-boundary` 帮助用户了解你的进度: +你非常擅长编写单元测试并使其通过。 +- 如果你编写了代码,建议通过编写测试并运行它们来测试代码 +- 在运行测试之前,确保你知道测试应该如何运行 +- 努力迭代测试直到它们通过 -**何时使用:** -- 复杂任务开始时:将状态设置为 PLANNING 并描述你要做什么 -- 切换主要阶段时:开始实施时更新为 WORKING -- 完成时:标记为 COMPLETED 并总结完成的内容 -- 如果被阻塞:标记为 BLOCKED 并解释原因 +# 错误处理 -**复杂任务示例:** - -/task-boundary -```json -{"taskName": "实现用户认证系统", "status": "PLANNING", "summary": "分析需求和现有代码结构"} -``` - +当工具执行失败时: +1. **仔细阅读错误消息**:查找特定的错误模式和代码 +2. **分析上下文**:考虑可能出了什么问题 +3. **尝试替代方法**:如果一种方法失败,考虑不同的工具 +4. **检查前提条件**:确保所需的文件和依赖项存在 +5. **验证路径和参数**:仔细检查文件路径和语法 -然后在几个实施步骤后: - -/task-boundary -```json -{"taskName": "实现用户认证系统", "status": "WORKING", "summary": "创建 User 实体、JWT 服务和认证端点"} -``` - +常见错误场景: +- **文件未找到**:使用 `/glob` 验证文件是否存在 +- **构建失败**:仔细阅读构建日志,检查依赖项 +- **语法错误**:检查最近的更改并验证代码语法 + +# 从困难中恢复 -**保持简洁** - 每个主要阶段更新一次就够了。关注高层进度,而不是单个工具调用。 +如果你发现自己在兜圈子,或者陷入了死胡同(例如,多次以类似方式调用同一工具),停下来重新考虑你的方法。 -## 重要:每次响应只执行一个工具 +# 重要:每次响应只执行一个工具 -**你必须每次响应只执行一个工具。** 不要在单个响应中包含多个工具调用。 +**你必须每次响应只执行一个工具。** - ✅ 正确:一个 块包含一个工具调用 - ❌ 错误:多个 块或一个块中有多个工具 每次工具执行后,你会看到结果,然后可以决定下一步。 -## 响应格式 +# 响应格式 对于每一步,请回复: -1. 你对下一步该做什么的推理(解释你的思考) +1. 你对下一步该做什么的推理(简短解释) 2. **恰好一个** DevIns 命令(包装在 标签中) 3. 你期望发生什么 示例: -我需要先检查现有实现以了解当前的代码结构。 +我需要先检查现有实现。 -/read-file path="src/main.ts" +/read-file +```json +{"path": "src/main.ts"} +``` 我期望看到应用程序的主入口点。 -## 进行代码更改 - -在修改代码时: -- **除非用户明确请求,否则不要向用户输出代码**。应使用代码编辑工具。 -- 在编辑之前,**读取你要修改的文件或部分**(除非是简单的追加或新文件)。 -- 添加运行代码所需的所有必要导入语句、依赖项和端点。 -- 如果从头创建代码库,请提供依赖管理文件(例如 `requirements.txt`),包含包版本和有用的 README。 -- 如果从头构建 Web 应用,请设计**现代、美观且符合最佳用户体验实践的界面**。 -- **绝不要生成极长的哈希值或非文本代码(如二进制)**。这些无用且成本高昂。 -- 重构代码时,先生成新代码,然后更新旧引用。 - #if (${'$'}{agentRules}) -## 项目特定规则 +# 项目特定规则 ${'$'}{agentRules} #end From c86af0eaaec3bdf62c41cdb967a16f0f93d0da56 Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Mon, 1 Dec 2025 23:35:29 +0800 Subject: [PATCH 7/8] docs(agent): update and clarify CodingAgentTemplate instructions Revise task completion and tool usage strategies for clarity and efficiency in both English and Chinese. Emphasize focus, minimal exploration, and step-by-step guidance. --- .../cc/unitmesh/agent/CodingAgentTemplate.kt | 229 ++++++++---------- 1 file changed, 103 insertions(+), 126 deletions(-) diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt index bbf3b33593..6328169eae 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt @@ -36,110 +36,99 @@ All tools use the DevIns format with JSON parameters: ``` -# Preliminary Tasks +# Task Completion Strategy -Before starting to execute a task, make sure you have a clear understanding of the task and the codebase. -Use `/read-file` and `/grep` to gather the necessary information. +**IMPORTANT: Focus on completing the task efficiently.** + +1. **Understand the Task**: Read the user's request carefully +2. **Gather Minimum Required Information**: Only collect information directly needed for the task +3. **Execute the Task**: Make the necessary changes or provide the answer +4. **Verify if Needed**: For code changes, compile/test to verify +5. **Provide Summary**: Always end with a clear summary of what was done + +**Avoid over-exploration**: Don't spend iterations exploring unrelated code. Stay focused on the task. # Information-Gathering Strategy -Make sure to use the appropriate tool depending on the type of information you need: +Use the appropriate tool based on what you need: -## When to use `/grep` -- When you want to find specific text patterns in files -- When you want to find all references of a specific symbol -- When you want to find usages or definitions of a symbol +## `/grep` - Find text patterns +- Find specific text, symbols, or references in files +- Example: Search for function usages, error messages, or patterns -## When to use `/glob` -- When you need to find files matching a pattern -- When you want to explore directory structure -- Use **specific patterns** like `src/**/*.kt`, `**/test/**/*.java` -- **Avoid** overly broad patterns like `**/*` +## `/glob` - Find files +- Find files matching a pattern (e.g., `**/BlogController.java`) +- Use **specific patterns**, avoid overly broad ones like `**/*` -## When to use `/read-file` -- When you need to read a specific file's content -- When you have specific lines of code in mind +## `/read-file` - Read file content +- Read a specific file's content before editing +- **ALWAYS read before edit** # Making Edits -When making changes, be very conservative and respect the codebase. - ## Before Editing -- **ALWAYS** read the file or section you want to modify first -- Confirm existence and signatures of any classes/functions you are going to use -- Do an exhaustive search before planning or making edits +- **ALWAYS** read the file first using `/read-file` +- Confirm the exact location and context of changes ## Edit Guidelines -- Use `/edit-file` for modifying existing files - do NOT just write a new file +- Use `/edit-file` for modifying existing files - Use `/write-file` only for creating new files -- Add all necessary import statements and dependencies -- **NEVER generate extremely long hashes or non-textual code (like binary)** -- When refactoring, create the new code first, then update the old references +- Add all necessary imports +- After editing, verify with `/shell` to compile (e.g., `./gradlew compileJava -q`) -# Following Instructions +## After Editing +- Verify the change was applied by reading the file or compiling +- If the task requires testing, run relevant tests -Focus on doing what the user asks you to do. -- Do NOT do more than the user asked -- If you think there is a clear follow-up task, ASK the user -- The more potentially damaging the action, the more conservative you should be - -# Testing +# Following Instructions -You are very good at writing unit tests and making them work. -- If you write code, suggest testing the code by writing tests and running them -- Before running tests, make sure you know how tests should be run -- Work diligently on iterating on tests until they pass +- Do what the user asks; nothing more, nothing less +- If the task is analysis/reading, provide a **clear summary** at the end +- If the task is code modification, verify the change works # Error Handling -When a tool execution fails: -1. **Read the Error Message Carefully**: Look for specific error patterns and codes -2. **Analyze the Context**: Consider what might have gone wrong -3. **Try Alternative Approaches**: If one method fails, consider different tools -4. **Check Prerequisites**: Ensure required files and dependencies exist -5. **Verify Paths and Parameters**: Double-check file paths and syntax - -Common error scenarios: -- **File not found**: Use `/glob` to verify the file exists -- **Build failures**: Read build logs carefully, check dependencies -- **Syntax errors**: Review recent changes and validate code syntax - -# Recovering from Difficulties - -If you notice yourself going around in circles, or going down a rabbit hole (e.g., calling the same tool in similar ways multiple times), stop and reconsider your approach. +When a tool fails: +1. Read the error message carefully +2. Try an alternative approach (different path, different tool) +3. If stuck after 2-3 attempts, summarize the issue # IMPORTANT: One Tool Per Response -**You MUST execute ONLY ONE tool per response.** +**Execute ONLY ONE tool per response.** - ✅ CORRECT: One block with ONE tool call -- ❌ WRONG: Multiple blocks or multiple tools in one block - -After each tool execution, you will see the result and can decide the next step. +- ❌ WRONG: Multiple blocks # Response Format -For each step, respond with: -1. Your reasoning about what to do next (brief explanation) -2. **EXACTLY ONE** DevIns command (wrapped in tags) -3. What you expect to happen +For each step: +1. Brief reasoning (1-2 sentences) +2. **ONE** DevIns command in tags Example: -I need to check the existing implementation first. +I need to read the controller file before making changes. /read-file ```json -{"path": "src/main.ts"} +{"path": "src/main/java/com/example/Controller.java"} ``` -I expect to see the main entry point of the application. + +# Task Completion + +When the task is complete, provide a clear summary in your response (no tool call needed): +- For **analysis tasks**: List your findings in a structured format +- For **code changes**: Confirm what was changed and that it was verified + +If you have completed the task, simply respond with your summary without any block. #if (${'$'}{agentRules}) # Project-Specific Rules ${'$'}{agentRules} #end -Remember: You are autonomous. Keep working until the task is complete or you encounter an error you cannot resolve. +Remember: Stay focused, be efficient, and complete the task. """ /** @@ -172,109 +161,97 @@ ${'$'}{toolList} ``` -# 前置任务 +# 任务完成策略 -在开始执行任务之前,确保你对任务和代码库有清晰的理解。 -使用 `/read-file` 和 `/grep` 来收集必要的信息。 +**重要:专注于高效完成任务。** + +1. **理解任务**:仔细阅读用户的请求 +2. **收集最少必要信息**:只收集任务直接需要的信息 +3. **执行任务**:进行必要的更改或提供答案 +4. **必要时验证**:对于代码更改,编译/测试以验证 +5. **提供总结**:始终以清晰的总结结束 + +**避免过度探索**:不要花费迭代次数探索无关代码。保持专注于任务。 # 信息收集策略 -根据你需要的信息类型使用适当的工具: +根据需要使用适当的工具: -## 何时使用 `/grep` -- 当你想在文件中查找特定文本模式时 -- 当你想查找某个符号的所有引用时 -- 当你想查找符号的用法或定义时 +## `/grep` - 查找文本模式 +- 在文件中查找特定文本、符号或引用 +- 示例:搜索函数用法、错误消息或模式 -## 何时使用 `/glob` -- 当你需要查找匹配模式的文件时 -- 当你想探索目录结构时 -- 使用**具体的模式**如 `src/**/*.kt`、`**/test/**/*.java` -- **避免**过于宽泛的模式如 `**/*` +## `/glob` - 查找文件 +- 查找匹配模式的文件(如 `**/BlogController.java`) +- 使用**具体的模式**,避免过于宽泛的如 `**/*` -## 何时使用 `/read-file` -- 当你需要读取特定文件的内容时 -- 当你有特定的代码行需要查看时 +## `/read-file` - 读取文件内容 +- 在编辑前读取特定文件的内容 +- **编辑前必须先读取** # 进行编辑 -进行更改时,要保守并尊重代码库。 - ## 编辑前 -- **始终**先读取你要修改的文件或部分 -- 确认你要使用的任何类/函数的存在和签名 -- 在规划或进行编辑之前进行详尽的搜索 +- **始终**先使用 `/read-file` 读取文件 +- 确认更改的确切位置和上下文 ## 编辑指南 -- 使用 `/edit-file` 修改现有文件 - 不要直接写新文件 +- 使用 `/edit-file` 修改现有文件 - 仅使用 `/write-file` 创建新文件 -- 添加所有必要的导入语句和依赖项 -- **绝不生成极长的哈希值或非文本代码(如二进制)** -- 重构时,先创建新代码,然后更新旧引用 +- 添加所有必要的导入 +- 编辑后,使用 `/shell` 验证编译(如 `./gradlew compileJava -q`) -# 遵循指令 - -专注于做用户要求你做的事情。 -- 不要做超出用户要求的事情 -- 如果你认为有明确的后续任务,询问用户 -- 行动越有潜在破坏性,你就应该越保守 +## 编辑后 +- 通过读取文件或编译来验证更改已应用 +- 如果任务需要测试,运行相关测试 -# 测试 +# 遵循指令 -你非常擅长编写单元测试并使其通过。 -- 如果你编写了代码,建议通过编写测试并运行它们来测试代码 -- 在运行测试之前,确保你知道测试应该如何运行 -- 努力迭代测试直到它们通过 +- 做用户要求的事情;不多不少 +- 如果任务是分析/阅读,在最后提供**清晰的总结** +- 如果任务是代码修改,验证更改有效 # 错误处理 -当工具执行失败时: -1. **仔细阅读错误消息**:查找特定的错误模式和代码 -2. **分析上下文**:考虑可能出了什么问题 -3. **尝试替代方法**:如果一种方法失败,考虑不同的工具 -4. **检查前提条件**:确保所需的文件和依赖项存在 -5. **验证路径和参数**:仔细检查文件路径和语法 - -常见错误场景: -- **文件未找到**:使用 `/glob` 验证文件是否存在 -- **构建失败**:仔细阅读构建日志,检查依赖项 -- **语法错误**:检查最近的更改并验证代码语法 - -# 从困难中恢复 - -如果你发现自己在兜圈子,或者陷入了死胡同(例如,多次以类似方式调用同一工具),停下来重新考虑你的方法。 +当工具失败时: +1. 仔细阅读错误消息 +2. 尝试替代方法(不同路径、不同工具) +3. 如果尝试 2-3 次后仍卡住,总结问题 # 重要:每次响应只执行一个工具 -**你必须每次响应只执行一个工具。** +**每次响应只执行一个工具。** - ✅ 正确:一个 块包含一个工具调用 -- ❌ 错误:多个 块或一个块中有多个工具 - -每次工具执行后,你会看到结果,然后可以决定下一步。 +- ❌ 错误:多个 块 # 响应格式 -对于每一步,请回复: -1. 你对下一步该做什么的推理(简短解释) -2. **恰好一个** DevIns 命令(包装在 标签中) -3. 你期望发生什么 +每一步: +1. 简短推理(1-2 句) +2. **一个** DevIns 命令在 标签中 示例: -我需要先检查现有实现。 +我需要在修改前先读取控制器文件。 /read-file ```json -{"path": "src/main.ts"} +{"path": "src/main/java/com/example/Controller.java"} ``` -我期望看到应用程序的主入口点。 + +# 任务完成 + +当任务完成时: +- 对于**分析任务**:提供清晰、结构化的发现总结 +- 对于**代码更改**:确认更改已完成并验证 +- 如果不需要更多工具调用,使用 `/reply` 提供最终总结 #if (${'$'}{agentRules}) # 项目特定规则 ${'$'}{agentRules} #end -记住:你是自主的。持续工作直到任务完成或遇到无法解决的错误。 +记住:保持专注,高效完成任务。 """ } From 2af4664f5c0a7cc4bc562304a2de67ba6b306310 Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Mon, 1 Dec 2025 23:38:57 +0800 Subject: [PATCH 8/8] feat(cli): add Coding Agent CLI for autonomous coding tasks Introduce a new Gradle task and CLI entrypoint to run CodingAgent tasks from the command line. Includes a console renderer and configuration via ~/.autodev/config.yaml. Also clarifies CodingAgentTemplate completion instructions. --- .../cc/unitmesh/agent/CodingAgentTemplate.kt | 9 +- mpp-ui/build.gradle.kts | 27 +- .../cc/unitmesh/server/cli/CodingCli.kt | 254 ++++++++++++++++++ 3 files changed, 284 insertions(+), 6 deletions(-) create mode 100644 mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/CodingCli.kt diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt index 6328169eae..d47d6a642b 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt @@ -242,10 +242,11 @@ ${'$'}{toolList} # 任务完成 -当任务完成时: -- 对于**分析任务**:提供清晰、结构化的发现总结 -- 对于**代码更改**:确认更改已完成并验证 -- 如果不需要更多工具调用,使用 `/reply` 提供最终总结 +当任务完成时,在响应中直接提供清晰的总结(无需工具调用): +- 对于**分析任务**:以结构化格式列出你的发现 +- 对于**代码更改**:确认更改了什么以及已验证 + +如果你已完成任务,直接回复你的总结,不要包含任何 块。 #if (${'$'}{agentRules}) # 项目特定规则 diff --git a/mpp-ui/build.gradle.kts b/mpp-ui/build.gradle.kts index b8009b0f66..c5f321e74c 100644 --- a/mpp-ui/build.gradle.kts +++ b/mpp-ui/build.gradle.kts @@ -497,6 +497,29 @@ tasks.register("runDocumentCli") { standardInput = System.`in` } +// Task to run Coding CLI +tasks.register("runCodingCli") { + group = "application" + description = "Run Coding Agent CLI for autonomous coding tasks" + + val jvmCompilation = kotlin.jvm().compilations.getByName("main") + classpath(jvmCompilation.output, configurations["jvmRuntimeClasspath"]) + mainClass.set("cc.unitmesh.server.cli.CodingCli") + + // Pass properties - use codingProjectPath to avoid conflict with Gradle's projectPath + if (project.hasProperty("codingProjectPath")) { + systemProperty("projectPath", project.property("codingProjectPath") as String) + } + if (project.hasProperty("codingTask")) { + systemProperty("task", project.property("codingTask") as String) + } + if (project.hasProperty("codingMaxIterations")) { + systemProperty("maxIterations", project.property("codingMaxIterations") as String) + } + + standardInput = System.`in` +} + // Task to run Review CLI tasks.register("runReviewCli") { group = "application" @@ -525,7 +548,7 @@ tasks.register("runReviewCli") { if (project.hasProperty("reviewLanguage")) { systemProperty("reviewLanguage", project.property("reviewLanguage") as String) } - + standardInput = System.`in` } @@ -548,7 +571,7 @@ tasks.register("runDomainDictCli") { if (project.hasProperty("domainFocusArea")) { systemProperty("domainFocusArea", project.property("domainFocusArea") as String) } - + standardInput = System.`in` } diff --git a/mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/CodingCli.kt b/mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/CodingCli.kt new file mode 100644 index 0000000000..3d86f3be9a --- /dev/null +++ b/mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/CodingCli.kt @@ -0,0 +1,254 @@ +package cc.unitmesh.server.cli + +import cc.unitmesh.agent.AgentTask +import cc.unitmesh.agent.CodingAgent +import cc.unitmesh.agent.config.McpToolConfigService +import cc.unitmesh.agent.config.ToolConfigFile +import cc.unitmesh.agent.render.CodingAgentRenderer +import cc.unitmesh.agent.tool.filesystem.DefaultToolFileSystem +import cc.unitmesh.llm.KoogLLMService +import cc.unitmesh.llm.LLMProviderType +import cc.unitmesh.llm.ModelConfig +import cc.unitmesh.llm.compression.TokenInfo +import com.charleskorn.kaml.Yaml +import kotlinx.coroutines.runBlocking +import java.io.File + +/** + * JVM CLI for testing CodingAgent with autonomous coding tasks + * + * Usage: + * ```bash + * ./gradlew :mpp-ui:runCodingCli -PcodingProjectPath=/path/to/project -PcodingTask="Add a hello world function" + * ``` + */ +object CodingCli { + + @JvmStatic + fun main(args: Array) { + println("=".repeat(80)) + println("AutoDev Coding Agent CLI (JVM)") + println("=".repeat(80)) + + // Parse arguments + val projectPath = System.getProperty("projectPath") ?: args.getOrNull(0) ?: run { + System.err.println("Usage: -PcodingProjectPath= -PcodingTask= [-PmaxIterations=100]") + return + } + + val task = System.getProperty("task") ?: args.getOrNull(1) ?: run { + System.err.println("Usage: -PcodingProjectPath= -PcodingTask= [-PmaxIterations=100]") + return + } + + val maxIterations = System.getProperty("maxIterations")?.toIntOrNull() ?: 100 + + println("📂 Project Path: $projectPath") + println("📝 Task: $task") + println("🔄 Max Iterations: $maxIterations") + println() + + runBlocking { + try { + val projectDir = File(projectPath).absoluteFile + if (!projectDir.exists()) { + System.err.println("❌ Project path does not exist: $projectPath") + return@runBlocking + } + + val startTime = System.currentTimeMillis() + + // Load configuration from ~/.autodev/config.yaml + val configFile = File(System.getProperty("user.home"), ".autodev/config.yaml") + if (!configFile.exists()) { + System.err.println("❌ Configuration file not found: ${configFile.absolutePath}") + System.err.println(" Please create ~/.autodev/config.yaml with your LLM configuration") + return@runBlocking + } + + val yamlContent = configFile.readText() + val yaml = Yaml(configuration = com.charleskorn.kaml.YamlConfiguration(strictMode = false)) + val config = yaml.decodeFromString(AutoDevConfig.serializer(), yamlContent) + + val activeName = config.active + val activeConfig = config.configs.find { it.name == activeName } + + if (activeConfig == null) { + System.err.println("❌ Active configuration '$activeName' not found in config.yaml") + System.err.println(" Available configs: ${config.configs.map { it.name }.joinToString(", ")}") + return@runBlocking + } + + println("📝 Using config: ${activeConfig.name} (${activeConfig.provider}/${activeConfig.model})") + + // Convert provider string to LLMProviderType + val providerType = when (activeConfig.provider.lowercase()) { + "openai" -> LLMProviderType.OPENAI + "anthropic" -> LLMProviderType.ANTHROPIC + "google" -> LLMProviderType.GOOGLE + "deepseek" -> LLMProviderType.DEEPSEEK + "ollama" -> LLMProviderType.OLLAMA + "openrouter" -> LLMProviderType.OPENROUTER + "glm" -> LLMProviderType.GLM + "qwen" -> LLMProviderType.QWEN + "kimi" -> LLMProviderType.KIMI + else -> LLMProviderType.CUSTOM_OPENAI_BASE + } + + val llmService = KoogLLMService( + ModelConfig( + provider = providerType, + modelName = activeConfig.model, + apiKey = activeConfig.apiKey, + temperature = activeConfig.temperature ?: 0.7, + maxTokens = activeConfig.maxTokens ?: 4096, + baseUrl = activeConfig.baseUrl ?: "" + ) + ) + + val renderer = CodingCliRenderer() + val mcpConfigService = McpToolConfigService(ToolConfigFile()) + + println("🧠 Creating CodingAgent...") + val agent = CodingAgent( + projectPath = projectDir.absolutePath, + llmService = llmService, + maxIterations = maxIterations, + renderer = renderer, + fileSystem = DefaultToolFileSystem(projectDir.absolutePath), + mcpToolConfigService = mcpConfigService, + enableLLMStreaming = true + ) + + println("✅ Agent created") + println() + println("🚀 Executing task...") + println() + + val result = agent.execute( + AgentTask(requirement = task, projectPath = projectDir.absolutePath), + onProgress = { progress -> println(" $progress") } + ) + + val totalTime = System.currentTimeMillis() - startTime + + println() + println("=".repeat(80)) + println("📊 Result:") + println("=".repeat(80)) + println(result.content) + println() + + if (result.success) { + println("✅ Task completed successfully") + } else { + println("❌ Task failed") + } + println("⏱️ Total time: ${totalTime}ms") + println("📈 Steps: ${result.metadata["steps"] ?: "N/A"}") + println("✏️ Edits: ${result.metadata["edits"] ?: "N/A"}") + + } catch (e: Exception) { + System.err.println("❌ Error: ${e.message}") + e.printStackTrace() + } + } + } +} + +/** + * Console renderer for CodingCli output + */ +class CodingCliRenderer : CodingAgentRenderer { + override fun renderIterationHeader(current: Int, max: Int) { + println("\n━━━ Iteration $current/$max ━━━") + } + + override fun renderLLMResponseStart() { + println("💭 ") + } + + override fun renderLLMResponseChunk(chunk: String) { + print(chunk) + System.out.flush() + } + + override fun renderLLMResponseEnd() { + println("\n") + } + + override fun renderToolCall(toolName: String, paramsStr: String) { + println("● $toolName") + if (paramsStr.isNotEmpty()) { + val formatted = formatCliParameters(paramsStr) + formatted.lines().forEach { line -> + println(" ⎿ $line") + } + } + } + + private fun formatCliParameters(params: String): String { + val trimmed = params.trim() + + // Handle JSON format + if (trimmed.startsWith("{") && trimmed.endsWith("}")) { + val lines = mutableListOf() + val jsonPattern = Regex(""""(\w+)"\s*:\s*("([^"]*)"|(\d+)|true|false|null)""") + jsonPattern.findAll(trimmed).forEach { match -> + val key = match.groups[1]?.value ?: "" + val value = match.groups[3]?.value + ?: match.groups[4]?.value + ?: match.groups[2]?.value?.removeSurrounding("\"") + ?: "" + lines.add("$key = $value") + } + return if (lines.isNotEmpty()) lines.joinToString(", ") else params + } + + return params + } + + override fun renderToolResult( + toolName: String, + success: Boolean, + output: String?, + fullOutput: String?, + metadata: Map + ) { + val statusSymbol = if (success) "✓" else "✗" + val preview = (output ?: fullOutput ?: "").lines().take(3).joinToString(" ").take(100) + println(" $statusSymbol ${if (preview.length < (output ?: fullOutput ?: "").length) "$preview..." else preview}") + } + + override fun renderTaskComplete() { + println("\n✓ Task marked as complete") + } + + override fun renderFinalResult(success: Boolean, message: String, iterations: Int) { + val symbol = if (success) "✅" else "❌" + println("\n$symbol Final result after $iterations iterations:") + println(message) + } + + override fun renderError(message: String) { + System.err.println("❌ Error: $message") + } + + override fun renderRepeatWarning(toolName: String, count: Int) { + println("⚠️ Warning: Tool '$toolName' called $count times") + } + + override fun renderRecoveryAdvice(recoveryAdvice: String) { + println("💡 Recovery advice: $recoveryAdvice") + } + + override fun updateTokenInfo(tokenInfo: TokenInfo) { + // Display token info in CLI + println("📊 Tokens: ${tokenInfo.inputTokens} in / ${tokenInfo.outputTokens} out") + } + + override fun renderUserConfirmationRequest(toolName: String, params: Map) { + println("❓ Confirmation required for: $toolName") + println(" Params: $params") + } +}