Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7c2bef1
feat(mpp-vscode): init vscode extension project structure
phodal Dec 2, 2025
0fd86e4
feat(mpp-vscode): implement core services and IDE integration
phodal Dec 2, 2025
1a1fad2
feat(mpp-vscode): add DevIns language support and status bar
phodal Dec 2, 2025
578308f
feat(mpp-vscode): add React Webview UI for chat interface
phodal Dec 2, 2025
80020cc
refactor(markdown): move MarkdownTextParser to mpp-core
phodal Dec 2, 2025
aad2201
feat(mpp-vscode): add Timeline-based Sketch Renderer architecture
phodal Dec 2, 2025
4f887f9
feat(mpp-vscode): integrate ConfigManager for ~/.autodev/config.yaml
phodal Dec 3, 2025
407cb3d
feat(mpp-vscode): add ModelSelector and unified toolbar layout
phodal Dec 3, 2025
2975520
fix(mpp-vscode): fix VSCode API acquisition for webview communication
phodal Dec 3, 2025
98facf2
fix(mpp-vscode): prevent duplicate user message in timeline
phodal Dec 3, 2025
d13ce4e
fix(mpp-vscode): address PR #32 review comments
phodal Dec 3, 2025
b569934
feat(mpp-vscode): init vscode extension project structure
phodal Dec 2, 2025
876e3a1
feat(mpp-vscode): implement core services and IDE integration
phodal Dec 2, 2025
7d6a9ce
feat(mpp-vscode): add DevIns language support and status bar
phodal Dec 2, 2025
6e78932
feat(mpp-vscode): add React Webview UI for chat interface
phodal Dec 2, 2025
e434dfe
refactor(markdown): move MarkdownTextParser to mpp-core
phodal Dec 2, 2025
8d64f56
feat(mpp-vscode): add Timeline-based Sketch Renderer architecture
phodal Dec 2, 2025
caca9a3
feat(mpp-vscode): integrate ConfigManager for ~/.autodev/config.yaml
phodal Dec 3, 2025
6b56e8a
feat(mpp-vscode): add ModelSelector and unified toolbar layout
phodal Dec 3, 2025
277e397
fix(mpp-vscode): fix VSCode API acquisition for webview communication
phodal Dec 3, 2025
c5bf005
fix(mpp-vscode): prevent duplicate user message in timeline
phodal Dec 3, 2025
2dd0fd8
fix(mpp-vscode): address PR #32 review comments
phodal Dec 3, 2025
27899f3
feat(vscode): integrate mpp-core CompletionManager for auto-completion
phodal Dec 3, 2025
31fb79f
Merge origin/master into feature/mpp-vscode
phodal Dec 3, 2025
40c4593
feat(vscode): Enhanced Chat Input with DevIn Language Support (#34)
phodal Dec 3, 2025
88781b5
feat(mpp-ui): implement file context management with indexed search
phodal Dec 3, 2025
316c277
feat(mpp-core): implement Task Management Tool for CodingAgent (#38)
phodal Dec 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mpp-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ kotlin {
// Kotlin Logging for multiplatform logging
implementation("io.github.oshai:kotlin-logging:7.0.13")

runtimeOnly("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1-0.6.x-compat")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1-0.6.x-compat")
// Koog AI Framework - JVM only for now
implementation("ai.koog:koog-agents:0.5.2")
implementation("ai.koog:agents-mcp:0.5.2")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>

# 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.

Expand Down Expand Up @@ -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. **提供总结**:始终以清晰的总结结束

**避免过度探索**:不要花费迭代次数探索无关代码。保持专注于任务。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import cc.unitmesh.agent.orchestrator.ToolExecutionContext as OrchestratorContex
data class AsyncShellConfig(
/** Initial wait timeout in milliseconds before notifying AI that process is still running */
val initialWaitTimeoutMs: Long = 60_000L, // 1 minute
/** Maximum total wait time in milliseconds */
val maxWaitTimeoutMs: Long = 300_000L, // 5 minutes
/** Maximum total wait time in milliseconds (2 minutes, similar to Cursor/Claude Code) */
val maxWaitTimeoutMs: Long = 120_000L, // 2 minutes
/** Interval for checking process status after initial timeout */
val checkIntervalMs: Long = 30_000L // 30 seconds
)
Expand Down Expand Up @@ -212,7 +212,8 @@ class CodingAgentExecutor(

val executionContext = OrchestratorContext(
workingDirectory = projectPath,
environment = emptyMap()
environment = emptyMap(),
timeout = asyncShellConfig.maxWaitTimeoutMs // Use max timeout for shell commands
)

var executionResult = toolOrchestrator.executeToolCall(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

val planParams = cc.unitmesh.agent.tool.impl.PlanManagementParams(
action = action,
planMarkdown = planMarkdown,
taskIndex = taskIndex,
stepIndex = stepIndex
)
val invocation = taskBoundaryTool.createInvocation(taskBoundaryParams)

val invocation = planTool.createInvocation(planParams)
return invocation.execute(context)
Comment on lines +678 to 709
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stricter validation for taskIndex/stepIndex would avoid mis-targeting steps

In executePlanManagementTool, both indices are derived as:

val taskIndex = when (val v = params["taskIndex"]) {
    is Number -> v.toInt()
    is String -> v.toIntOrNull() ?: 0
    else -> 0
}

This means a bad value like "abc" silently becomes 0, and missing indices are indistinguishable from an explicit 0. For plan operations this could accidentally act on the first task/step instead of failing fast.

Consider:

  • Treating parse failures as an error (ToolResult.Error("taskIndex must be an integer")), and
  • Using a sentinel (e.g. -1) or nullable field in PlanManagementParams to represent “no index provided”, if the tool supports actions that don’t require indices.

This will make plan actions safer and easier to debug when the LLM or caller sends bad parameters.

Also applies to: 383-392

🤖 Prompt for AI Agents
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/orchestrator/ToolOrchestrator.kt
around lines 678-709 (and also adjust same pattern at 383-392): the current
parsing of taskIndex/stepIndex silently maps invalid or missing values to 0
which can accidentally target the first task/step; instead, validate inputs and
fail fast on parse errors and distinguish “not provided” from an explicit 0 — if
params contain a non-null value that is a String attempt to parse it and return
ToolResult.Error("taskIndex must be an integer") or ToolResult.Error("stepIndex
must be an integer") on parse failure, and for absent keys or nulls pass a
sentinel (e.g. -1) or use nullable Ints in PlanManagementParams (update the
PlanManagementParams type accordingly), then construct the invocation with the
corrected/tightened taskIndex/stepIndex values so bad input is rejected rather
than defaulting to 0.

}

Expand All @@ -704,16 +715,16 @@ class ToolOrchestrator(
context: cc.unitmesh.agent.tool.ToolExecutionContext
): ToolResult {
val docqlTool = tool as cc.unitmesh.agent.tool.impl.DocQLTool

val query = params["query"] as? String
?: return ToolResult.Error("query parameter is required")
val documentPath = params["documentPath"] as? String // Optional

val docqlParams = cc.unitmesh.agent.tool.impl.DocQLParams(
query = query,
documentPath = documentPath
)

val invocation = docqlTool.createInvocation(docqlParams)
return invocation.execute(context)
}
Expand All @@ -738,7 +749,7 @@ class ToolOrchestrator(

/**
* Execute generic tool using ExecutableTool interface
* This handles new tools like task-boundary, ask-agent, etc. without needing specific implementations
* This handles new tools like ask-agent, etc. without needing specific implementations
*/
private suspend fun executeGenericTool(
tool: Tool,
Expand Down
140 changes: 140 additions & 0 deletions mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/plan/AgentPlan.kt
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()}"
}
}
}

Loading
Loading