Skip to content

Commit 0a61e78

Browse files
committed
feat(sketch): add AutoSketchModeListener and blocking plan review
- Introduce AutoSketchModeListener for sketch mode completion events. - Refactor PlanReviewAction to support blocking mode for plan review. - Add stop() method to SketchInputListener for future implementation. - Use runWriteAction for thread-safe file updates in SingleFileDiffSketch.
1 parent 32f756c commit 0a61e78

File tree

6 files changed

+122
-33
lines changed

6 files changed

+122
-33
lines changed

core/src/main/kotlin/cc/unitmesh/devti/mcp/host/AutoDevMcpTools.kt

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@ package cc.unitmesh.devti.mcp.host
22

33
import cc.unitmesh.devti.gui.AutoDevToolWindowFactory
44
import cc.unitmesh.devti.gui.chat.message.ChatActionType
5+
import cc.unitmesh.devti.observer.agent.AgentStateService
6+
import cc.unitmesh.devti.observer.plan.reviewPlan
57
import cc.unitmesh.devti.sketch.AutoSketchMode
8+
import cc.unitmesh.devti.sketch.AutoSketchModeListener
9+
import cc.unitmesh.devti.util.parser.CodeFence
10+
import com.intellij.openapi.application.ApplicationManager
611
import com.intellij.openapi.project.Project
712
import kotlinx.serialization.Serializable
813
import com.intellij.openapi.application.runInEdt
14+
import com.intellij.openapi.util.Disposer
15+
import java.util.concurrent.CompletableFuture
916

1017
@Serializable
1118
data class IssueArgs(val issue: String)
@@ -31,6 +38,34 @@ class IssueEvaluateTool : AbstractMcpTool<IssueArgs>() {
3138
}
3239
}
3340

34-
return Response("Start analysis in IDEA")
41+
val hintDisposable = Disposer.newDisposable()
42+
val future = CompletableFuture<String>()
43+
val connection = ApplicationManager.getApplication().messageBus.connect(hintDisposable)
44+
connection.subscribe(AutoSketchModeListener.TOPIC, object : AutoSketchModeListener {
45+
override fun done() {
46+
val messages = project.getService(AgentStateService::class.java).getAllMessages()
47+
var plan: String = ""
48+
messages.lastOrNull()?.content?.also {
49+
plan = CodeFence.parseAll(it).firstOrNull {
50+
it.originLanguage == "plan"
51+
}?.text ?: ""
52+
}
53+
54+
if (plan.isNotEmpty()) {
55+
future.complete(plan)
56+
} else {
57+
val messages = project.getService(AgentStateService::class.java).getAllMessages()
58+
if (messages.isNotEmpty()) {
59+
val plan = reviewPlan(project, isBlockingMode = true)
60+
future.complete(plan)
61+
} else {
62+
future.completeExceptionally(throw Exception("Failure to analysis"))
63+
}
64+
}
65+
}
66+
})
67+
68+
69+
return Response(future.get())
3570
}
3671
}

core/src/main/kotlin/cc/unitmesh/devti/observer/plan/PlanReviewAction.kt

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import cc.unitmesh.devti.util.parser.CodeFence
1414
import com.intellij.openapi.actionSystem.ActionUpdateThread
1515
import com.intellij.openapi.actionSystem.AnAction
1616
import com.intellij.openapi.actionSystem.AnActionEvent
17+
import com.intellij.openapi.project.Project
1718
import kotlinx.coroutines.launch
1819
import kotlinx.coroutines.runBlocking
1920
import org.intellij.markdown.MarkdownElementTypes
@@ -29,48 +30,73 @@ class PlanReviewAction : AnAction(AutoDevBundle.message("sketch.plan.review"), n
2930

3031
override fun actionPerformed(anActionEvent: AnActionEvent) {
3132
val project = anActionEvent.project ?: return
32-
val agentStateService = project.getService(AgentStateService::class.java)
33+
reviewPlan(project)
34+
}
35+
}
3336

34-
val currentPlan = agentStateService.getPlan()
35-
val plan = MarkdownPlanParser.formatPlanToMarkdown(currentPlan)
37+
fun reviewPlan(project: Project, isBlockingMode: Boolean = false): String {
38+
val agentStateService = project.getService(AgentStateService::class.java)
3639

37-
val allMessages = agentStateService.getAllMessages()
38-
val withoutCodeMsgs = allMessages.map {
39-
it.copy(role = it.role, content = removeAllMarkdownCode(it.content))
40-
}
40+
val currentPlan = agentStateService.getPlan()
41+
val plan = MarkdownPlanParser.formatPlanToMarkdown(currentPlan)
4142

42-
val templateRender = TemplateRender(GENIUS_CODE)
43-
val systemPrompt = templateRender.getTemplate("plan-reviewer.vm")
44-
val history = withoutCodeMsgs.joinToString {
45-
"# Role ${it.role}\nMessage:\n${it.content}"
46-
} + "\nLastPlan: \n$plan\n"
43+
val allMessages = agentStateService.getAllMessages()
44+
val withoutCodeMsgs = allMessages.map {
45+
it.copy(role = it.role, content = removeAllMarkdownCode(it.content))
46+
}
4747

48+
val templateRender = TemplateRender(GENIUS_CODE)
49+
val systemPrompt = templateRender.getTemplate("plan-reviewer.vm")
50+
val history = withoutCodeMsgs.joinToString {
51+
"# Role ${it.role}\nMessage:\n${it.content}"
52+
} + "\nLastPlan: \n$plan\n"
4853

49-
val stream = LlmFactory.create(project).stream(history, systemPrompt)
50-
AutoDevCoroutineScope.scope(project).launch {
51-
AutoDevNotifications.notify(project, AutoDevBundle.message("sketch.plan.reviewing"))
52-
AutoDevStatusService.notifyApplication(AutoDevStatus.InProgress, "review the plan")
54+
val stream = LlmFactory.create(project).stream(history, systemPrompt)
55+
val llmResult = StringBuilder()
56+
var planText = ""
5357

54-
val llmResult = StringBuilder()
55-
runBlocking {
56-
stream.collect {
57-
llmResult.append(it)
58-
}
58+
if (isBlockingMode) {
59+
runBlocking {
60+
stream.collect {
61+
llmResult.append(it)
5962
}
63+
}
6064

61-
val result = llmResult.toString()
62-
AutoDevNotifications.notify(project, result)
63-
AutoDevStatusService.notifyApplication(AutoDevStatus.Done, "review the plan")
65+
val result = llmResult.toString()
66+
val plan = CodeFence.parseAll(result).firstOrNull {
67+
it.originLanguage == "plan"
68+
}
6469

65-
val plan = CodeFence.parseAll(result).firstOrNull {
66-
it.originLanguage == "plan"
67-
}
70+
return plan?.text ?: result
71+
}
72+
73+
AutoDevCoroutineScope.scope(project).launch {
74+
AutoDevNotifications.notify(project, AutoDevBundle.message("sketch.plan.reviewing"))
75+
AutoDevStatusService.notifyApplication(AutoDevStatus.InProgress, "review the plan")
6876

69-
if (plan !== null) {
70-
agentStateService.updatePlan(plan.text)
77+
runBlocking {
78+
stream.collect {
79+
llmResult.append(it)
7180
}
7281
}
82+
83+
val result = llmResult.toString()
84+
AutoDevNotifications.notify(project, result)
85+
AutoDevStatusService.notifyApplication(AutoDevStatus.Done, "review the plan")
86+
87+
val plan = CodeFence.parseAll(result).firstOrNull {
88+
it.originLanguage == "plan"
89+
}
90+
91+
if (plan !== null) {
92+
planText = plan.text
93+
agentStateService.updatePlan(plan.text)
94+
} else {
95+
planText = result
96+
}
7397
}
98+
99+
return planText
74100
}
75101

76102
/**

core/src/main/kotlin/cc/unitmesh/devti/sketch/AutoSketchMode.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import cc.unitmesh.devti.observer.agent.AgentStateService
55
import cc.unitmesh.devti.provider.devins.LanguageProcessor
66
import cc.unitmesh.devti.provider.toolchain.ToolchainFunctionProvider
77
import cc.unitmesh.devti.util.parser.CodeFence
8+
import com.intellij.openapi.application.ApplicationManager
89
import com.intellij.openapi.application.invokeLater
910
import com.intellij.openapi.components.Service
1011
import com.intellij.openapi.components.service
@@ -43,7 +44,12 @@ class AutoSketchMode(val project: Project) {
4344
project.getService(AgentStateService::class.java).addTools(commands)
4445
}
4546

46-
if (allCode.isEmpty()) return
47+
if (allCode.isEmpty()) {
48+
ApplicationManager.getApplication().messageBus
49+
.syncPublisher(AutoSketchModeListener.TOPIC)
50+
.done()
51+
return
52+
}
4753

4854
val allCodeText = allCode.map { it.text }.distinct().joinToString("\n")
4955
if (allCodeText.trim().isEmpty()) {
@@ -73,4 +79,4 @@ class AutoSketchMode(val project: Project) {
7379
return project.service<AutoSketchMode>()
7480
}
7581
}
76-
}
82+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cc.unitmesh.devti.sketch
2+
3+
import com.intellij.util.messages.Topic
4+
import java.util.EventListener
5+
6+
@FunctionalInterface
7+
interface AutoSketchModeListener : EventListener {
8+
fun done()
9+
10+
companion object {
11+
@Topic.AppLevel
12+
val TOPIC: Topic<AutoSketchModeListener> =
13+
Topic(AutoSketchModeListener::class.java, Topic.BroadcastDirection.TO_DIRECT_CHILDREN)
14+
}
15+
}

core/src/main/kotlin/cc/unitmesh/devti/sketch/SketchInputListener.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,8 @@ open class SketchInputListener(
124124
override fun dispose() {
125125
connection.disconnect()
126126
}
127+
128+
fun stop() {
129+
TODO("Not yet implemented")
130+
}
127131
}

core/src/main/kotlin/cc/unitmesh/devti/sketch/ui/patch/SingleFileDiffSketch.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.intellij.lang.Language
1212
import com.intellij.lang.annotation.HighlightSeverity
1313
import com.intellij.openapi.application.ApplicationManager
1414
import com.intellij.openapi.application.runReadAction
15+
import com.intellij.openapi.application.runWriteAction
1516
import com.intellij.openapi.command.CommandProcessor
1617
import com.intellij.openapi.command.WriteCommandAction
1718
import com.intellij.openapi.diagnostic.logger
@@ -156,7 +157,9 @@ class SingleFileDiffSketch(
156157
}
157158

158159
invokeLater {
159-
currentFile.writeText(fixedCode)
160+
runWriteAction {
161+
currentFile.writeText(fixedCode)
162+
}
160163
}
161164

162165
createActionButtons(currentFile, appliedPatch, patch).let { actions ->

0 commit comments

Comments
 (0)