diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgent.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgent.kt index 8b237e7e92..2cd260eb77 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgent.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgent.kt @@ -107,22 +107,18 @@ class CodingAgent( private var mcpToolsInitialized = false init { - // 注册 Agents(作为 Tools)- 根据配置决定是否启用 - if (configService.isBuiltinToolEnabled("error-agent")) { - registerTool(errorRecoveryAgent) - toolRegistry.registerTool(errorRecoveryAgent) // 同时注册到 ToolRegistry - subAgentManager.registerSubAgent(errorRecoveryAgent) // 注册到 SubAgentManager - } - if (configService.isBuiltinToolEnabled("analysis-agent")) { - registerTool(analysisAgent) - toolRegistry.registerTool(analysisAgent) // 同时注册到 ToolRegistry - subAgentManager.registerSubAgent(analysisAgent) // 注册到 SubAgentManager - } - if (configService.isBuiltinToolEnabled("code-agent")) { - registerTool(codebaseInvestigatorAgent) - toolRegistry.registerTool(codebaseInvestigatorAgent) // 同时注册到 ToolRegistry - subAgentManager.registerSubAgent(codebaseInvestigatorAgent) // 注册到 SubAgentManager - } + // Register Sub-Agents (as Tools) - Always enabled as they are built-in tools + registerTool(errorRecoveryAgent) + toolRegistry.registerTool(errorRecoveryAgent) + subAgentManager.registerSubAgent(errorRecoveryAgent) + + registerTool(analysisAgent) + toolRegistry.registerTool(analysisAgent) + subAgentManager.registerSubAgent(analysisAgent) + + registerTool(codebaseInvestigatorAgent) + toolRegistry.registerTool(codebaseInvestigatorAgent) + subAgentManager.registerSubAgent(codebaseInvestigatorAgent) CoroutineScope(SupervisorJob() + Dispatchers.Default).launch { initializeWorkspace(projectPath) 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 10af7f744a..858aeac88f 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgentTemplate.kt @@ -38,12 +38,72 @@ Each tool's parameters are validated against its JSON Schema. Refer to the schem ## Task Execution Guidelines -1. **Gather Context First**: Before making changes, use /read-file and /glob to understand the codebase +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 + +When searching for files, use **specific and targeted patterns** to avoid overwhelming context: + +**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 + +**DON'T:** +- ❌ Avoid `**/*` or overly broad patterns (returns too many files, wastes context) +- ❌ Don't glob the entire codebase without a specific goal + +**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 + +## Agent Communication & Collaboration + +When dealing with complex information or large content, you can **communicate with specialized SubAgents** to get focused analysis: + +**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 + +**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?"} + ``` + ``` + +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?"} + ``` + ``` + +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?"} + ``` + ``` + +**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 + +This approach keeps your context efficient while getting deep insights from specialized agents! + ## Task Progress Communication For complex multi-step tasks (5+ steps), use `/task-boundary` to help users understand your progress: @@ -158,11 +218,71 @@ ${'$'}{toolList} ## 任务执行指南 -1. **先获取上下文**: 在进行更改之前,使用 /read-file 和 /glob 来了解代码库 +1. **先获取上下文**: 在进行更改之前,先来了解代码库 2. **规划你的方法**: 逐步思考需要做什么 3. **增量更改**: 一次做一个更改并验证其有效性 4. **测试更改**: 运行测试或构建命令来验证更改 +## 智能文件搜索指南 + +搜索文件时,使用**具体且有针对性的模式**以避免上下文超载: + +**应该做:** +- ✅ 使用具体的模式:`src/**/*.kt`、`**/test/**/*.java`、`**/config/*.yml` +- ✅ 针对特定目录:`/glob pattern="*.ts" path="src/main"` +- ✅ 先使用 grep 配合具体模式来缩小范围 +- ✅ 对于广泛探索,使用 `/ask-agent` 获取摘要 + +**不应该做:** +- ❌ 避免 `**/*` 或过于宽泛的模式(返回太多文件,浪费上下文) +- ❌ 不要在没有明确目标的情况下 glob 整个代码库 + +**智能策略:** +1. 如果需要了解项目结构,先使用 grep 搜索特定关键词 +2. 根据发现的内容使用有针对性的 glob 模式 +3. 对于非常大的结果集(100+ 文件),系统会自动调用 SummaryAgent 提供简洁概述 + +## Agent 通信与协作 + +处理复杂信息或大量内容时,你可以**与专业的 SubAgent 通信**来获取专注的分析: + +**可用的 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": "你能识别出文件中的主要依赖关系吗?"} + ``` + ``` + +**示例工作流:** +1. `/glob pattern="**/*.kt"` → 自动触发 AnalysisAgent(返回摘要) +2. 查看摘要,然后询问:`/ask-agent` 获取特定见解 +3. 基于见解,使用有针对性的 `/read-file` 或 `/grep` 命令 + +这种方法既保持上下文高效,又能从专业 Agent 获得深度见解! + ## 任务进度沟通 对于复杂的多步骤任务(5+ 步骤),使用 `/task-boundary` 帮助用户了解你的进度: diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/McpToolConfigService.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/McpToolConfigService.kt index 66a2fbdab2..2c1585704f 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/McpToolConfigService.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/McpToolConfigService.kt @@ -22,11 +22,16 @@ class McpToolConfigService(val toolConfig: ToolConfigFile) { } } + /** + * Built-in tools are always enabled and cannot be disabled. + * This method always returns true for backward compatibility. + * + * @deprecated Built-in tools are now always enabled + */ + @Deprecated("Built-in tools are always enabled", ReplaceWith("true")) fun isBuiltinToolEnabled(toolName: String): Boolean { - if (toolConfig.enabledBuiltinTools.isEmpty()) { - return true - } - return toolName in toolConfig.enabledBuiltinTools + // Built-in tools are always enabled + return true } fun isMcpToolEnabled(toolName: String): Boolean { @@ -36,14 +41,16 @@ class McpToolConfigService(val toolConfig: ToolConfigFile) { return toolName in toolConfig.enabledMcpTools } + /** + * Built-in tools are always enabled and cannot be disabled. + * This method returns all tools without filtering. + * + * @deprecated Built-in tools are now always enabled + */ + @Deprecated("Built-in tools are always enabled", ReplaceWith("tools")) fun > filterBuiltinTools(tools: List): List { - if (toolConfig.enabledBuiltinTools.isEmpty()) { - return tools - } - - return tools.filter { tool -> - isBuiltinToolEnabled(tool.name) - } + // Built-in tools are always enabled - no filtering + return tools } fun > filterMcpTools(tools: List): List { diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/ToolConfig.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/ToolConfig.kt index 040b209678..64df10b674 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/ToolConfig.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/ToolConfig.kt @@ -7,24 +7,13 @@ import kotlinx.serialization.Serializable * Tool Configuration - Manages enabled tools for CodingAgent * * Supports both: - * - Built-in system tools (from ToolType) - * - MCP (Model Context Protocol) tools from external servers + * - Built-in system tools (always enabled, cannot be disabled) + * - MCP (Model Context Protocol) tools from external servers (configurable) * * Stored in ~/.autodev/mcp.json */ @Serializable data class ToolConfigFile( - /** - * List of enabled built-in tool names - * - * Available tools: - * - File System: read-file, write-file, list-files, edit-file, patch-file - * - Search: grep, glob - * - Execution: shell - * - SubAgent: error-recovery, log-summary, codebase-investigator - */ - val enabledBuiltinTools: List = emptyList(), - /** * List of enabled MCP tool names (tool names, not server names) */ @@ -38,17 +27,13 @@ data class ToolConfigFile( ) { companion object { /** - * Default configuration with all built-in tools enabled + * Default configuration. + * + * Note: Built-in tools are always enabled and don't need to be listed. + * The enabledBuiltinTools field is deprecated and ignored. */ fun default(): ToolConfigFile { return ToolConfigFile( - enabledBuiltinTools = listOf( - "read-file", "write-file", "list-files", "edit-file", "patch-file", - "grep", "glob", - "shell", - "web-fetch", - "error-recovery", "log-summary", "codebase-investigator" - ), enabledMcpTools = emptyList(), mcpServers = emptyMap() ) diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/ToolConfigManager.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/ToolConfigManager.kt deleted file mode 100644 index 28cc80b5de..0000000000 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/config/ToolConfigManager.kt +++ /dev/null @@ -1,116 +0,0 @@ -package cc.unitmesh.agent.config - -import cc.unitmesh.agent.mcp.McpServerConfig -import cc.unitmesh.agent.tool.ExecutableTool -import cc.unitmesh.agent.tool.schema.ToolCategory - -/** - * Tool Configuration Manager - * - * Manages tool configurations including: - * - Built-in tools (self-describing via metadata) - * - MCP tools from external servers - * - * Note: Tools are now self-describing. This manager no longer needs - * to hardcode tool information or maintain parallel metadata. - */ -object ToolConfigManager { - - /** - * Get all available built-in tools grouped by category - * - * @param tools List of executable tools to organize by category - * @return Tools grouped by category - */ - fun getBuiltinToolsByCategory( - tools: List> - ): Map> { - val toolsByCategory = mutableMapOf>() - - tools.forEach { tool -> - val metadata = tool.metadata - val category = metadata.category - - val toolItem = ToolItem( - name = tool.name, - displayName = metadata.displayName, - description = tool.description, - category = category.name, - source = ToolSource.BUILTIN, - schema = null - ) - - toolsByCategory.getOrPut(category) { mutableListOf() }.add(toolItem) - } - - return toolsByCategory - } - - /** - * Create a tool configuration from current config file - */ - fun applyEnabledTools( - toolsByCategory: Map>, - config: ToolConfigFile - ): Map> { - val enabledBuiltinTools = config.enabledBuiltinTools.toSet() - - return toolsByCategory.mapValues { (_, tools) -> - tools.map { tool -> - tool.copy(enabled = tool.name in enabledBuiltinTools) - } - } - } - - /** - * Discover MCP tools from server configurations - */ - suspend fun discoverMcpTools( - mcpServers: Map, - enabledMcpTools: Set - ): Map> { - return McpToolConfigManager.discoverMcpTools(mcpServers, enabledMcpTools) - } - - fun updateToolConfig( - currentConfig: ToolConfigFile, - enabledBuiltinTools: List, - enabledMcpTools: List - ): ToolConfigFile { - return currentConfig.copy( - enabledBuiltinTools = enabledBuiltinTools, - enabledMcpTools = enabledMcpTools - ) - } - - fun getConfigSummary(config: ToolConfigFile): String { - return buildString { - appendLine("Built-in Tools: ${config.enabledBuiltinTools.size} enabled") - appendLine("MCP Tools: ${config.enabledMcpTools.size} enabled") - appendLine("MCP Servers: ${config.mcpServers.size} configured") - - if (config.enabledBuiltinTools.isNotEmpty()) { - appendLine("\nEnabled Built-in Tools:") - config.enabledBuiltinTools.forEach { toolName -> - appendLine(" - $toolName") - } - } - - if (config.enabledMcpTools.isNotEmpty()) { - appendLine("\nEnabled MCP Tools:") - config.enabledMcpTools.forEach { toolName -> - appendLine(" - $toolName") - } - } - - if (config.mcpServers.isNotEmpty()) { - appendLine("\nMCP Servers:") - config.mcpServers.forEach { (name, server) -> - val status = if (server.disabled) "disabled" else "enabled" - appendLine(" - $name ($status)") - } - } - } - } -} - diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/orchestrator/ToolOrchestrator.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/orchestrator/ToolOrchestrator.kt index cf29655b46..3ea469b499 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/orchestrator/ToolOrchestrator.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/orchestrator/ToolOrchestrator.kt @@ -253,10 +253,16 @@ class ToolOrchestrator( ToolType.Glob -> executeGlobTool(tool, params, basicContext) ToolType.Grep -> executeGrepTool(tool, params, basicContext) ToolType.WebFetch -> executeWebFetchTool(tool, params, basicContext) + ToolType.AskAgent -> executeAskAgentTool(tool, params, basicContext) else -> { - // For new tools (task-boundary, ask-agent, etc.), use generic execution - logger.debug { "Executing tool generically: $toolName" } - executeGenericTool(tool, params, basicContext) + // Handle special tools that need parameter conversion + if (toolName == "task-boundary") { + executeTaskBoundaryTool(tool, params, basicContext) + } else { + // For truly generic tools, use generic execution + logger.debug { "Executing tool generically: $toolName" } + executeGenericTool(tool, params, basicContext) + } } } } @@ -512,6 +518,52 @@ class ToolOrchestrator( return invocation.execute(context) } + private suspend fun executeAskAgentTool( + tool: Tool, + params: Map, + context: cc.unitmesh.agent.tool.ToolExecutionContext + ): ToolResult { + val askAgentTool = tool as cc.unitmesh.agent.tool.impl.AskAgentTool + + val agentName = params["agentName"] as? String + ?: return ToolResult.Error("agentName parameter is required") + val question = params["question"] as? String + ?: return ToolResult.Error("question parameter is required") + val contextMap = params["context"] as? Map<*, *> + + val askAgentParams = cc.unitmesh.agent.tool.impl.AskSubAgentParams( + agentName = agentName, + question = question, + context = contextMap?.mapKeys { it.key.toString() }?.mapValues { it.value.toString() } ?: emptyMap() + ) + + val invocation = askAgentTool.createInvocation(askAgentParams) + return invocation.execute(context) + } + + private suspend fun executeTaskBoundaryTool( + tool: Tool, + params: Map, + context: cc.unitmesh.agent.tool.ToolExecutionContext + ): ToolResult { + val taskBoundaryTool = tool as cc.unitmesh.agent.tool.impl.TaskBoundaryTool + + val taskName = params["taskName"] as? String + ?: return ToolResult.Error("taskName parameter is required") + val status = params["status"] as? String + ?: return ToolResult.Error("status parameter is required") + val summary = params["summary"] as? String ?: "" + + val taskBoundaryParams = cc.unitmesh.agent.tool.impl.TaskBoundaryParams( + taskName = taskName, + status = status, + summary = summary + ) + + val invocation = taskBoundaryTool.createInvocation(taskBoundaryParams) + return invocation.execute(context) + } + private fun isSuccessResult(result: ToolResult): Boolean { return when (result) { is ToolResult.Success -> true diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/impl/GlobTool.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/impl/GlobTool.kt index f39c68c46f..977880bcdc 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/impl/GlobTool.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/impl/GlobTool.kt @@ -86,9 +86,17 @@ class GlobInvocation( params: GlobParams, tool: GlobTool, private val fileSystem: ToolFileSystem, - private val gitIgnoreParser: GitIgnoreParser? = null + private val gitIgnoreParser: GitIgnoreParser? = null, + private val analysisAgent: cc.unitmesh.agent.subagent.AnalysisAgent? = null ) : BaseToolInvocation(params, tool) { + companion object { + // Threshold for triggering AnalysisAgent (file count) + const val FILE_COUNT_THRESHOLD = 100 + // Threshold for triggering AnalysisAgent (character count) + const val CHAR_COUNT_THRESHOLD = 10000 + } + override fun getDescription(): String { val searchPath = params.path ?: "project root" val typeDesc = if (params.includeDirectories) "files and directories" else "files" @@ -118,7 +126,7 @@ class GlobInvocation( val limitedMatches = sortedMatches.take(params.maxResults) val resultText = formatResults(limitedMatches, matches.size) - val metadata = mapOf( + val metadata = mutableMapOf( "pattern" to params.pattern, "search_path" to searchPath, "total_matches" to matches.size.toString(), @@ -129,7 +137,49 @@ class GlobInvocation( "respect_gitignore" to params.respectGitIgnore.toString() ) - ToolResult.Success(resultText, metadata) + // Check if result is too long and should trigger AnalysisAgent + val shouldSummarize = limitedMatches.size >= FILE_COUNT_THRESHOLD || + resultText.length >= CHAR_COUNT_THRESHOLD + + if (shouldSummarize && analysisAgent != null) { + metadata["triggered_analysis"] = "true" + metadata["original_result_length"] = resultText.length.toString() + + // Trigger AnalysisAgent to summarize the results + val analysisContext = cc.unitmesh.agent.subagent.ContentHandlerContext( + content = resultText, + contentType = "file-list", + source = "glob", + metadata = metadata.mapValues { it.value.toString() } + ) + + val analysisResult = analysisAgent.execute(analysisContext) { progress -> + // Log progress if needed + } + + if (analysisResult.success) { + // Return summarized result with reference to full list + val summarizedText = buildString { + appendLine("⚠️ Large file list detected (${limitedMatches.size} files, ${resultText.length} chars)") + appendLine("🤖 AnalysisAgent automatically triggered to provide a summary:") + appendLine() + appendLine(analysisResult.content) + appendLine() + appendLine("💡 Tip: Use more specific glob patterns to reduce result size:") + appendLine(" - Instead of: **/*") + appendLine(" - Try: src/**/*.kt or **/test/**/*.java") + appendLine() + appendLine("📋 Full file list available in metadata if needed.") + } + + metadata["full_result"] = resultText + metadata["analysis_metadata"] = analysisResult.metadata.toString() + + return@safeExecute ToolResult.Success(summarizedText, metadata.toMap()) + } + } + + ToolResult.Success(resultText, metadata.toMap()) } } @@ -284,12 +334,15 @@ class GlobInvocation( } class GlobTool( - private val fileSystem: ToolFileSystem + private val fileSystem: ToolFileSystem, + private val analysisAgent: cc.unitmesh.agent.subagent.AnalysisAgent? = null ) : BaseExecutableTool() { override val name: String = "glob" override val description: String = - """Efficiently finds files matching specific glob patterns (e.g., `src/**/*.ts`, `**/*.md`), returning absolute paths sorted by modification time (newest first). Ideal for quickly locating files based on their name or path structure, especially in large codebases.""".trimIndent() + """Efficiently finds files matching specific glob patterns (e.g., `src/**/*.ts`, `**/*.md`), returning absolute paths sorted by modification time (newest first). Ideal for quickly locating files based on their name or path structure, especially in large codebases. + +⚠️ IMPORTANT: Avoid overly broad patterns like `**/*` as they can return too many files and waste context. Use specific patterns instead. When results are too large (100+ files), the system automatically triggers AnalysisAgent to provide a concise summary.""".trimIndent() override val metadata: ToolMetadata = ToolMetadata( displayName = "Find Files", @@ -321,7 +374,7 @@ class GlobTool( null } - return GlobInvocation(params, this, fileSystem, gitIgnoreParser) + return GlobInvocation(params, this, fileSystem, gitIgnoreParser, analysisAgent) } private fun validateParameters(params: GlobParams) { diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/registry/BuiltinToolsProvider.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/registry/BuiltinToolsProvider.kt index 97283dec5a..3f81eab24d 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/registry/BuiltinToolsProvider.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/registry/BuiltinToolsProvider.kt @@ -32,7 +32,10 @@ class BuiltinToolsProvider : ToolProvider { // Search tools tools.add(GrepTool(dependencies.fileSystem)) - tools.add(GlobTool(dependencies.fileSystem)) + + // GlobTool with AnalysisAgent support for auto-summarization of large results + val analysisAgent = dependencies.subAgentManager?.getSubAgent("analysis-agent") as? cc.unitmesh.agent.subagent.AnalysisAgent + tools.add(GlobTool(dependencies.fileSystem, analysisAgent)) if (dependencies.shellExecutor.isAvailable()) { tools.add(ShellTool(dependencies.shellExecutor)) diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/registry/ToolRegistry.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/registry/ToolRegistry.kt index 56e46a0141..e9fc9cd1a5 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/registry/ToolRegistry.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/registry/ToolRegistry.kt @@ -168,18 +168,10 @@ class ToolRegistry( val allBuiltinTools = ToolProviderRegistry.discoverTools(dependencies) logger.debug { "🔧 [ToolRegistry] All available built-in tools: ${allBuiltinTools.map { it.name }}" } - logger.debug { "🔧 [ToolRegistry] ConfigService available: ${configService != null}" } - - val toolsToRegister = if (configService != null) { - val filtered = configService.filterBuiltinTools(allBuiltinTools) - logger.debug { "🔧 [ToolRegistry] Filtered tools: ${filtered.map { it.name }}" } - filtered - } else { - logger.debug { "🔧 [ToolRegistry] No config service, registering all tools" } - allBuiltinTools - } - - toolsToRegister.forEach { tool -> + + // Built-in tools are always enabled and cannot be disabled + // They are essential for agent functionality + allBuiltinTools.forEach { tool -> try { registerTool(tool) } catch (e: Exception) { @@ -187,7 +179,7 @@ class ToolRegistry( } } - logger.info { "🔧 Registered ${toolsToRegister.size}/${allBuiltinTools.size} built-in tools" } + logger.info { "🔧 Registered ${allBuiltinTools.size} built-in tools (always enabled)" } } } diff --git a/mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/orchestrator/McpToolExecutionTest.kt b/mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/orchestrator/McpToolExecutionTest.kt index f937056ecb..9e15bd0f3d 100644 --- a/mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/orchestrator/McpToolExecutionTest.kt +++ b/mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/orchestrator/McpToolExecutionTest.kt @@ -23,8 +23,9 @@ class McpToolExecutionTest { @Test fun testMcpToolNameWithoutPrefix() = runTest { // Test that MCP tools use actual tool names, not prefixed names + // Note: enabledBuiltinTools is deprecated and ignored (built-in tools are always enabled) val toolConfig = ToolConfigFile( - enabledBuiltinTools = listOf("read-file", "write-file"), + enabledBuiltinTools = emptyList(), // Deprecated: built-in tools are always enabled enabledMcpTools = listOf("list_directory", "read_file"), // Actual tool names mcpServers = mapOf( "filesystem" to McpServerConfig( @@ -48,8 +49,9 @@ class McpToolExecutionTest { return@runTest } + // Note: enabledBuiltinTools is deprecated and ignored (built-in tools are always enabled) val toolConfig = ToolConfigFile( - enabledBuiltinTools = listOf("read-file"), + enabledBuiltinTools = emptyList(), // Deprecated: built-in tools are always enabled enabledMcpTools = listOf("list_directory"), mcpServers = mapOf( "filesystem" to McpServerConfig( diff --git a/mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/config/ToolConfigExports.kt b/mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/config/ToolConfigExports.kt index c65ebf0e68..2e1d33eb24 100644 --- a/mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/config/ToolConfigExports.kt +++ b/mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/config/ToolConfigExports.kt @@ -52,19 +52,45 @@ object JsToolConfigManager { @JsName("getConfigSummary") fun getConfigSummary(config: JsToolConfigFile): String { - return ToolConfigManager.getConfigSummary(config.toCommon()) + val config1 = config.toCommon() + return buildString { + this.appendLine("Built-in Tools: Always enabled (all)") + this.appendLine("MCP Tools: ${config1.enabledMcpTools.size} enabled") + this.appendLine("MCP Servers: ${config1.mcpServers.size} configured") + + if (config1.enabledMcpTools.isNotEmpty()) { + this.appendLine("\nEnabled MCP Tools:") + config1.enabledMcpTools.forEach { toolName -> + this.appendLine(" - $toolName") + } + } + + if (config1.mcpServers.isNotEmpty()) { + this.appendLine("\nMCP Servers:") + config1.mcpServers.forEach { (name, server) -> + val status = if (server.disabled) "disabled" else "enabled" + this.appendLine(" - $name ($status)") + } + } + } } + /** + * Update tool configuration + * + * @param currentConfig Current configuration + * @param enabledBuiltinTools Deprecated: Built-in tools are always enabled, this parameter is ignored + * @param enabledMcpTools List of enabled MCP tool names + * @return Updated configuration + */ @JsName("updateToolConfig") fun updateToolConfig( currentConfig: JsToolConfigFile, enabledBuiltinTools: Array, enabledMcpTools: Array ): JsToolConfigFile { - val updated = ToolConfigManager.updateToolConfig( - currentConfig.toCommon(), - enabledBuiltinTools.toList(), - enabledMcpTools.toList() + val updated = currentConfig.toCommon().copy( + enabledMcpTools = enabledMcpTools.toList() ) return JsToolConfigFile.fromCommon(updated) } @@ -72,9 +98,13 @@ object JsToolConfigManager { /** * JS-friendly ToolConfigFile + * + * Note: enabledBuiltinTools is deprecated and ignored. Built-in tools are always enabled. + * This field is kept for backward compatibility with existing JS/TS code. */ @JsExport class JsToolConfigFile( + /** @deprecated Built-in tools are always enabled. This field is ignored. */ val enabledBuiltinTools: Array, val enabledMcpTools: Array, val mcpServers: dynamic, @@ -98,7 +128,6 @@ class JsToolConfigFile( } return ToolConfigFile( - enabledBuiltinTools = enabledBuiltinTools.toList(), enabledMcpTools = enabledMcpTools.toList(), mcpServers = mcpServersMap, ) @@ -119,7 +148,7 @@ class JsToolConfigFile( } return JsToolConfigFile( - enabledBuiltinTools = config.enabledBuiltinTools.toTypedArray(), + enabledBuiltinTools = emptyArray(), // Deprecated: Built-in tools are always enabled enabledMcpTools = config.enabledMcpTools.toTypedArray(), mcpServers = mcpServersJs ) @@ -204,7 +233,7 @@ private suspend fun loadToolConfigFromFile(): ToolConfigFile { } val config = json.decodeFromString(content) console.log("✅ Tool config parsed successfully") - console.log(" Builtin tools:", config.enabledBuiltinTools.size) + console.log(" Built-in tools: Always enabled (all)") console.log(" MCP tools:", config.enabledMcpTools.size) console.log(" MCP servers:", config.mcpServers.size) diff --git a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt index 5b333361e4..fc992f6182 100644 --- a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt +++ b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt @@ -617,7 +617,7 @@ private fun AutoDevContent( onDismiss = { showToolConfigDialog = false }, onSave = { newConfig -> println("✅ 工具配置已保存") - println(" 启用的内置工具: ${newConfig.enabledBuiltinTools.size}") + println(" 内置工具: 始终启用 (全部)") println(" 启用的 MCP 工具: ${newConfig.enabledMcpTools.size}") showToolConfigDialog = false }, diff --git a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt index dbe4a48653..3ed277dfe6 100644 --- a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt +++ b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt @@ -119,17 +119,51 @@ fun AgentChatInterface( ) } - // Chat 消息列表 - AgentMessageList( - renderer = viewModel.renderer, - modifier = - Modifier + // Chat 消息列表 和 Task Panel + val activeTasks = remember(viewModel.renderer.tasks) { + viewModel.renderer.tasks.filter { + it.status != TaskStatus.COMPLETED && it.status != TaskStatus.CANCELLED + } + } + + if (activeTasks.isNotEmpty()) { + Row( + modifier = Modifier .fillMaxWidth() .weight(1f), - onOpenFileViewer = { filePath -> - viewModel.renderer.openFileViewer(filePath) + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + AgentMessageList( + renderer = viewModel.renderer, + modifier = Modifier + .weight(0.65f) + .fillMaxHeight(), + onOpenFileViewer = { filePath -> + viewModel.renderer.openFileViewer(filePath) + } + ) + + TaskPanel( + tasks = activeTasks, + modifier = Modifier + .weight(0.35f) + .fillMaxHeight() + .padding(end = 12.dp), + onClose = { /* Tasks auto-hide when completed */ } + ) } - ) + } else { + AgentMessageList( + renderer = viewModel.renderer, + modifier = + Modifier + .fillMaxWidth() + .weight(1f), + onOpenFileViewer = { filePath -> + viewModel.renderer.openFileViewer(filePath) + } + ) + } val callbacks = remember(viewModel) { @@ -242,16 +276,51 @@ fun AgentChatInterface( ) } - AgentMessageList( - renderer = viewModel.renderer, - modifier = - Modifier + // Chat 消息列表 和 Task Panel + val activeTasks = remember(viewModel.renderer.tasks) { + viewModel.renderer.tasks.filter { + it.status != TaskStatus.COMPLETED && it.status != TaskStatus.CANCELLED + } + } + + if (activeTasks.isNotEmpty()) { + Row( + modifier = Modifier .fillMaxWidth() .weight(1f), - onOpenFileViewer = { filePath -> - viewModel.renderer.openFileViewer(filePath) + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + AgentMessageList( + renderer = viewModel.renderer, + modifier = Modifier + .weight(0.65f) + .fillMaxHeight(), + onOpenFileViewer = { filePath -> + viewModel.renderer.openFileViewer(filePath) + } + ) + + TaskPanel( + tasks = activeTasks, + modifier = Modifier + .weight(0.35f) + .fillMaxHeight() + .padding(end = 12.dp), + onClose = { /* Tasks auto-hide when completed */ } + ) } - ) + } else { + AgentMessageList( + renderer = viewModel.renderer, + modifier = + Modifier + .fillMaxWidth() + .weight(1f), + onOpenFileViewer = { filePath -> + viewModel.renderer.openFileViewer(filePath) + } + ) + } val callbacks = remember(viewModel) { @@ -309,17 +378,6 @@ private fun ToolLoadingStatusBar( horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { - // Built-in Tools Status - ToolStatusChip( - label = "Built-in", - count = toolStatus.builtinToolsEnabled, - total = toolStatus.builtinToolsTotal, - isLoading = false, - color = MaterialTheme.colorScheme.primary, - tooltip = "Core tools: read-file, write-file, grep, glob, shell" - ) - - // SubAgents Status ToolStatusChip( label = "SubAgents", count = toolStatus.subAgentsEnabled, @@ -329,7 +387,6 @@ private fun ToolLoadingStatusBar( tooltip = "AI agents: error-recovery, log-summary, codebase-investigator" ) - // MCP Tools Status (async) ToolStatusChip( label = "MCP Tools", count = toolStatus.mcpToolsEnabled, diff --git a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentViewModel.kt b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentViewModel.kt index 7ba00e51d2..5b8c0eb493 100644 --- a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentViewModel.kt +++ b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentViewModel.kt @@ -30,7 +30,6 @@ class CodingAgentViewModel( val renderer = ComposeRenderer() - // Current agent type var currentAgentType by mutableStateOf(AgentType.CODING) private set @@ -42,24 +41,13 @@ class CodingAgentViewModel( private set private var currentExecutionJob: Job? = null - // MCP preloading state var mcpPreloadingStatus by mutableStateOf(PreloadingStatus(false, emptyList(), 0)) private set var mcpPreloadingMessage by mutableStateOf("") private set - // TreeView state var isTreeViewVisible by mutableStateOf(false) - fun toggleTreeView() { - isTreeViewVisible = !isTreeViewVisible - } - - fun closeTreeView() { - isTreeViewVisible = false - } - - // Cached tool configuration for UI display private var cachedToolConfig: cc.unitmesh.agent.config.ToolConfigFile? = null init { @@ -166,7 +154,6 @@ class CodingAgentViewModel( return } - // Check if LLM service is configured if (!isConfigured()) { renderer.addUserMessage(task) renderer.renderError("WARNING: LLM model is not configured. Please configure your model to continue.") @@ -216,27 +203,29 @@ class CodingAgentViewModel( try { // Get timeline snapshot with metadata from renderer val timelineMessages = renderer.getTimelineSnapshot() - + // Get existing messages count to avoid duplicates val existingMessagesCount = manager.getMessages().size - + // Only save new messages val newMessages = timelineMessages.drop(existingMessagesCount) - + newMessages.forEach { message -> when (message.role) { MessageRole.USER -> { // Save user message with metadata manager.getCurrentSession().messages.add(message) } + MessageRole.ASSISTANT -> { // Save assistant message with metadata manager.getCurrentSession().messages.add(message) } + else -> {} // Ignore SYSTEM messages } } - + // Trigger save to disk if (newMessages.isNotEmpty()) { manager.getCurrentSession().updatedAt = kotlinx.datetime.Clock.System.now().toEpochMilliseconds() @@ -423,27 +412,8 @@ class CodingAgentViewModel( fun getToolLoadingStatus(): ToolLoadingStatus { val toolConfig = cachedToolConfig - - val allBuiltinTools = ToolType.ALL_TOOLS.filter { it.category != ToolCategory.SubAgent } - val builtinToolsEnabled = - if (toolConfig != null) { - allBuiltinTools.count { toolType -> - toolType.name in toolConfig.enabledBuiltinTools - } - } else { - allBuiltinTools.size - } - val subAgentTools = ToolType.byCategory(ToolCategory.SubAgent) - val subAgentsEnabled = - if (toolConfig != null) { - subAgentTools.count { toolType -> - toolType.name in toolConfig.enabledBuiltinTools - } - } else { - subAgentTools.size - } - + val subAgentsEnabled = subAgentTools.size // All sub-agents are always enabled val mcpServersTotal = toolConfig?.mcpServers?.filter { !it.value.disabled }?.size ?: 0 val mcpServersLoaded = mcpPreloadingStatus.preloadedServers.size @@ -467,8 +437,6 @@ class CodingAgentViewModel( } return ToolLoadingStatus( - builtinToolsEnabled = builtinToolsEnabled, - builtinToolsTotal = allBuiltinTools.size, subAgentsEnabled = subAgentsEnabled, subAgentsTotal = subAgentTools.size, mcpServersLoaded = mcpServersLoaded, @@ -484,8 +452,6 @@ class CodingAgentViewModel( * Data class to hold tool loading status information */ data class ToolLoadingStatus( - val builtinToolsEnabled: Int = 0, - val builtinToolsTotal: Int = 0, val subAgentsEnabled: Int = 0, val subAgentsTotal: Int = 0, val mcpServersLoaded: Int = 0, diff --git a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt index c3576624fb..2b824cc3a0 100644 --- a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt +++ b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt @@ -65,6 +65,10 @@ class ComposeRenderer : BaseRenderer() { private var _currentViewingFile by mutableStateOf(null) val currentViewingFile: String? get() = _currentViewingFile + // Task tracking from task-boundary tool + private val _tasks = mutableStateListOf() + val tasks: List = _tasks + // Timeline data structures for chronological rendering sealed class TimelineItem(val timestamp: Long = Clock.System.now().toEpochMilliseconds()) { data class MessageItem( @@ -223,6 +227,11 @@ class ComposeRenderer : BaseRenderer() { val params = parseParamsString(paramsStr) val toolType = toolName.toToolType() + // Handle task-boundary tool - update task list + if (toolName == "task-boundary") { + updateTaskFromToolCall(params) + } + // Extract file path for read/write operations val filePath = when (toolType) { @@ -255,6 +264,44 @@ class ComposeRenderer : BaseRenderer() { ) } + /** + * Update task list from task-boundary tool call + */ + private fun updateTaskFromToolCall(params: Map) { + val taskName = params["taskName"] ?: return + val statusStr = params["status"] ?: "WORKING" + val summary = params["summary"] ?: "" + val status = TaskStatus.fromString(statusStr) + + // Find existing task or create new one + val existingIndex = _tasks.indexOfFirst { it.taskName == taskName } + + if (existingIndex >= 0) { + // Update existing task + val existingTask = _tasks[existingIndex] + _tasks[existingIndex] = existingTask.copy( + status = status, + summary = summary, + timestamp = Clock.System.now().toEpochMilliseconds() + ) + } else { + // Add new task + _tasks.add( + TaskInfo( + taskName = taskName, + status = status, + summary = summary + ) + ) + } + + // Remove completed or cancelled tasks after a delay (keep them visible briefly) + if (status == TaskStatus.COMPLETED || status == TaskStatus.CANCELLED) { + // Keep completed tasks visible for review + // You could add auto-removal logic here if desired + } + } + override fun renderToolResult( toolName: String, success: Boolean, diff --git a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/TaskPanel.kt b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/TaskPanel.kt new file mode 100644 index 0000000000..cde787a9d7 --- /dev/null +++ b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/TaskPanel.kt @@ -0,0 +1,264 @@ +package cc.unitmesh.devins.ui.compose.agent + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.* +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import cc.unitmesh.devins.ui.compose.theme.AutoDevColors +import kotlinx.datetime.Clock + +/** + * Task information from task-boundary tool + */ +data class TaskInfo( + val taskName: String, + val status: TaskStatus, + val summary: String = "", + val timestamp: Long = Clock.System.now().toEpochMilliseconds(), + val startTime: Long = Clock.System.now().toEpochMilliseconds() +) + +enum class TaskStatus(val displayName: String, val icon: @Composable () -> Unit, val color: Color) { + PLANNING("Planning", { Icon(Icons.Default.Create, null) }, Color(0xFF9C27B0)), + WORKING("Working", { Icon(Icons.Default.Build, null) }, Color(0xFF2196F3)), + COMPLETED("Completed", { Icon(Icons.Default.CheckCircle, null) }, Color(0xFF4CAF50)), + BLOCKED("Blocked", { Icon(Icons.Default.Warning, null) }, Color(0xFFFF9800)), + CANCELLED("Cancelled", { Icon(Icons.Default.Cancel, null) }, Color(0xFF9E9E9E)); + + companion object { + fun fromString(status: String): TaskStatus { + return entries.find { it.name.equals(status, ignoreCase = true) } ?: WORKING + } + } +} + +/** + * Task Panel Component - displays active tasks from task-boundary tool + */ +@Composable +fun TaskPanel( + tasks: List, + modifier: Modifier = Modifier, + onClose: () -> Unit = {} +) { + Card( + modifier = modifier, + shape = RoundedCornerShape(12.dp), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surface) + ) { + // Header + Surface( + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + Icons.Default.List, + contentDescription = "Tasks", + tint = MaterialTheme.colorScheme.primary + ) + Text( + "Tasks", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + if (tasks.isNotEmpty()) { + Badge( + containerColor = MaterialTheme.colorScheme.primaryContainer + ) { + Text(tasks.size.toString()) + } + } + } + + IconButton(onClick = onClose, modifier = Modifier.size(24.dp)) { + Icon( + Icons.Default.Close, + contentDescription = "Close", + modifier = Modifier.size(18.dp) + ) + } + } + } + + Divider() + + // Task List + if (tasks.isEmpty()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Icon( + Icons.Default.CheckCircle, + contentDescription = null, + modifier = Modifier.size(48.dp), + tint = MaterialTheme.colorScheme.outline + ) + Text( + "No active tasks", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } else { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + items(tasks, key = { "${it.taskName}_${it.timestamp}" }) { task -> + TaskCard(task = task) + } + } + } + } + } +} + +@Composable +private fun TaskCard(task: TaskInfo, modifier: Modifier = Modifier) { + val infiniteTransition = rememberInfiniteTransition() + val angle by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 360f, + animationSpec = infiniteRepeatable( + animation = tween(2000, easing = LinearEasing), + repeatMode = RepeatMode.Restart + ) + ) + + Card( + modifier = modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = task.status.color.copy(alpha = 0.08f) + ) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + // Task Header + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + // Status Badge + Surface( + shape = RoundedCornerShape(12.dp), + color = task.status.color.copy(alpha = 0.15f) + ) { + Row( + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier.size(16.dp), + contentAlignment = Alignment.Center + ) { + if (task.status == TaskStatus.WORKING) { + Icon( + Icons.Default.Refresh, + contentDescription = null, + modifier = Modifier + .size(14.dp) + .rotate(angle), + tint = task.status.color + ) + } else { + Box( + modifier = Modifier + .size(14.dp) + .background(task.status.color, CircleShape) + ) + } + } + Text( + task.status.displayName, + style = MaterialTheme.typography.labelSmall, + color = task.status.color, + fontWeight = FontWeight.Medium + ) + } + } + + // Time elapsed + val elapsed = (Clock.System.now().toEpochMilliseconds() - task.startTime) / 1000 + Text( + formatDuration(elapsed), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + // Task Name + Text( + task.taskName, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface + ) + + // Summary + if (task.summary.isNotEmpty()) { + Text( + task.summary, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + lineHeight = MaterialTheme.typography.bodySmall.fontSize.times(1.4) + ) + } + } + } +} + +private fun formatDuration(seconds: Long): String { + return when { + seconds < 60 -> "${seconds}s" + seconds < 3600 -> "${seconds / 60}m ${seconds % 60}s" + else -> "${seconds / 3600}h ${(seconds % 3600) / 60}m" + } +} + diff --git a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/config/ToolConfigDialog.kt b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/config/ToolConfigDialog.kt index d36f1c713c..68c99c6e5b 100644 --- a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/config/ToolConfigDialog.kt +++ b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/config/ToolConfigDialog.kt @@ -21,14 +21,9 @@ import cc.unitmesh.agent.config.McpServerLoadingStatus import cc.unitmesh.agent.config.McpServerState import cc.unitmesh.agent.config.McpToolConfigManager import cc.unitmesh.agent.config.ToolConfigFile -import cc.unitmesh.agent.config.ToolConfigManager import cc.unitmesh.agent.config.ToolItem import cc.unitmesh.agent.mcp.McpServerConfig import cc.unitmesh.agent.tool.schema.ToolCategory -import cc.unitmesh.agent.tool.filesystem.DefaultToolFileSystem -import cc.unitmesh.agent.tool.registry.BuiltinToolsProvider -import cc.unitmesh.agent.tool.registry.ToolDependencies -import cc.unitmesh.agent.tool.shell.DefaultShellExecutor import cc.unitmesh.devins.ui.compose.icons.AutoDevComposeIcons import cc.unitmesh.devins.ui.config.ConfigManager import cc.unitmesh.llm.KoogLLMService @@ -41,7 +36,7 @@ fun ToolConfigDialog( llmService: KoogLLMService? = null ) { var toolConfig by remember { mutableStateOf(ToolConfigFile.default()) } - var builtinToolsByCategory by remember { mutableStateOf>>(emptyMap()) } + // Built-in tools are now always enabled and not configurable via UI var mcpTools by remember { mutableStateOf>>(emptyMap()) } var mcpLoadingState by remember { mutableStateOf(McpLoadingState()) } var isLoading by remember { mutableStateOf(true) } @@ -58,16 +53,10 @@ fun ToolConfigDialog( fun scheduleAutoSave() { hasUnsavedChanges = true autoSaveJob?.cancel() - autoSaveJob = + autoSaveJob = scope.launch { kotlinx.coroutines.delay(2000) // Wait 2 seconds before auto-saving try { - val enabledBuiltinTools = - builtinToolsByCategory.values - .flatten() - .filter { it.enabled } - .map { it.name } - val enabledMcpTools = mcpTools.values .flatten() @@ -79,7 +68,6 @@ fun ToolConfigDialog( val newMcpServers = result.getOrThrow() val updatedConfig = toolConfig.copy( - enabledBuiltinTools = enabledBuiltinTools, enabledMcpTools = enabledMcpTools, mcpServers = newMcpServers ) @@ -99,18 +87,7 @@ fun ToolConfigDialog( scope.launch { try { toolConfig = ConfigManager.loadToolConfig() - val provider = BuiltinToolsProvider() - val tools = - provider.provide( - ToolDependencies( - fileSystem = DefaultToolFileSystem(), - shellExecutor = DefaultShellExecutor(), - subAgentManager = null, - llmService = llmService - ) - ) - val allTools = ToolConfigManager.getBuiltinToolsByCategory(tools) - builtinToolsByCategory = ToolConfigManager.applyEnabledTools(allTools, toolConfig) + // Built-in tools are always enabled - no need to load them for configuration mcpConfigJson = serializeMcpConfig(toolConfig.mcpServers) @@ -282,22 +259,8 @@ fun ToolConfigDialog( when (selectedTab) { 0 -> ToolSelectionTab( - builtinToolsByCategory = builtinToolsByCategory, mcpTools = mcpTools, mcpLoadingState = mcpLoadingState, - onBuiltinToolToggle = { category, toolName, enabled -> - builtinToolsByCategory = - builtinToolsByCategory.mapValues { (cat, toolsList) -> - if (cat == category) { - toolsList.map { - if (it.name == toolName) it.copy(enabled = enabled) else it - } - } else { - toolsList - } - } - scheduleAutoSave() - }, onMcpToolToggle = { toolName, enabled -> mcpTools = mcpTools.mapValues { (_, tools) -> @@ -345,7 +308,7 @@ fun ToolConfigDialog( toolConfig = updatedConfig try { - ToolConfigManager.discoverMcpTools( + McpToolConfigManager.discoverMcpTools( newMcpServers, toolConfig.enabledMcpTools.toSet() ) @@ -375,14 +338,12 @@ fun ToolConfigDialog( verticalAlignment = Alignment.CenterVertically ) { // Summary - val enabledBuiltin = builtinToolsByCategory.values.flatten().count { it.enabled } - val totalBuiltin = builtinToolsByCategory.values.flatten().size val enabledMcp = mcpTools.values.flatten().count { it.enabled } val totalMcp = mcpTools.values.flatten().size Column(modifier = Modifier.weight(1f)) { Text( - text = "Built-in: $enabledBuiltin/$totalBuiltin | MCP: $enabledMcp/$totalMcp", + text = "MCP Tools: $enabledMcp/$totalMcp enabled | Built-in tools: Always enabled", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -414,12 +375,6 @@ fun ToolConfigDialog( try { autoSaveJob?.cancel() - val enabledBuiltinTools = - builtinToolsByCategory.values - .flatten() - .filter { it.enabled } - .map { it.name } - val enabledMcpTools = mcpTools.values .flatten() @@ -438,7 +393,6 @@ fun ToolConfigDialog( val updatedConfig = toolConfig.copy( - enabledBuiltinTools = enabledBuiltinTools, enabledMcpTools = enabledMcpTools, mcpServers = newMcpServers ) @@ -463,10 +417,8 @@ fun ToolConfigDialog( @Composable private fun ToolSelectionTab( - builtinToolsByCategory: Map>, mcpTools: Map>, mcpLoadingState: McpLoadingState, - onBuiltinToolToggle: (ToolCategory, String, Boolean) -> Unit, onMcpToolToggle: (String, Boolean) -> Unit ) { val expandedCategories = remember { mutableStateMapOf() } @@ -475,32 +427,37 @@ private fun ToolSelectionTab( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(vertical = 4.dp) ) { - // Built-in tools by category (collapsible) - builtinToolsByCategory.forEach { (category, tools) -> - val categoryKey = category.name - val isExpanded = expandedCategories.getOrPut(categoryKey) { true } - - item { - CollapsibleCategoryHeader( - category = category, - icon = getCategoryIcon(category), - isExpanded = isExpanded, - toolCount = tools.size, - enabledCount = tools.count { it.enabled }, - onToggle = { - expandedCategories[categoryKey] = !isExpanded - } - ) - } - - if (isExpanded) { - items(tools) { tool -> - CompactToolItemRow( - tool = tool, - onToggle = { enabled -> - onBuiltinToolToggle(category, tool.name, enabled) - } + // Info banner about built-in tools + item { + Surface( + color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f), + shape = RoundedCornerShape(8.dp), + modifier = Modifier.padding(bottom = 12.dp) + ) { + Row( + modifier = Modifier.padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + AutoDevComposeIcons.Info, + contentDescription = "Info", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp) ) + Column { + Text( + text = "Built-in Tools Always Enabled", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = "File operations, search, shell, and other essential tools are always available", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } } } } diff --git a/mpp-ui/src/jsMain/typescript/index.tsx b/mpp-ui/src/jsMain/typescript/index.tsx index f5a99ac0fe..d37586a66e 100644 --- a/mpp-ui/src/jsMain/typescript/index.tsx +++ b/mpp-ui/src/jsMain/typescript/index.tsx @@ -86,7 +86,7 @@ async function runCodingAgent(projectPath: string, task: string, quiet: boolean // Debug: Log tool config details if (!quiet) { console.log('🔍 Debug: Tool config loaded'); - console.log(' Enabled builtin tools:', toolConfig.enabledBuiltinTools.length); + console.log(' Built-in tools: Always enabled (all)'); console.log(' Enabled MCP tools:', toolConfig.enabledMcpTools.length); console.log(' MCP servers in tool config:', Object.keys(toolConfig.mcpServers || {}).length); } @@ -113,7 +113,7 @@ async function runCodingAgent(projectPath: string, task: string, quiet: boolean console.log(`\n🚀 AutoDev Coding Agent`); console.log(`📦 Provider: ${activeConfig.provider}`); console.log(`🤖 Model: ${activeConfig.model}`); - console.log(`🔧 Enabled builtin tools: ${toolConfig.enabledBuiltinTools.length}`); + console.log(`🔧 Built-in tools: Always enabled (all)`); console.log(`🔌 Enabled MCP tools: ${toolConfig.enabledMcpTools.length}`); if (Object.keys(enabledMcpServers).length > 0) { console.log(`🔌 MCP Servers: ${Object.keys(enabledMcpServers).join(', ')}`);