diff --git a/AGENTS.md b/AGENTS.md index 341d469459..d2c047db65 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,39 +64,17 @@ cd mpp-idea && ../gradlew buildPlugin - `IdeaAgentViewModelTest` requires IntelliJ Platform Test Framework - `JewelRendererTest` can run standalone with JUnit 5 -**Swing/Compose Z-Index Issues (SwingPanel blocking Compose popups):** - -When using `SwingPanel` to embed Swing components (e.g., `EditorTextField`) in Compose, Swing components render on top of Compose popups, causing z-index issues. - -**Solution 1: For Popup/Dropdown menus** -1. Enable Jewel's custom popup renderer in `IdeaAgentToolWindowFactory`: - ```kotlin - JewelFlags.useCustomPopupRenderer = true - ``` -2. Use Jewel's `PopupMenu` instead of `androidx.compose.ui.window.Popup`: - ```kotlin - PopupMenu( - onDismissRequest = { expanded = false; true }, - horizontalAlignment = Alignment.Start - ) { - selectableItem(selected = ..., onClick = { ... }) { Text("Item") } - } - ``` - -**Solution 2: For Dialogs** -Use IntelliJ's `DialogWrapper` with `org.jetbrains.jewel.bridge.compose` instead of `androidx.compose.ui.window.Dialog`: -```kotlin -class MyDialogWrapper(project: Project?) : DialogWrapper(project) { - override fun createCenterPanel(): JComponent = compose { - // Compose content here - } -} -``` +## VSCode Plugin (mpp-vscode) + +`mpp-vscode` is a standalone npm package with `devDependencies` on parent project. ## Release 1. modify version in `gradle.properties` 2. publish cli version: `cd mpp-ui && npm publish:remote` -3. publish Desktop: `git tag compose-vVersion` (same in `gradle.properties`), `git push origin compose-vVersion` -4. draft release in GitHub, run gh cli: `gh release create compose-vVersion --draft` + +### Desktop Compose App + +1. publish Desktop: `git tag compose-vVersion` (same in `gradle.properties`), `git push origin compose-vVersion` +2. draft release in GitHub, run gh cli: `gh release create compose-vVersion --draft` diff --git a/README.md b/README.md index d806916b01..16d880cdb9 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,58 @@ > πŸ§™β€AutoDev: The AI-powered coding wizard with multilingual support 🌐, auto code generation πŸ—οΈ, and a helpful > bug-slaying assistant 🐞! Customizable prompts 🎨 and a magic Auto Dev/Testing/Document/Agent feature πŸ§ͺ included! πŸš€ -## AutoDev 3.0 - Multiplatform Plane (Doing) - -- Multiplatform Agent: Android/iOS, Web, Desktop, IDEs/VSCode,CLI/TUI -- Web: https://unit-mesh.github.io/auto-dev/ -- Built-in Coding Agent +## AutoDev 3.0 - Multiplatform Revolution πŸš€ + +Rebuilt with Kotlin Multiplatform (KMP) to deliver a truly cross-platform AI Coding Agent ecosystem. + +**Current Versions**: +- IntelliJ Plugin: `v2.4.6` +- New Intellij Plugin: https://plugins.jetbrains.com/plugin/29223-autodev-experiment +- MPP Modules (Core/UI/Server): `v0.3.4` +- VSCode Extension: `v0.5.x` + +### πŸ“¦ Core Modules + +| Module | Platform Support | Description | +|--------|------------------|-------------| +| **mpp-core** | JVM, JS, WASM, Android, iOS | AI Agent engine, DevIns compiler, tool system, LLM integration, MCP protocol | +| **mpp-codegraph** | JVM, JS | TreeSitter-based code parsing & graph building (8+ languages) | + +### πŸ–₯️ Client Applications + +| Module | Platform | Status | Description | +|--------|----------|--------|-------------| +| **mpp-idea** | IntelliJ IDEA | βœ… Production | Jewel UI, Agent toolwindow, code review, remote agent | +| **mpp-vscode** | VSCode | βœ… Production | CodeLens, auto test/doc, MCP protocol, Tree-sitter | +| **mpp-ui** (Desktop) | macOS/Windows/Linux | βœ… Production | Compose Multiplatform desktop app | +| **mpp-ui** (CLI) | Terminal (Node.js) | βœ… Production | Terminal UI (React/Ink), local/server mode | +| **mpp-ui** (Android) | Android | 🚧 In Progress | Native Android app | +| **mpp-web** (Web) | Web | 🚧 In Progress | Web app | +| **mpp-ios** | iOS | 🚧 In Progress | Native iOS app (SwiftUI + Compose) | + +### βš™οΈ Server & Tools + +| Module | Platform | Features | +|--------|----------|----------| +| **mpp-server** | JVM (Ktor) | HTTP API, SSE streaming, remote project management | +| **mpp-viewer** | Multiplatform | Universal viewer API (code, Markdown, images, PDF, etc.) | +| **mpp-viewer-web** | JVM, Android, iOS | WebView implementation, Monaco Editor integration | + +### 🌟 Key Features + +- **Unified Codebase**: Core logic shared across all platforms - write once, run everywhere +- **Native Performance**: Compiled natively for each platform with zero overhead +- **Full AI Agent**: Built-in Coding Agent, tool system, multi-LLM support (OpenAI, Anthropic, Google, DeepSeek, Ollama, etc.) +- **DevIns Language**: Executable AI Agent scripting language +- **MCP Protocol**: Model Context Protocol support for extensible tool ecosystem +- **Code Understanding**: TreeSitter-based multi-language parsing (Java, Kotlin, Python, JS, TS, Go, Rust, C#) +- **Internationalization**: Chinese/English UI support + +### πŸ”— Links + +- **Web Demo**: https://unit-mesh.github.io/auto-dev/ +- **VSCode Extension**: [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Phodal.autodev) +- **CLI Tool**: `npm install -g @autodev/cli` ## AutoDev 2.0 - the Cursor Composer in Intellij IDEA diff --git a/core/src/main/kotlin/cc/unitmesh/devti/llms/custom/CustomSSEProcessor.kt b/core/src/main/kotlin/cc/unitmesh/devti/llms/custom/CustomSSEProcessor.kt index 5ce2abc8dc..e0b08fa9a4 100644 --- a/core/src/main/kotlin/cc/unitmesh/devti/llms/custom/CustomSSEProcessor.kt +++ b/core/src/main/kotlin/cc/unitmesh/devti/llms/custom/CustomSSEProcessor.kt @@ -15,10 +15,8 @@ import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.jayway.jsonpath.JsonPath -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.FlowableEmitter import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -71,26 +69,22 @@ open class CustomSSEProcessor(private val project: Project) { @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) fun streamSSE(call: Call, promptText: String, keepHistory: Boolean = false, messages: MutableList): Flow { - var emit: FlowableEmitter? = null - val sseFlowable = Flowable - .create({ emitter: FlowableEmitter -> - emit = emitter.apply { call.enqueue(ResponseBodyCallback(emitter, true)) } - }, BackpressureStrategy.BUFFER) + var producerScope: ProducerScope? = null + val sseFlow = callbackFlow { + producerScope = this + call.enqueue(ResponseBodyCallback(this, true)) + awaitClose { } + } try { var output = "" var reasonerOutput = "" return CustomFlowWrapper(callbackFlow { withContext(Dispatchers.IO) { - sseFlowable - .doOnError { - it.printStackTrace() - trySend(it.message ?: "Error occurs") - close() - } - .blockingForEach { sse -> + try { + sseFlow.collect { sse -> if (sse.data == "[DONE]") { - return@blockingForEach + return@collect } if (responseFormat.isNotEmpty()) { @@ -148,6 +142,11 @@ open class CustomSSEProcessor(private val project: Project) { } } } + } catch (e: Exception) { + e.printStackTrace() + trySend(e.message ?: "Error occurs") + close() + } // when stream finished, check if any response parsed succeeded // if not, notice user check response format @@ -176,7 +175,7 @@ open class CustomSSEProcessor(private val project: Project) { close() } awaitClose() - }).also { it.cancelCallback { emit?.onComplete() } } + }).also { it.cancelCallback { producerScope?.close() } } } catch (e: Exception) { if (hasSuccessRequest) { logger.info("Failed to stream", e) diff --git a/core/src/main/kotlin/cc/unitmesh/devti/llms/custom/ResponseBodyCallback.kt b/core/src/main/kotlin/cc/unitmesh/devti/llms/custom/ResponseBodyCallback.kt index c9d840a46d..4da2df85be 100644 --- a/core/src/main/kotlin/cc/unitmesh/devti/llms/custom/ResponseBodyCallback.kt +++ b/core/src/main/kotlin/cc/unitmesh/devti/llms/custom/ResponseBodyCallback.kt @@ -22,7 +22,7 @@ package cc.unitmesh.devti.llms.custom import com.intellij.openapi.diagnostic.logger -import io.reactivex.rxjava3.core.FlowableEmitter +import kotlinx.coroutines.channels.ProducerScope import okhttp3.Call import okhttp3.Callback import okhttp3.Response @@ -39,10 +39,9 @@ class AutoDevHttpException(error: String, private val statusCode: Int) : Runtime /** * Callback to parse Server Sent Events (SSE) from raw InputStream and - * emit the events with io.reactivex.FlowableEmitter to allow streaming of - * SSE. + * emit the events with ProducerScope to allow streaming of SSE. */ -class ResponseBodyCallback(private val emitter: FlowableEmitter, private val emitDone: Boolean) : Callback { +class ResponseBodyCallback(private val emitter: ProducerScope, private val emitDone: Boolean) : Callback { val logger = logger() override fun onResponse(call: Call, response: Response) { @@ -59,7 +58,7 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter, private va reader = BufferedReader(InputStreamReader(inputStream, StandardCharsets.UTF_8)) var line: String? = null var sse: SSE? = null - while (!emitter.isCancelled && reader.readLine().also { line = it } != null) { + while (!emitter.isClosedForSend && reader.readLine().also { line = it } != null) { sse = when { line!!.startsWith("data:") -> { val data = line!!.substring(5).trim { it <= ' ' } @@ -69,11 +68,11 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter, private va line == "" && sse != null -> { if (sse.isDone) { if (emitDone) { - emitter.onNext(sse) + emitter.trySend(sse) } break } - emitter.onNext(sse) + emitter.trySend(sse) null } // starts with event: @@ -82,8 +81,8 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter, private va val eventName = line!!.substring(6).trim { it <= ' ' } if (eventName == "ping") { // skip ping event and data - emitter.onNext(sse ?: SSE("")) - emitter.onNext(sse ?: SSE("")) + emitter.trySend(sse ?: SSE("")) + emitter.trySend(sse ?: SSE("")) } null @@ -108,8 +107,8 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter, private va } line.startsWith("{") && line.endsWith("}") -> { - emitter.onNext(SSE(line)) - emitter.onComplete() + emitter.trySend(SSE(line)) + emitter.close() return } @@ -121,7 +120,7 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter, private va } } - emitter.onComplete() + emitter.close() } catch (t: Throwable) { logger().error("Error while reading SSE", t) logger().error("Request: ${call.request()}") @@ -138,6 +137,6 @@ class ResponseBodyCallback(private val emitter: FlowableEmitter, private va } override fun onFailure(call: Call, e: IOException) { - emitter.onError(e) + emitter.close(e) } } diff --git a/core/src/main/kotlin/cc/unitmesh/devti/mcp/editor/McpPreviewEditorProvider.kt b/core/src/main/kotlin/cc/unitmesh/devti/mcp/editor/McpPreviewEditorProvider.kt index 2f137e8880..d3fb70ba73 100644 --- a/core/src/main/kotlin/cc/unitmesh/devti/mcp/editor/McpPreviewEditorProvider.kt +++ b/core/src/main/kotlin/cc/unitmesh/devti/mcp/editor/McpPreviewEditorProvider.kt @@ -1,27 +1,18 @@ package cc.unitmesh.devti.mcp.editor -import com.intellij.openapi.fileEditor.AsyncFileEditorProvider import com.intellij.openapi.fileEditor.FileEditor import com.intellij.openapi.fileEditor.FileEditorPolicy import com.intellij.openapi.fileEditor.WeighedFileEditorProvider import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -class McpPreviewEditorProvider : WeighedFileEditorProvider(), AsyncFileEditorProvider { +class McpPreviewEditorProvider : WeighedFileEditorProvider() { override fun accept(project: Project, file: VirtualFile) = file.name.contains(".mcp.json") override fun createEditor(project: Project, virtualFile: VirtualFile): FileEditor { return McpPreviewEditor(project, virtualFile) } - override fun createEditorAsync(project: Project, file: VirtualFile): AsyncFileEditorProvider.Builder { - return object : AsyncFileEditorProvider.Builder() { - override fun build(): FileEditor { - return McpPreviewEditor(project, file) - } - } - } - override fun getEditorTypeId(): String = "mcp-preview-editor" override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.PLACE_AFTER_DEFAULT_EDITOR diff --git a/core/src/main/kotlin/cc/unitmesh/devti/mcp/host/BuiltinMcpTools.kt b/core/src/main/kotlin/cc/unitmesh/devti/mcp/host/BuiltinMcpTools.kt index cdabd35ab5..c5287b4be6 100644 --- a/core/src/main/kotlin/cc/unitmesh/devti/mcp/host/BuiltinMcpTools.kt +++ b/core/src/main/kotlin/cc/unitmesh/devti/mcp/host/BuiltinMcpTools.kt @@ -10,6 +10,7 @@ import com.intellij.ide.DataManager import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.ex.ActionManagerEx +import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.invokeLater @@ -20,7 +21,7 @@ import com.intellij.openapi.editor.Document import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManager.getInstance import com.intellij.openapi.module.ModuleManager -import com.intellij.openapi.progress.impl.CoreProgressManager +import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.roots.OrderEnumerator @@ -611,13 +612,13 @@ class ListAvailableActionsTool : AbstractMcpTool() { // Create event and presentation to check if action is enabled val event = AnActionEvent.createFromAnAction(action, null, "", dataContext) - val presentation = action.templatePresentation.clone() - - // Update presentation to check if action is available - action.update(event) + + // Use ActionUtil to update the presentation properly instead of calling update directly + ActionUtil.performDumbAwareUpdate(action, event, false) // Only include actions that have text and are enabled - if (event.presentation.isEnabledAndVisible && !presentation.text.isNullOrBlank()) { + val presentation = event.presentation + if (presentation.isEnabledAndVisible && !presentation.text.isNullOrBlank()) { """{"id": "$actionId", "text": "${presentation.text.replace("\"", "\\\"")}"}""" } else { null @@ -652,13 +653,15 @@ class ExecuteActionByIdTool : AbstractMcpTool() { } ApplicationManager.getApplication().invokeLater({ + val dataContext = DataManager.getInstance().dataContext val event = AnActionEvent.createFromAnAction( action, null, "", - DataManager.getInstance().dataContext + dataContext ) - action.actionPerformed(event) + // Use ActionUtil to properly invoke the action instead of calling actionPerformed directly + ActionUtil.performActionDumbAwareWithCallbacks(action, event) }, ModalityState.NON_MODAL) return Response("ok") @@ -668,26 +671,29 @@ class ExecuteActionByIdTool : AbstractMcpTool() { class GetProgressIndicatorsTool : AbstractMcpTool() { override val name: String = "get_progress_indicators" override val description: String = """ - Retrieves the status of all running progress indicators in JetBrains IDE editor. - Returns a JSON array of objects containing progress information: + Retrieves the status of current progress indicator in JetBrains IDE editor. + Returns a JSON object containing progress information: - text: The progress text/description - fraction: The progress ratio (0.0 to 1.0) - indeterminate: Whether the progress is indeterminate - Returns an empty array if no progress indicators are running. + Returns empty object if no progress indicator is running. + Note: Only returns the current thread's progress indicator. """.trimIndent() override fun handle(project: Project, args: NoArgs): Response { - val runningIndicators = CoreProgressManager.getCurrentIndicators() + val progressManager = ProgressManager.getInstance() + val indicator = progressManager.progressIndicator - val progressInfos = runningIndicators.map { indicator -> + if (indicator != null) { val text = indicator.text ?: "" val fraction = if (indicator.isIndeterminate) -1.0 else indicator.fraction val indeterminate = indicator.isIndeterminate - """{"text": "${text.replace("\"", "\\\"")}", "fraction": $fraction, "indeterminate": $indeterminate}""" + val progressInfo = """{"text": "${text.replace("\"", "\\\"")}", "fraction": $fraction, "indeterminate": $indeterminate}""" + return Response(progressInfo) } - return Response(progressInfos.joinToString(",\n", prefix = "[", postfix = "]")) + return Response("{}") } } diff --git a/core/src/main/kotlin/cc/unitmesh/devti/observer/test/RunTestUtil.kt b/core/src/main/kotlin/cc/unitmesh/devti/observer/test/RunTestUtil.kt index 3135cca1a3..06aa1ceeb4 100644 --- a/core/src/main/kotlin/cc/unitmesh/devti/observer/test/RunTestUtil.kt +++ b/core/src/main/kotlin/cc/unitmesh/devti/observer/test/RunTestUtil.kt @@ -115,10 +115,19 @@ object RunTestUtil { } is BuildView -> { - when (executionConsole.consoleView) { - is SMTRunnerConsoleView -> { - return getTestView(executionConsole.consoleView as SMTRunnerConsoleView) + // Use reflection to access internal consoleView to avoid using Internal API + try { + val consoleViewField = BuildView::class.java.getDeclaredField("consoleView") + consoleViewField.isAccessible = true + val consoleView = consoleViewField.get(executionConsole) + when (consoleView) { + is SMTRunnerConsoleView -> { + return getTestView(consoleView) + } } + } catch (e: Exception) { + // Fallback: return null if reflection fails + return null } } diff --git a/core/src/main/kotlin/cc/unitmesh/devti/sketch/run/ProcessExecutor.kt b/core/src/main/kotlin/cc/unitmesh/devti/sketch/run/ProcessExecutor.kt index 1f01f0cf64..a646288e19 100644 --- a/core/src/main/kotlin/cc/unitmesh/devti/sketch/run/ProcessExecutor.kt +++ b/core/src/main/kotlin/cc/unitmesh/devti/sketch/run/ProcessExecutor.kt @@ -121,7 +121,11 @@ class ProcessExecutor(val project: Project) { commandLine.withEnvironment("BASH_SILENCE_DEPRECATION_WARNING", "1") commandLine.withEnvironment("GIT_PAGER", "cat") val commands: List = listOf("bash", "--noprofile", "--norc", "-i") - return commandLine.startProcessWithPty(commands) + + // Use createProcess instead of startProcessWithPty to avoid Internal API + return commandLine.withExePath(commands[0]) + .withParameters(commands.drop(1)) + .createProcess() } private fun createProcess(shellScript: String): Process { @@ -149,7 +153,10 @@ class ProcessExecutor(val project: Project) { commandLine.withWorkDirectory(basedir) } - return commandLine.startProcessWithPty(commands) + // Use createProcess instead of startProcessWithPty to avoid Internal API + return commandLine.withExePath(commands[0]) + .withParameters(commands.drop(1)) + .createProcess() } private fun formatCommand(command: String): String { diff --git a/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/compiler/exec/vcs/CommitInsCommand.kt b/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/compiler/exec/vcs/CommitInsCommand.kt index 14803b2cb8..664a9b7425 100644 --- a/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/compiler/exec/vcs/CommitInsCommand.kt +++ b/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/compiler/exec/vcs/CommitInsCommand.kt @@ -7,6 +7,7 @@ import cc.unitmesh.devti.sketch.AutoSketchMode import com.intellij.ide.DataManager import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ModalityState import com.intellij.openapi.project.Project @@ -48,7 +49,8 @@ class CommitInsCommand(val myProject: Project, val commitMsg: String) : InsComma "", dataContext ) - shelveAction.actionPerformed(event) + // Use ActionUtil to properly invoke the action instead of calling actionPerformed directly + ActionUtil.performActionDumbAwareWithCallbacks(shelveAction, event) }, ModalityState.NON_MODAL) } } diff --git a/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/debugger/editor/ShirePreviewEditorProvider.kt b/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/debugger/editor/ShirePreviewEditorProvider.kt index 9a5c3c5e56..17dfa64cbb 100644 --- a/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/debugger/editor/ShirePreviewEditorProvider.kt +++ b/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/debugger/editor/ShirePreviewEditorProvider.kt @@ -1,7 +1,6 @@ package cc.unitmesh.devti.language.debugger.editor import cc.unitmesh.devti.language.DevInFileType -import com.intellij.openapi.fileEditor.AsyncFileEditorProvider import com.intellij.openapi.fileEditor.FileEditor import com.intellij.openapi.fileEditor.FileEditorPolicy import com.intellij.openapi.fileEditor.WeighedFileEditorProvider @@ -9,7 +8,7 @@ import com.intellij.openapi.fileTypes.FileTypeRegistry import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -class ShirePreviewEditorProvider : WeighedFileEditorProvider(), AsyncFileEditorProvider { +class ShirePreviewEditorProvider : WeighedFileEditorProvider() { override fun accept(project: Project, file: VirtualFile): Boolean { return FileTypeRegistry.getInstance().isFileOfType(file, DevInFileType.INSTANCE) } @@ -18,14 +17,6 @@ class ShirePreviewEditorProvider : WeighedFileEditorProvider(), AsyncFileEditorP return ShirePreviewEditor(project, virtualFile) } - override fun createEditorAsync(project: Project, file: VirtualFile): AsyncFileEditorProvider.Builder { - return object : AsyncFileEditorProvider.Builder() { - override fun build(): FileEditor { - return ShirePreviewEditor(project, file) - } - } - } - override fun getEditorTypeId(): String = "shire-preview-editor" override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.PLACE_AFTER_DEFAULT_EDITOR diff --git a/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/processor/shell/ShireShellCommandRunner.kt b/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/processor/shell/ShireShellCommandRunner.kt index 48fadd9a40..ac4b45b99f 100644 --- a/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/processor/shell/ShireShellCommandRunner.kt +++ b/exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/processor/shell/ShireShellCommandRunner.kt @@ -42,6 +42,9 @@ object ShireShellCommandRunner { val fileContent = fill(myProject, virtualFile, processVariables) val tempFile = File.createTempFile("tempScript", ".sh"); tempFile.writeText(fileContent) + + // Mark temp file for deletion on JVM exit as fallback + tempFile.deleteOnExit() val commandLine: GeneralCommandLine = GeneralCommandLine() .withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) @@ -51,31 +54,34 @@ object ShireShellCommandRunner { .withParameters(tempFile.path) val future = ApplicationManager.getApplication().executeOnPooledThread { - val processOutput = runCatching { - CapturingProcessHandler(commandLine).runProcess(DEFAULT_TIMEOUT) - }.apply { deleteFileOnTermination(commandLine, tempFile) }.getOrThrow() - - - val exitCode = processOutput.exitCode - if (exitCode != 0) { - throw RuntimeException("Cannot execute ${commandLine}: exit code $exitCode, error output: ${processOutput.stderr}") + try { + val processOutput = CapturingProcessHandler(commandLine).runProcess(DEFAULT_TIMEOUT) + val exitCode = processOutput.exitCode + if (exitCode != 0) { + throw RuntimeException("Cannot execute ${commandLine}: exit code $exitCode, error output: ${processOutput.stderr}") + } + processOutput.stdout + } finally { + // Always try to delete temp file after execution + try { + tempFile.delete() + } catch (e: Exception) { + logger().warn("Failed to delete temporary file: ${tempFile.path}", e) + } } - processOutput.stdout } return try { future.get() // ι˜»ε‘žθŽ·ε–η»“ζžœοΌŒε―δ»₯ι€‰ζ‹©ζ·»εŠ θΆ…ζ—ΆζŽ§εˆΆ } catch (e: Exception) { logger().error("Command execution failed", e) + // Ensure temp file is deleted even on error + try { + tempFile.delete() + } catch (deleteException: Exception) { + logger().warn("Failed to delete temporary file after error: ${tempFile.path}", deleteException) + } throw RuntimeException("Execution failed: ${e.message}", e) } } - - /** - * We need to ensure that the file is deleted after the process is executed. - * for example,the file also needs to be deleted when [create-process][OSProcessHandler.startProcess] fails. - */ - private fun deleteFileOnTermination(commandLine: GeneralCommandLine, tempFile: File) { - OSProcessHandler.deleteFileOnTermination(commandLine, tempFile) // is Internal API - } } diff --git a/mpp-idea/build.gradle.kts b/mpp-idea/build.gradle.kts index 5cf4bf53e4..cdb4d96ced 100644 --- a/mpp-idea/build.gradle.kts +++ b/mpp-idea/build.gradle.kts @@ -201,7 +201,7 @@ dependencies { intellijPlatform { pluginConfiguration { - name = "AutoDev Compose UI" + name = "AutoDev Experiment" version = mppVersion ideaVersion { @@ -209,6 +209,16 @@ intellijPlatform { } } + pluginVerification { + ides { + recommended() + } + } + + publishing { + token = providers.environmentVariable("PUBLISH_TOKEN") + } + buildSearchableOptions = false instrumentCode = false } diff --git a/mpp-idea/gradle.properties b/mpp-idea/gradle.properties index c4921efaaf..c8e8c09105 100644 --- a/mpp-idea/gradle.properties +++ b/mpp-idea/gradle.properties @@ -13,3 +13,6 @@ org.gradle.caching = true # Kotlin stdlib kotlin.stdlib.default.dependency = false +# MPP Version +mppVersion = 0.3.4 + diff --git a/mpp-idea/src/main/resources/META-INF/plugin.xml b/mpp-idea/src/main/resources/META-INF/plugin.xml index 7fa411017a..c872cdc9da 100644 --- a/mpp-idea/src/main/resources/META-INF/plugin.xml +++ b/mpp-idea/src/main/resources/META-INF/plugin.xml @@ -1,7 +1,7 @@ cc.unitmesh.devins.idea - AutoDev Next + AutoDev Experiment UnitMesh + ``` + +### Option 2: Manual Upload via Web + +1. Go to https://marketplace.visualstudio.com/manage/publishers/Phodal +2. Click "New Extension" +3. Upload `autodev-0.5.3.vsix` +4. Fill in the marketplace details +5. Publish + +## πŸ“ First-Time Publisher Setup + +If this is your first time publishing as "Phodal": + +1. **Create Publisher** + ```bash + vsce create-publisher Phodal + ``` + +2. **Login** + ```bash + vsce login Phodal + # Enter your PAT when prompted + ``` + +## πŸ” Verify Installation + +After publishing, test the extension: + +```bash +# Install from marketplace +code --install-extension Phodal.autodev + +# Or install from local VSIX (for testing) +code --install-extension autodev-0.5.3.vsix +``` + +## πŸ“Š What's Included in the Package + +The VSIX contains: +- Extension code (13.6 MB - bundled with esbuild) +- Webview assets (374 KB - React UI) +- Tree-sitter WASM files (10.74 MB - 8 languages) +- Media assets (icons, fonts) +- DevIns language syntax +- Test mocks + +**Total package size**: 3.11 MB (compressed) + +## πŸ”„ Updating the Extension + +For future updates: + +1. Update version in `gradle.properties` +2. Build and publish mpp-core if needed +3. Update mpp-vscode/package.json dependency versions +4. Run `npm run build` +5. Run `vsce package` +6. Run `vsce publish` or upload manually + +## πŸ“– Marketplace Listing + +**Display Name**: AutoDev - πŸ§™the AI-powered coding wizard (KMP Edition). + +**Description**: +> πŸ§™β€ AI-powered coding wizard with multilingual support 🌐, auto code generation πŸ—οΈ, based on Kotlin Multiplatform. AutoDev provides CodeLens, Chat, and powerful agent features! πŸš€ + +**Categories**: +- Programming Languages +- Education +- Machine Learning +- Snippets + +**Keywords**: ai, coding assistant, llm, autodev, kotlin multiplatform, kmp + +## πŸ”— Links + +- Homepage: https://vscode.unitmesh.cc +- Repository: https://github.com/unit-mesh/auto-dev-vscode +- Issues: https://github.com/unit-mesh/auto-dev-vscode/issues +- Marketplace: https://marketplace.visualstudio.com/items?itemName=Phodal.autodev + +## ⚠️ Important Notes + +1. **Same Publisher ID**: This extension uses `Phodal` as publisher, matching the existing AutoDev extension +2. **Version Coordination**: mpp-core (0.3.4) and CLI (0.3.4) are published separately +3. **File Size**: Extension is 3.11 MB compressed (includes WASM files for Tree-sitter) +4. **Minimum VSCode**: Requires VSCode 1.85.0 or higher + +## 🎯 Next Steps + +1. Execute: `vsce publish` in the mpp-vscode directory +2. Wait for marketplace processing (usually 5-10 minutes) +3. Verify extension appears in marketplace +4. Test installation and functionality +5. Announce the release! + +--- + +**Ready to publish!** πŸš€ +