-
Notifications
You must be signed in to change notification settings - Fork 1
feat(mpp-core): implement Task Management Tool for CodingAgent #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
29e1e18
24c8bd2
f60a05f
47e7dd8
067802c
d572bba
2f9aa86
495cb96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,15 +36,48 @@ All tools use the DevIns format with JSON parameters: | |
| ``` | ||
| </devin> | ||
|
|
||
| # Planning and Task Management | ||
|
|
||
| For complex multi-step tasks, use the `/plan` tool to create and track progress: | ||
|
|
||
| ## When to Use Planning | ||
| - Tasks requiring multiple files to be created or modified | ||
| - Tasks with dependencies between steps | ||
| - Tasks that benefit from structured tracking | ||
|
|
||
| ## Plan Format | ||
| ```markdown | ||
| 1. Task Title | ||
| - [ ] Step 1 description | ||
| - [ ] Step 2 description | ||
|
|
||
| 2. Another Task | ||
| - [ ] Step description | ||
| ``` | ||
|
|
||
| ## Plan Actions | ||
| - `CREATE`: Create a new plan with markdown content | ||
| - `COMPLETE_STEP`: Mark a step as done (taskIndex=1, stepIndex=1 for first step of first task) | ||
| - `VIEW`: View current plan status | ||
|
|
||
| Example: | ||
| <devin> | ||
| /plan | ||
| ```json | ||
| {"action": "CREATE", "planMarkdown": "1. Setup\n - [ ] Create entity class\n - [ ] Create repository\n\n2. Implementation\n - [ ] Create service\n - [ ] Create controller"} | ||
| ``` | ||
| </devin> | ||
|
|
||
|
Comment on lines
+58
to
+70
|
||
| # Task Completion Strategy | ||
|
|
||
| **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 | ||
| 2. **Plan if Complex**: For multi-step tasks, create a plan first using `/plan` | ||
| 3. **Gather Minimum Required Information**: Only collect information directly needed for the task | ||
| 4. **Execute the Task**: Make the necessary changes, marking steps complete as you go | ||
| 5. **Verify if Needed**: For code changes, compile/test to verify | ||
| 6. **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. | ||
|
|
||
|
|
@@ -161,15 +194,48 @@ ${'$'}{toolList} | |
| ``` | ||
| </devin> | ||
|
|
||
| # 计划和任务管理 | ||
|
|
||
| 对于复杂的多步骤任务,使用 `/plan` 工具来创建和跟踪进度: | ||
|
|
||
| ## 何时使用计划 | ||
| - 需要创建或修改多个文件的任务 | ||
| - 步骤之间有依赖关系的任务 | ||
| - 需要结构化跟踪的任务 | ||
|
|
||
| ## 计划格式 | ||
| ```markdown | ||
| 1. 任务标题 | ||
| - [ ] 步骤1描述 | ||
| - [ ] 步骤2描述 | ||
|
|
||
| 2. 另一个任务 | ||
| - [ ] 步骤描述 | ||
| ``` | ||
|
|
||
| ## 计划操作 | ||
| - `CREATE`: 使用 markdown 内容创建新计划 | ||
| - `COMPLETE_STEP`: 标记步骤完成 (taskIndex=1, stepIndex=1 表示第一个任务的第一个步骤) | ||
| - `VIEW`: 查看当前计划状态 | ||
|
|
||
| 示例: | ||
| <devin> | ||
| /plan | ||
| ```json | ||
| {"action": "CREATE", "planMarkdown": "1. 设置\n - [ ] 创建实体类\n - [ ] 创建仓库\n\n2. 实现\n - [ ] 创建服务\n - [ ] 创建控制器"} | ||
| ``` | ||
| </devin> | ||
|
|
||
| # 任务完成策略 | ||
|
|
||
| **重要:专注于高效完成任务。** | ||
|
|
||
| 1. **理解任务**:仔细阅读用户的请求 | ||
| 2. **收集最少必要信息**:只收集任务直接需要的信息 | ||
| 3. **执行任务**:进行必要的更改或提供答案 | ||
| 4. **必要时验证**:对于代码更改,编译/测试以验证 | ||
| 5. **提供总结**:始终以清晰的总结结束 | ||
| 2. **复杂任务先计划**:对于多步骤任务,先使用 `/plan` 创建计划 | ||
| 3. **收集最少必要信息**:只收集任务直接需要的信息 | ||
| 4. **执行任务**:进行必要的更改,完成后标记步骤 | ||
| 5. **必要时验证**:对于代码更改,编译/测试以验证 | ||
| 6. **提供总结**:始终以清晰的总结结束 | ||
|
|
||
| **避免过度探索**:不要花费迭代次数探索无关代码。保持专注于任务。 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -382,7 +382,7 @@ class ToolOrchestrator( | |||||||||||||||||||||||||||||||||||||
| else -> { | ||||||||||||||||||||||||||||||||||||||
| // Handle special tools that need parameter conversion | ||||||||||||||||||||||||||||||||||||||
| when (toolName.lowercase()) { | ||||||||||||||||||||||||||||||||||||||
| "task-boundary" -> executeTaskBoundaryTool(tool, params, basicContext) | ||||||||||||||||||||||||||||||||||||||
| "plan" -> executePlanManagementTool(tool, params, basicContext) | ||||||||||||||||||||||||||||||||||||||
| "docql" -> executeDocQLTool(tool, params, basicContext) | ||||||||||||||||||||||||||||||||||||||
| else -> { | ||||||||||||||||||||||||||||||||||||||
| // For truly generic tools, use generic execution | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -675,26 +675,37 @@ class ToolOrchestrator( | |||||||||||||||||||||||||||||||||||||
| return invocation.execute(context) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private suspend fun executeTaskBoundaryTool( | ||||||||||||||||||||||||||||||||||||||
| private suspend fun executePlanManagementTool( | ||||||||||||||||||||||||||||||||||||||
| tool: Tool, | ||||||||||||||||||||||||||||||||||||||
| params: Map<String, Any>, | ||||||||||||||||||||||||||||||||||||||
| 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 planTool = tool as cc.unitmesh.agent.tool.impl.PlanManagementTool | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| val action = params["action"] as? String | ||||||||||||||||||||||||||||||||||||||
| ?: return ToolResult.Error("action parameter is required") | ||||||||||||||||||||||||||||||||||||||
| val planMarkdown = params["planMarkdown"] as? String ?: "" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Handle taskIndex and stepIndex - can be Number or String | ||||||||||||||||||||||||||||||||||||||
| val taskIndex = when (val v = params["taskIndex"]) { | ||||||||||||||||||||||||||||||||||||||
| is Number -> v.toInt() | ||||||||||||||||||||||||||||||||||||||
| is String -> v.toIntOrNull() ?: 0 | ||||||||||||||||||||||||||||||||||||||
| else -> 0 | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| val stepIndex = when (val v = params["stepIndex"]) { | ||||||||||||||||||||||||||||||||||||||
| is Number -> v.toInt() | ||||||||||||||||||||||||||||||||||||||
| is String -> v.toIntOrNull() ?: 0 | ||||||||||||||||||||||||||||||||||||||
| else -> 0 | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+691
to
+698
|
||||||||||||||||||||||||||||||||||||||
| is Number -> v.toInt() | |
| is String -> v.toIntOrNull() ?: 0 | |
| else -> 0 | |
| } | |
| val stepIndex = when (val v = params["stepIndex"]) { | |
| is Number -> v.toInt() | |
| is String -> v.toIntOrNull() ?: 0 | |
| else -> 0 | |
| null -> return ToolResult.Error("taskIndex parameter is required for COMPLETE_STEP and FAIL_STEP") | |
| is Number -> v.toInt() | |
| is String -> v.toIntOrNull() ?: return ToolResult.Error("taskIndex must be a valid integer") | |
| else -> return ToolResult.Error("taskIndex has invalid type") | |
| } | |
| val stepIndex = when (val v = params["stepIndex"]) { | |
| null -> return ToolResult.Error("stepIndex parameter is required for COMPLETE_STEP and FAIL_STEP") | |
| is Number -> v.toInt() | |
| is String -> v.toIntOrNull() ?: return ToolResult.Error("stepIndex must be a valid integer") | |
| else -> return ToolResult.Error("stepIndex has invalid type") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd -t f "PlanManagementTool.kt" --exec cat -n {}Repository: phodal/auto-dev-sketch
Length of output: 10270
Address invalid default indices for step operations.
The taskIndex and stepIndex default to 0, but PlanManagementTool enforces 1-based indexing and validates that both must be positive (line 87 in PlanManagementTool.kt). This causes COMPLETE_STEP and FAIL_STEP actions to fail with error "taskIndex and stepIndex must be positive" whenever indices are not explicitly provided. Consider:
- Making these parameters required for operations that need them (COMPLETE_STEP, FAIL_STEP)
- Or use
nullas default and validate at invocation creation time - Or document that defaults only apply to CREATE/UPDATE/VIEW operations
🤖 Prompt for AI Agents
In
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/orchestrator/ToolOrchestrator.kt
around lines 678 to 710, the current code defaults taskIndex and stepIndex to 0
which breaks PlanManagementTool because it expects 1-based positive indices;
change handling so indices are nullable by default and validate/require them for
actions that need them: parse params["taskIndex"] and params["stepIndex"] into
Int? (null if missing or unparsable), then when building PlanManagementParams
set taskIndex/stepIndex to the nullable values (or call a constructor that
accepts nullable), and before creating the invocation check the action: for
COMPLETE_STEP and FAIL_STEP (and any other actions that require indices) return
ToolResult.Error if the corresponding index is null or <=0; for
CREATE/UPDATE/VIEW keep behavior that treats null as absent. Ensure validation
error messages match PlanManagementTool expectations.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| package cc.unitmesh.agent.plan | ||
|
|
||
| import kotlinx.datetime.Clock | ||
| import kotlinx.serialization.Serializable | ||
|
|
||
| /** | ||
| * Represents a complete agent plan containing multiple tasks. | ||
| * | ||
| * An AgentPlan is the top-level container for organizing work | ||
| * into tasks and steps, with tracking for creation and update times. | ||
| */ | ||
| @Serializable | ||
| data class AgentPlan( | ||
| /** | ||
| * Unique identifier for this plan | ||
| */ | ||
| val id: String, | ||
|
|
||
| /** | ||
| * Tasks in this plan | ||
| */ | ||
| val tasks: MutableList<PlanTask> = mutableListOf(), | ||
|
|
||
| /** | ||
| * Timestamp when this plan was created (epoch milliseconds) | ||
| */ | ||
| val createdAt: Long = Clock.System.now().toEpochMilliseconds(), | ||
|
|
||
| /** | ||
| * Timestamp when this plan was last updated (epoch milliseconds) | ||
| */ | ||
| var updatedAt: Long = createdAt | ||
| ) { | ||
| /** | ||
| * Overall status of the plan (derived from tasks) | ||
| */ | ||
| val status: TaskStatus | ||
| get() = when { | ||
| tasks.isEmpty() -> TaskStatus.TODO | ||
| tasks.all { it.status == TaskStatus.COMPLETED } -> TaskStatus.COMPLETED | ||
| tasks.any { it.status == TaskStatus.FAILED } -> TaskStatus.FAILED | ||
| tasks.any { it.status == TaskStatus.IN_PROGRESS } -> TaskStatus.IN_PROGRESS | ||
| tasks.any { it.status == TaskStatus.BLOCKED } -> TaskStatus.BLOCKED | ||
| else -> TaskStatus.TODO | ||
| } | ||
|
|
||
| /** | ||
| * Overall progress percentage (0-100) | ||
| */ | ||
| val progressPercent: Int | ||
| get() { | ||
| val totalSteps = tasks.sumOf { it.totalStepCount } | ||
| if (totalSteps == 0) return 0 | ||
| val completedSteps = tasks.sumOf { it.completedStepCount } | ||
| return (completedSteps * 100) / totalSteps | ||
| } | ||
|
|
||
| /** | ||
| * Total number of tasks | ||
| */ | ||
| val taskCount: Int | ||
| get() = tasks.size | ||
|
|
||
| /** | ||
| * Number of completed tasks | ||
| */ | ||
| val completedTaskCount: Int | ||
| get() = tasks.count { it.isCompleted } | ||
|
|
||
| /** | ||
| * Add a task to this plan | ||
| */ | ||
| fun addTask(task: PlanTask) { | ||
| tasks.add(task) | ||
| touch() | ||
| } | ||
|
|
||
| /** | ||
| * Get a task by ID | ||
| */ | ||
| fun getTask(taskId: String): PlanTask? { | ||
| return tasks.find { it.id == taskId } | ||
| } | ||
|
|
||
| /** | ||
| * Update a task's status | ||
| */ | ||
| fun updateTaskStatus(taskId: String, status: TaskStatus) { | ||
| getTask(taskId)?.updateStatus(status) | ||
| touch() | ||
| } | ||
|
|
||
| /** | ||
| * Complete a step within a task | ||
| */ | ||
| fun completeStep(taskId: String, stepId: String) { | ||
| getTask(taskId)?.completeStep(stepId) | ||
| touch() | ||
| } | ||
|
|
||
| /** | ||
| * Update the updatedAt timestamp | ||
| */ | ||
| private fun touch() { | ||
| updatedAt = Clock.System.now().toEpochMilliseconds() | ||
| } | ||
|
|
||
| /** | ||
| * Convert to markdown format | ||
| */ | ||
| fun toMarkdown(): String { | ||
| val sb = StringBuilder() | ||
| tasks.forEachIndexed { index, task -> | ||
| sb.append(task.toMarkdown(index + 1)) | ||
| } | ||
| return sb.toString() | ||
| } | ||
|
|
||
| companion object { | ||
| private var idCounter = 0L | ||
|
|
||
| /** | ||
| * Create a new plan with generated ID | ||
| */ | ||
| fun create(tasks: List<PlanTask> = emptyList()): AgentPlan { | ||
| return AgentPlan( | ||
| id = generateId(), | ||
| tasks = tasks.toMutableList() | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Generate a unique plan ID | ||
| */ | ||
| fun generateId(): String { | ||
| return "plan_${++idCounter}_${Clock.System.now().toEpochMilliseconds()}" | ||
| } | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The dependency change from
runtimeOnlytoimplementationforkotlinx-datetimeis correct and necessary since the library is now directly used in the commonMain source set (e.g., inAgentPlan.kt,PlanStep.kt). However, consider whether this should use a version catalog or be consistent with other datetime usages in the project.