Skip to content

Commit ce48f7c

Browse files
authored
Merge pull request phodal#482 from phodal/master
chore: release new version for VSCode && IDEA
2 parents eb1588a + c1855b4 commit ce48f7c

File tree

15 files changed

+315
-125
lines changed

15 files changed

+315
-125
lines changed

AGENTS.md

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -64,39 +64,17 @@ cd mpp-idea && ../gradlew buildPlugin
6464
- `IdeaAgentViewModelTest` requires IntelliJ Platform Test Framework
6565
- `JewelRendererTest` can run standalone with JUnit 5
6666

67-
**Swing/Compose Z-Index Issues (SwingPanel blocking Compose popups):**
68-
69-
When using `SwingPanel` to embed Swing components (e.g., `EditorTextField`) in Compose, Swing components render on top of Compose popups, causing z-index issues.
70-
71-
**Solution 1: For Popup/Dropdown menus**
72-
1. Enable Jewel's custom popup renderer in `IdeaAgentToolWindowFactory`:
73-
```kotlin
74-
JewelFlags.useCustomPopupRenderer = true
75-
```
76-
2. Use Jewel's `PopupMenu` instead of `androidx.compose.ui.window.Popup`:
77-
```kotlin
78-
PopupMenu(
79-
onDismissRequest = { expanded = false; true },
80-
horizontalAlignment = Alignment.Start
81-
) {
82-
selectableItem(selected = ..., onClick = { ... }) { Text("Item") }
83-
}
84-
```
85-
86-
**Solution 2: For Dialogs**
87-
Use IntelliJ's `DialogWrapper` with `org.jetbrains.jewel.bridge.compose` instead of `androidx.compose.ui.window.Dialog`:
88-
```kotlin
89-
class MyDialogWrapper(project: Project?) : DialogWrapper(project) {
90-
override fun createCenterPanel(): JComponent = compose {
91-
// Compose content here
92-
}
93-
}
94-
```
67+
## VSCode Plugin (mpp-vscode)
68+
69+
`mpp-vscode` is a standalone npm package with `devDependencies` on parent project.
9570

9671
## Release
9772

9873
1. modify version in `gradle.properties`
9974
2. publish cli version: `cd mpp-ui && npm publish:remote`
100-
3. publish Desktop: `git tag compose-vVersion` (same in `gradle.properties`), `git push origin compose-vVersion`
101-
4. draft release in GitHub, run gh cli: `gh release create compose-vVersion --draft`
75+
76+
### Desktop Compose App
77+
78+
1. publish Desktop: `git tag compose-vVersion` (same in `gradle.properties`), `git push origin compose-vVersion`
79+
2. draft release in GitHub, run gh cli: `gh release create compose-vVersion --draft`
10280

README.md

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,58 @@
1717
> 🧙‍AutoDev: The AI-powered coding wizard with multilingual support 🌐, auto code generation 🏗️, and a helpful
1818
> bug-slaying assistant 🐞! Customizable prompts 🎨 and a magic Auto Dev/Testing/Document/Agent feature 🧪 included! 🚀
1919
20-
## AutoDev 3.0 - Multiplatform Plane (Doing)
21-
22-
- Multiplatform Agent: Android/iOS, Web, Desktop, IDEs/VSCode,CLI/TUI
23-
- Web: https://unit-mesh.github.io/auto-dev/
24-
- Built-in Coding Agent
20+
## AutoDev 3.0 - Multiplatform Revolution 🚀
21+
22+
Rebuilt with Kotlin Multiplatform (KMP) to deliver a truly cross-platform AI Coding Agent ecosystem.
23+
24+
**Current Versions**:
25+
- IntelliJ Plugin: `v2.4.6`
26+
- New Intellij Plugin: https://plugins.jetbrains.com/plugin/29223-autodev-experiment
27+
- MPP Modules (Core/UI/Server): `v0.3.4`
28+
- VSCode Extension: `v0.5.x`
29+
30+
### 📦 Core Modules
31+
32+
| Module | Platform Support | Description |
33+
|--------|------------------|-------------|
34+
| **mpp-core** | JVM, JS, WASM, Android, iOS | AI Agent engine, DevIns compiler, tool system, LLM integration, MCP protocol |
35+
| **mpp-codegraph** | JVM, JS | TreeSitter-based code parsing & graph building (8+ languages) |
36+
37+
### 🖥️ Client Applications
38+
39+
| Module | Platform | Status | Description |
40+
|--------|----------|--------|-------------|
41+
| **mpp-idea** | IntelliJ IDEA | ✅ Production | Jewel UI, Agent toolwindow, code review, remote agent |
42+
| **mpp-vscode** | VSCode | ✅ Production | CodeLens, auto test/doc, MCP protocol, Tree-sitter |
43+
| **mpp-ui** (Desktop) | macOS/Windows/Linux | ✅ Production | Compose Multiplatform desktop app |
44+
| **mpp-ui** (CLI) | Terminal (Node.js) | ✅ Production | Terminal UI (React/Ink), local/server mode |
45+
| **mpp-ui** (Android) | Android | 🚧 In Progress | Native Android app |
46+
| **mpp-web** (Web) | Web | 🚧 In Progress | Web app |
47+
| **mpp-ios** | iOS | 🚧 In Progress | Native iOS app (SwiftUI + Compose) |
48+
49+
### ⚙️ Server & Tools
50+
51+
| Module | Platform | Features |
52+
|--------|----------|----------|
53+
| **mpp-server** | JVM (Ktor) | HTTP API, SSE streaming, remote project management |
54+
| **mpp-viewer** | Multiplatform | Universal viewer API (code, Markdown, images, PDF, etc.) |
55+
| **mpp-viewer-web** | JVM, Android, iOS | WebView implementation, Monaco Editor integration |
56+
57+
### 🌟 Key Features
58+
59+
- **Unified Codebase**: Core logic shared across all platforms - write once, run everywhere
60+
- **Native Performance**: Compiled natively for each platform with zero overhead
61+
- **Full AI Agent**: Built-in Coding Agent, tool system, multi-LLM support (OpenAI, Anthropic, Google, DeepSeek, Ollama, etc.)
62+
- **DevIns Language**: Executable AI Agent scripting language
63+
- **MCP Protocol**: Model Context Protocol support for extensible tool ecosystem
64+
- **Code Understanding**: TreeSitter-based multi-language parsing (Java, Kotlin, Python, JS, TS, Go, Rust, C#)
65+
- **Internationalization**: Chinese/English UI support
66+
67+
### 🔗 Links
68+
69+
- **Web Demo**: https://unit-mesh.github.io/auto-dev/
70+
- **VSCode Extension**: [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Phodal.autodev)
71+
- **CLI Tool**: `npm install -g @autodev/cli`
2572

2673
## AutoDev 2.0 - the Cursor Composer in Intellij IDEA
2774

core/src/main/kotlin/cc/unitmesh/devti/llms/custom/CustomSSEProcessor.kt

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ import com.intellij.openapi.components.service
1515
import com.intellij.openapi.diagnostic.logger
1616
import com.intellij.openapi.project.Project
1717
import com.jayway.jsonpath.JsonPath
18-
import io.reactivex.rxjava3.core.BackpressureStrategy
19-
import io.reactivex.rxjava3.core.Flowable
20-
import io.reactivex.rxjava3.core.FlowableEmitter
2118
import kotlinx.coroutines.Dispatchers
19+
import kotlinx.coroutines.channels.ProducerScope
2220
import kotlinx.coroutines.channels.awaitClose
2321
import kotlinx.coroutines.flow.Flow
2422
import kotlinx.coroutines.flow.callbackFlow
@@ -71,26 +69,22 @@ open class CustomSSEProcessor(private val project: Project) {
7169

7270
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
7371
fun streamSSE(call: Call, promptText: String, keepHistory: Boolean = false, messages: MutableList<Message>): Flow<String> {
74-
var emit: FlowableEmitter<SSE>? = null
75-
val sseFlowable = Flowable
76-
.create({ emitter: FlowableEmitter<SSE> ->
77-
emit = emitter.apply { call.enqueue(ResponseBodyCallback(emitter, true)) }
78-
}, BackpressureStrategy.BUFFER)
72+
var producerScope: ProducerScope<SSE>? = null
73+
val sseFlow = callbackFlow {
74+
producerScope = this
75+
call.enqueue(ResponseBodyCallback(this, true))
76+
awaitClose { }
77+
}
7978

8079
try {
8180
var output = ""
8281
var reasonerOutput = ""
8382
return CustomFlowWrapper(callbackFlow {
8483
withContext(Dispatchers.IO) {
85-
sseFlowable
86-
.doOnError {
87-
it.printStackTrace()
88-
trySend(it.message ?: "Error occurs")
89-
close()
90-
}
91-
.blockingForEach { sse ->
84+
try {
85+
sseFlow.collect { sse ->
9286
if (sse.data == "[DONE]") {
93-
return@blockingForEach
87+
return@collect
9488
}
9589

9690
if (responseFormat.isNotEmpty()) {
@@ -148,6 +142,11 @@ open class CustomSSEProcessor(private val project: Project) {
148142
}
149143
}
150144
}
145+
} catch (e: Exception) {
146+
e.printStackTrace()
147+
trySend(e.message ?: "Error occurs")
148+
close()
149+
}
151150

152151
// when stream finished, check if any response parsed succeeded
153152
// if not, notice user check response format
@@ -176,7 +175,7 @@ open class CustomSSEProcessor(private val project: Project) {
176175
close()
177176
}
178177
awaitClose()
179-
}).also { it.cancelCallback { emit?.onComplete() } }
178+
}).also { it.cancelCallback { producerScope?.close() } }
180179
} catch (e: Exception) {
181180
if (hasSuccessRequest) {
182181
logger.info("Failed to stream", e)

core/src/main/kotlin/cc/unitmesh/devti/llms/custom/ResponseBodyCallback.kt

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
package cc.unitmesh.devti.llms.custom
2323

2424
import com.intellij.openapi.diagnostic.logger
25-
import io.reactivex.rxjava3.core.FlowableEmitter
25+
import kotlinx.coroutines.channels.ProducerScope
2626
import okhttp3.Call
2727
import okhttp3.Callback
2828
import okhttp3.Response
@@ -39,10 +39,9 @@ class AutoDevHttpException(error: String, private val statusCode: Int) : Runtime
3939

4040
/**
4141
* Callback to parse Server Sent Events (SSE) from raw InputStream and
42-
* emit the events with io.reactivex.FlowableEmitter to allow streaming of
43-
* SSE.
42+
* emit the events with ProducerScope to allow streaming of SSE.
4443
*/
45-
class ResponseBodyCallback(private val emitter: FlowableEmitter<SSE>, private val emitDone: Boolean) : Callback {
44+
class ResponseBodyCallback(private val emitter: ProducerScope<SSE>, private val emitDone: Boolean) : Callback {
4645
val logger = logger<ResponseBodyCallback>()
4746

4847
override fun onResponse(call: Call, response: Response) {
@@ -59,7 +58,7 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter<SSE>, private va
5958
reader = BufferedReader(InputStreamReader(inputStream, StandardCharsets.UTF_8))
6059
var line: String? = null
6160
var sse: SSE? = null
62-
while (!emitter.isCancelled && reader.readLine().also { line = it } != null) {
61+
while (!emitter.isClosedForSend && reader.readLine().also { line = it } != null) {
6362
sse = when {
6463
line!!.startsWith("data:") -> {
6564
val data = line!!.substring(5).trim { it <= ' ' }
@@ -69,11 +68,11 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter<SSE>, private va
6968
line == "" && sse != null -> {
7069
if (sse.isDone) {
7170
if (emitDone) {
72-
emitter.onNext(sse)
71+
emitter.trySend(sse)
7372
}
7473
break
7574
}
76-
emitter.onNext(sse)
75+
emitter.trySend(sse)
7776
null
7877
}
7978
// starts with event:
@@ -82,8 +81,8 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter<SSE>, private va
8281
val eventName = line!!.substring(6).trim { it <= ' ' }
8382
if (eventName == "ping") {
8483
// skip ping event and data
85-
emitter.onNext(sse ?: SSE(""))
86-
emitter.onNext(sse ?: SSE(""))
84+
emitter.trySend(sse ?: SSE(""))
85+
emitter.trySend(sse ?: SSE(""))
8786
}
8887

8988
null
@@ -108,8 +107,8 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter<SSE>, private va
108107
}
109108

110109
line.startsWith("{") && line.endsWith("}") -> {
111-
emitter.onNext(SSE(line))
112-
emitter.onComplete()
110+
emitter.trySend(SSE(line))
111+
emitter.close()
113112
return
114113
}
115114

@@ -121,7 +120,7 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter<SSE>, private va
121120
}
122121
}
123122

124-
emitter.onComplete()
123+
emitter.close()
125124
} catch (t: Throwable) {
126125
logger<ResponseBodyCallback>().error("Error while reading SSE", t)
127126
logger<ResponseBodyCallback>().error("Request: ${call.request()}")
@@ -138,6 +137,6 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter<SSE>, private va
138137
}
139138

140139
override fun onFailure(call: Call, e: IOException) {
141-
emitter.onError(e)
140+
emitter.close(e)
142141
}
143142
}

core/src/main/kotlin/cc/unitmesh/devti/mcp/editor/McpPreviewEditorProvider.kt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
11
package cc.unitmesh.devti.mcp.editor
22

3-
import com.intellij.openapi.fileEditor.AsyncFileEditorProvider
43
import com.intellij.openapi.fileEditor.FileEditor
54
import com.intellij.openapi.fileEditor.FileEditorPolicy
65
import com.intellij.openapi.fileEditor.WeighedFileEditorProvider
76
import com.intellij.openapi.project.Project
87
import com.intellij.openapi.vfs.VirtualFile
98

10-
class McpPreviewEditorProvider : WeighedFileEditorProvider(), AsyncFileEditorProvider {
9+
class McpPreviewEditorProvider : WeighedFileEditorProvider() {
1110
override fun accept(project: Project, file: VirtualFile) = file.name.contains(".mcp.json")
1211

1312
override fun createEditor(project: Project, virtualFile: VirtualFile): FileEditor {
1413
return McpPreviewEditor(project, virtualFile)
1514
}
1615

17-
override fun createEditorAsync(project: Project, file: VirtualFile): AsyncFileEditorProvider.Builder {
18-
return object : AsyncFileEditorProvider.Builder() {
19-
override fun build(): FileEditor {
20-
return McpPreviewEditor(project, file)
21-
}
22-
}
23-
}
24-
2516
override fun getEditorTypeId(): String = "mcp-preview-editor"
2617

2718
override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.PLACE_AFTER_DEFAULT_EDITOR

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

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.intellij.ide.DataManager
1010
import com.intellij.openapi.actionSystem.ActionManager
1111
import com.intellij.openapi.actionSystem.AnActionEvent
1212
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
13+
import com.intellij.openapi.actionSystem.ex.ActionUtil
1314
import com.intellij.openapi.application.ApplicationManager
1415
import com.intellij.openapi.application.ModalityState
1516
import com.intellij.openapi.application.invokeLater
@@ -20,7 +21,7 @@ import com.intellij.openapi.editor.Document
2021
import com.intellij.openapi.fileEditor.FileDocumentManager
2122
import com.intellij.openapi.fileEditor.FileEditorManager.getInstance
2223
import com.intellij.openapi.module.ModuleManager
23-
import com.intellij.openapi.progress.impl.CoreProgressManager
24+
import com.intellij.openapi.progress.ProgressManager
2425
import com.intellij.openapi.project.Project
2526
import com.intellij.openapi.project.guessProjectDir
2627
import com.intellij.openapi.roots.OrderEnumerator
@@ -611,13 +612,13 @@ class ListAvailableActionsTool : AbstractMcpTool<NoArgs>() {
611612

612613
// Create event and presentation to check if action is enabled
613614
val event = AnActionEvent.createFromAnAction(action, null, "", dataContext)
614-
val presentation = action.templatePresentation.clone()
615-
616-
// Update presentation to check if action is available
617-
action.update(event)
615+
616+
// Use ActionUtil to update the presentation properly instead of calling update directly
617+
ActionUtil.performDumbAwareUpdate(action, event, false)
618618

619619
// Only include actions that have text and are enabled
620-
if (event.presentation.isEnabledAndVisible && !presentation.text.isNullOrBlank()) {
620+
val presentation = event.presentation
621+
if (presentation.isEnabledAndVisible && !presentation.text.isNullOrBlank()) {
621622
"""{"id": "$actionId", "text": "${presentation.text.replace("\"", "\\\"")}"}"""
622623
} else {
623624
null
@@ -652,13 +653,15 @@ class ExecuteActionByIdTool : AbstractMcpTool<ExecuteActionArgs>() {
652653
}
653654

654655
ApplicationManager.getApplication().invokeLater({
656+
val dataContext = DataManager.getInstance().dataContext
655657
val event = AnActionEvent.createFromAnAction(
656658
action,
657659
null,
658660
"",
659-
DataManager.getInstance().dataContext
661+
dataContext
660662
)
661-
action.actionPerformed(event)
663+
// Use ActionUtil to properly invoke the action instead of calling actionPerformed directly
664+
ActionUtil.performActionDumbAwareWithCallbacks(action, event)
662665
}, ModalityState.NON_MODAL)
663666

664667
return Response("ok")
@@ -668,26 +671,29 @@ class ExecuteActionByIdTool : AbstractMcpTool<ExecuteActionArgs>() {
668671
class GetProgressIndicatorsTool : AbstractMcpTool<NoArgs>() {
669672
override val name: String = "get_progress_indicators"
670673
override val description: String = """
671-
Retrieves the status of all running progress indicators in JetBrains IDE editor.
672-
Returns a JSON array of objects containing progress information:
674+
Retrieves the status of current progress indicator in JetBrains IDE editor.
675+
Returns a JSON object containing progress information:
673676
- text: The progress text/description
674677
- fraction: The progress ratio (0.0 to 1.0)
675678
- indeterminate: Whether the progress is indeterminate
676-
Returns an empty array if no progress indicators are running.
679+
Returns empty object if no progress indicator is running.
680+
Note: Only returns the current thread's progress indicator.
677681
""".trimIndent()
678682

679683
override fun handle(project: Project, args: NoArgs): Response {
680-
val runningIndicators = CoreProgressManager.getCurrentIndicators()
684+
val progressManager = ProgressManager.getInstance()
685+
val indicator = progressManager.progressIndicator
681686

682-
val progressInfos = runningIndicators.map { indicator ->
687+
if (indicator != null) {
683688
val text = indicator.text ?: ""
684689
val fraction = if (indicator.isIndeterminate) -1.0 else indicator.fraction
685690
val indeterminate = indicator.isIndeterminate
686691

687-
"""{"text": "${text.replace("\"", "\\\"")}", "fraction": $fraction, "indeterminate": $indeterminate}"""
692+
val progressInfo = """{"text": "${text.replace("\"", "\\\"")}", "fraction": $fraction, "indeterminate": $indeterminate}"""
693+
return Response(progressInfo)
688694
}
689695

690-
return Response(progressInfos.joinToString(",\n", prefix = "[", postfix = "]"))
696+
return Response("{}")
691697
}
692698
}
693699

0 commit comments

Comments
 (0)