Skip to content

Commit 316c277

Browse files
authored
feat(mpp-core): implement Task Management Tool for CodingAgent (#38)
* feat(mpp-ui): implement file context management with indexed search - Add SelectedFileItem, FileChip, TopToolbar, FileSearchPopup components - Add WorkspaceFileSearchProvider with pre-built file index for fast search - Add IndexingState enum for tracking indexing progress - Integrate file context into DevInEditorInput with buildAndSendMessage() - Add Prompt Enhancement button to BottomToolbar - Add AutoAwesome and History icons to AutoDevComposeIcons - Add FileContext to EditorCallbacks for file context submission Closes #35 * feat(mpp-core): add plan management data models and parser for task management - Add TaskStatus enum (TODO, IN_PROGRESS, COMPLETED, FAILED, BLOCKED) and PlanPhase enum (PDCA cycle) - Add CodeFileLink data class for markdown file link extraction - Add PlanStep, PlanTask, AgentPlan data models with serialization support - Add MarkdownPlanParser using pure Kotlin regex (no IntelliJ dependencies) - Add PlanStateService with StateFlow and listener pattern for reactive updates - Add PlanUpdateListener interface for UI notifications - Add comprehensive unit tests for parser and state service - Fix DocQLReturnAllTest missing Pending branch in when expressions Part of #37 * feat(mpp-core): add PlanManagementTool for AI agent task planning - Add PlanManagementTool with CREATE, UPDATE, COMPLETE_STEP, FAIL_STEP, VIEW actions - Add PlanManagementParams, PlanManagementSchema, PlanManagementInvocation - Add comprehensive unit tests for all tool actions - Tool integrates with PlanStateService for state management Part of #37 * feat(mpp-core): register PlanManagementTool in BuiltinToolsProvider - Add PlanManagementTool to BuiltinToolsProvider.provide() - Add executePlanManagementTool method in ToolOrchestrator - Add tests for plan and task-boundary tool registration Part of #37 * feat(mpp-ui): add PlanPanel UI component and integrate with ComposeRenderer - Create PlanPanel composable with task and step display - Add expandable task cards with progress tracking - Implement status icons and colors for plan visualization - Integrate plan state tracking in ComposeRenderer - Handle plan tool calls to update UI state Part of #37 * feat(mpp-core): add plan management guidance to system prompt and fix parameter parsing - Add Planning and Task Management section to CodingAgentTemplate (EN and ZH) - Document when to use planning, plan format, and plan actions - Update Task Completion Strategy to include planning step - Fix taskIndex/stepIndex parameter parsing in ToolOrchestrator to handle both Number and String types Part of #37 * refactor(mpp-core): remove TaskBoundaryTool in favor of PlanManagementTool - Remove TaskBoundaryTool.kt as PlanManagementTool provides superset functionality - Remove TaskBoundaryTool from BuiltinToolsProvider and ToolOrchestrator - Update ToolRegistryTest to remove task-boundary test - Update comments referencing task-boundary - Enhance PlanManagementTool with detailed KDoc documentation - Fix tuiEmoji to use proper emoji character Part of #37 * fix(executor): reduce shell command max timeout to 2 minutes Lower the maximum wait time for shell command execution to 2 minutes and pass it as a timeout to the orchestrator context.
1 parent 88781b5 commit 316c277

File tree

23 files changed

+1979
-244
lines changed

23 files changed

+1979
-244
lines changed

mpp-core/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ kotlin {
135135
// Kotlin Logging for multiplatform logging
136136
implementation("io.github.oshai:kotlin-logging:7.0.13")
137137

138-
runtimeOnly("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1-0.6.x-compat")
138+
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1-0.6.x-compat")
139139
// Koog AI Framework - JVM only for now
140140
implementation("ai.koog:koog-agents:0.5.2")
141141
implementation("ai.koog:agents-mcp:0.5.2")

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

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,48 @@ All tools use the DevIns format with JSON parameters:
3636
```
3737
</devin>
3838
39+
# Planning and Task Management
40+
41+
For complex multi-step tasks, use the `/plan` tool to create and track progress:
42+
43+
## When to Use Planning
44+
- Tasks requiring multiple files to be created or modified
45+
- Tasks with dependencies between steps
46+
- Tasks that benefit from structured tracking
47+
48+
## Plan Format
49+
```markdown
50+
1. Task Title
51+
- [ ] Step 1 description
52+
- [ ] Step 2 description
53+
54+
2. Another Task
55+
- [ ] Step description
56+
```
57+
58+
## Plan Actions
59+
- `CREATE`: Create a new plan with markdown content
60+
- `COMPLETE_STEP`: Mark a step as done (taskIndex=1, stepIndex=1 for first step of first task)
61+
- `VIEW`: View current plan status
62+
63+
Example:
64+
<devin>
65+
/plan
66+
```json
67+
{"action": "CREATE", "planMarkdown": "1. Setup\n - [ ] Create entity class\n - [ ] Create repository\n\n2. Implementation\n - [ ] Create service\n - [ ] Create controller"}
68+
```
69+
</devin>
70+
3971
# Task Completion Strategy
4072
4173
**IMPORTANT: Focus on completing the task efficiently.**
4274
4375
1. **Understand the Task**: Read the user's request carefully
44-
2. **Gather Minimum Required Information**: Only collect information directly needed for the task
45-
3. **Execute the Task**: Make the necessary changes or provide the answer
46-
4. **Verify if Needed**: For code changes, compile/test to verify
47-
5. **Provide Summary**: Always end with a clear summary of what was done
76+
2. **Plan if Complex**: For multi-step tasks, create a plan first using `/plan`
77+
3. **Gather Minimum Required Information**: Only collect information directly needed for the task
78+
4. **Execute the Task**: Make the necessary changes, marking steps complete as you go
79+
5. **Verify if Needed**: For code changes, compile/test to verify
80+
6. **Provide Summary**: Always end with a clear summary of what was done
4881
4982
**Avoid over-exploration**: Don't spend iterations exploring unrelated code. Stay focused on the task.
5083
@@ -161,15 +194,48 @@ ${'$'}{toolList}
161194
```
162195
</devin>
163196
197+
# 计划和任务管理
198+
199+
对于复杂的多步骤任务,使用 `/plan` 工具来创建和跟踪进度:
200+
201+
## 何时使用计划
202+
- 需要创建或修改多个文件的任务
203+
- 步骤之间有依赖关系的任务
204+
- 需要结构化跟踪的任务
205+
206+
## 计划格式
207+
```markdown
208+
1. 任务标题
209+
- [ ] 步骤1描述
210+
- [ ] 步骤2描述
211+
212+
2. 另一个任务
213+
- [ ] 步骤描述
214+
```
215+
216+
## 计划操作
217+
- `CREATE`: 使用 markdown 内容创建新计划
218+
- `COMPLETE_STEP`: 标记步骤完成 (taskIndex=1, stepIndex=1 表示第一个任务的第一个步骤)
219+
- `VIEW`: 查看当前计划状态
220+
221+
示例:
222+
<devin>
223+
/plan
224+
```json
225+
{"action": "CREATE", "planMarkdown": "1. 设置\n - [ ] 创建实体类\n - [ ] 创建仓库\n\n2. 实现\n - [ ] 创建服务\n - [ ] 创建控制器"}
226+
```
227+
</devin>
228+
164229
# 任务完成策略
165230
166231
**重要:专注于高效完成任务。**
167232
168233
1. **理解任务**:仔细阅读用户的请求
169-
2. **收集最少必要信息**:只收集任务直接需要的信息
170-
3. **执行任务**:进行必要的更改或提供答案
171-
4. **必要时验证**:对于代码更改,编译/测试以验证
172-
5. **提供总结**:始终以清晰的总结结束
234+
2. **复杂任务先计划**:对于多步骤任务,先使用 `/plan` 创建计划
235+
3. **收集最少必要信息**:只收集任务直接需要的信息
236+
4. **执行任务**:进行必要的更改,完成后标记步骤
237+
5. **必要时验证**:对于代码更改,编译/测试以验证
238+
6. **提供总结**:始终以清晰的总结结束
173239
174240
**避免过度探索**:不要花费迭代次数探索无关代码。保持专注于任务。
175241

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import cc.unitmesh.agent.orchestrator.ToolExecutionContext as OrchestratorContex
2424
data class AsyncShellConfig(
2525
/** Initial wait timeout in milliseconds before notifying AI that process is still running */
2626
val initialWaitTimeoutMs: Long = 60_000L, // 1 minute
27-
/** Maximum total wait time in milliseconds */
28-
val maxWaitTimeoutMs: Long = 300_000L, // 5 minutes
27+
/** Maximum total wait time in milliseconds (2 minutes, similar to Cursor/Claude Code) */
28+
val maxWaitTimeoutMs: Long = 120_000L, // 2 minutes
2929
/** Interval for checking process status after initial timeout */
3030
val checkIntervalMs: Long = 30_000L // 30 seconds
3131
)
@@ -212,7 +212,8 @@ class CodingAgentExecutor(
212212

213213
val executionContext = OrchestratorContext(
214214
workingDirectory = projectPath,
215-
environment = emptyMap()
215+
environment = emptyMap(),
216+
timeout = asyncShellConfig.maxWaitTimeoutMs // Use max timeout for shell commands
216217
)
217218

218219
var executionResult = toolOrchestrator.executeToolCall(

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

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ class ToolOrchestrator(
382382
else -> {
383383
// Handle special tools that need parameter conversion
384384
when (toolName.lowercase()) {
385-
"task-boundary" -> executeTaskBoundaryTool(tool, params, basicContext)
385+
"plan" -> executePlanManagementTool(tool, params, basicContext)
386386
"docql" -> executeDocQLTool(tool, params, basicContext)
387387
else -> {
388388
// For truly generic tools, use generic execution
@@ -675,26 +675,37 @@ class ToolOrchestrator(
675675
return invocation.execute(context)
676676
}
677677

678-
private suspend fun executeTaskBoundaryTool(
678+
private suspend fun executePlanManagementTool(
679679
tool: Tool,
680680
params: Map<String, Any>,
681681
context: cc.unitmesh.agent.tool.ToolExecutionContext
682682
): ToolResult {
683-
val taskBoundaryTool = tool as cc.unitmesh.agent.tool.impl.TaskBoundaryTool
684-
685-
val taskName = params["taskName"] as? String
686-
?: return ToolResult.Error("taskName parameter is required")
687-
val status = params["status"] as? String
688-
?: return ToolResult.Error("status parameter is required")
689-
val summary = params["summary"] as? String ?: ""
690-
691-
val taskBoundaryParams = cc.unitmesh.agent.tool.impl.TaskBoundaryParams(
692-
taskName = taskName,
693-
status = status,
694-
summary = summary
683+
val planTool = tool as cc.unitmesh.agent.tool.impl.PlanManagementTool
684+
685+
val action = params["action"] as? String
686+
?: return ToolResult.Error("action parameter is required")
687+
val planMarkdown = params["planMarkdown"] as? String ?: ""
688+
689+
// Handle taskIndex and stepIndex - can be Number or String
690+
val taskIndex = when (val v = params["taskIndex"]) {
691+
is Number -> v.toInt()
692+
is String -> v.toIntOrNull() ?: 0
693+
else -> 0
694+
}
695+
val stepIndex = when (val v = params["stepIndex"]) {
696+
is Number -> v.toInt()
697+
is String -> v.toIntOrNull() ?: 0
698+
else -> 0
699+
}
700+
701+
val planParams = cc.unitmesh.agent.tool.impl.PlanManagementParams(
702+
action = action,
703+
planMarkdown = planMarkdown,
704+
taskIndex = taskIndex,
705+
stepIndex = stepIndex
695706
)
696-
697-
val invocation = taskBoundaryTool.createInvocation(taskBoundaryParams)
707+
708+
val invocation = planTool.createInvocation(planParams)
698709
return invocation.execute(context)
699710
}
700711

@@ -704,16 +715,16 @@ class ToolOrchestrator(
704715
context: cc.unitmesh.agent.tool.ToolExecutionContext
705716
): ToolResult {
706717
val docqlTool = tool as cc.unitmesh.agent.tool.impl.DocQLTool
707-
718+
708719
val query = params["query"] as? String
709720
?: return ToolResult.Error("query parameter is required")
710721
val documentPath = params["documentPath"] as? String // Optional
711-
722+
712723
val docqlParams = cc.unitmesh.agent.tool.impl.DocQLParams(
713724
query = query,
714725
documentPath = documentPath
715726
)
716-
727+
717728
val invocation = docqlTool.createInvocation(docqlParams)
718729
return invocation.execute(context)
719730
}
@@ -738,7 +749,7 @@ class ToolOrchestrator(
738749

739750
/**
740751
* Execute generic tool using ExecutableTool interface
741-
* This handles new tools like task-boundary, ask-agent, etc. without needing specific implementations
752+
* This handles new tools like ask-agent, etc. without needing specific implementations
742753
*/
743754
private suspend fun executeGenericTool(
744755
tool: Tool,
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package cc.unitmesh.agent.plan
2+
3+
import kotlinx.datetime.Clock
4+
import kotlinx.serialization.Serializable
5+
6+
/**
7+
* Represents a complete agent plan containing multiple tasks.
8+
*
9+
* An AgentPlan is the top-level container for organizing work
10+
* into tasks and steps, with tracking for creation and update times.
11+
*/
12+
@Serializable
13+
data class AgentPlan(
14+
/**
15+
* Unique identifier for this plan
16+
*/
17+
val id: String,
18+
19+
/**
20+
* Tasks in this plan
21+
*/
22+
val tasks: MutableList<PlanTask> = mutableListOf(),
23+
24+
/**
25+
* Timestamp when this plan was created (epoch milliseconds)
26+
*/
27+
val createdAt: Long = Clock.System.now().toEpochMilliseconds(),
28+
29+
/**
30+
* Timestamp when this plan was last updated (epoch milliseconds)
31+
*/
32+
var updatedAt: Long = createdAt
33+
) {
34+
/**
35+
* Overall status of the plan (derived from tasks)
36+
*/
37+
val status: TaskStatus
38+
get() = when {
39+
tasks.isEmpty() -> TaskStatus.TODO
40+
tasks.all { it.status == TaskStatus.COMPLETED } -> TaskStatus.COMPLETED
41+
tasks.any { it.status == TaskStatus.FAILED } -> TaskStatus.FAILED
42+
tasks.any { it.status == TaskStatus.IN_PROGRESS } -> TaskStatus.IN_PROGRESS
43+
tasks.any { it.status == TaskStatus.BLOCKED } -> TaskStatus.BLOCKED
44+
else -> TaskStatus.TODO
45+
}
46+
47+
/**
48+
* Overall progress percentage (0-100)
49+
*/
50+
val progressPercent: Int
51+
get() {
52+
val totalSteps = tasks.sumOf { it.totalStepCount }
53+
if (totalSteps == 0) return 0
54+
val completedSteps = tasks.sumOf { it.completedStepCount }
55+
return (completedSteps * 100) / totalSteps
56+
}
57+
58+
/**
59+
* Total number of tasks
60+
*/
61+
val taskCount: Int
62+
get() = tasks.size
63+
64+
/**
65+
* Number of completed tasks
66+
*/
67+
val completedTaskCount: Int
68+
get() = tasks.count { it.isCompleted }
69+
70+
/**
71+
* Add a task to this plan
72+
*/
73+
fun addTask(task: PlanTask) {
74+
tasks.add(task)
75+
touch()
76+
}
77+
78+
/**
79+
* Get a task by ID
80+
*/
81+
fun getTask(taskId: String): PlanTask? {
82+
return tasks.find { it.id == taskId }
83+
}
84+
85+
/**
86+
* Update a task's status
87+
*/
88+
fun updateTaskStatus(taskId: String, status: TaskStatus) {
89+
getTask(taskId)?.updateStatus(status)
90+
touch()
91+
}
92+
93+
/**
94+
* Complete a step within a task
95+
*/
96+
fun completeStep(taskId: String, stepId: String) {
97+
getTask(taskId)?.completeStep(stepId)
98+
touch()
99+
}
100+
101+
/**
102+
* Update the updatedAt timestamp
103+
*/
104+
private fun touch() {
105+
updatedAt = Clock.System.now().toEpochMilliseconds()
106+
}
107+
108+
/**
109+
* Convert to markdown format
110+
*/
111+
fun toMarkdown(): String {
112+
val sb = StringBuilder()
113+
tasks.forEachIndexed { index, task ->
114+
sb.append(task.toMarkdown(index + 1))
115+
}
116+
return sb.toString()
117+
}
118+
119+
companion object {
120+
private var idCounter = 0L
121+
122+
/**
123+
* Create a new plan with generated ID
124+
*/
125+
fun create(tasks: List<PlanTask> = emptyList()): AgentPlan {
126+
return AgentPlan(
127+
id = generateId(),
128+
tasks = tasks.toMutableList()
129+
)
130+
}
131+
132+
/**
133+
* Generate a unique plan ID
134+
*/
135+
fun generateId(): String {
136+
return "plan_${++idCounter}_${Clock.System.now().toEpochMilliseconds()}"
137+
}
138+
}
139+
}
140+

0 commit comments

Comments
 (0)