Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d52fe4b
feat: preserve shell processes on timeout for AI-controlled termination
phodal Dec 24, 2025
8460753
feat: enable parallel tool execution with improved result formatting
phodal Dec 24, 2025
760e45d
feat(artifact): add AutoDev Unit Artifact system for reversible artif…
phodal Dec 24, 2025
59a229e
fix: resolve compilation errors in Artifact system - Fix AutoDevColor…
phodal Dec 24, 2025
8ce370a
feat(artifact): use compose-webview-multiplatform for ArtifactPreview…
phodal Dec 24, 2025
52997ed
refactor(artifact): reuse existing CodingAgentPage architecture
phodal Dec 24, 2025
d78c2cc
feat(artifact): add streaming preview support
phodal Dec 24, 2025
730f2c5
fix(artifact): improve streaming preview with live code view
phodal Dec 24, 2025
4075dde
fix(renderer): don't stall streaming on dangling '<'
phodal Dec 24, 2025
0315537
fix(artifact): render plain HTML as implicit artifact
phodal Dec 24, 2025
be47e56
fix(llm): consume streamPrompt flow without toList
phodal Dec 24, 2025
58b4a77
feat(artifact): implement .unit bundle format for artifact packaging
phodal Dec 24, 2025
8fc1d7b
feat(desktop): add .unit file association and handler
phodal Dec 24, 2025
e27addb
fix(artifact): fix .unit bundle ZIP serialization
phodal Dec 24, 2025
7d8d8c6
fix(artifact): improve export and load-back support
phodal Dec 24, 2025
5ac029a
fix(artifact): ensure console.log capture works reliably in artifact …
phodal Dec 24, 2025
94e54ea
fix(artifact): suppress noisy kmpJsBridge onCallback(-1) logs
phodal Dec 24, 2025
fb008c7
refactor(artifact): dedupe console logs via idempotent console bridge
phodal Dec 24, 2025
9416914
feat(artifact): improve console UI (colors + repeat counter)
phodal Dec 24, 2025
38bdd1d
feat(desktop): support initial bundle from file association #526
phodal Dec 24, 2025
d0b2b0f
feat(desktop): improve .unit file handling and logging #526
phodal Dec 24, 2025
84d0c27
feat(desktop): handle macOS file association via OpenFilesHandler
phodal Dec 24, 2025
1183d8f
fix: Use expect/actual pattern for cross-platform file open handler
phodal Dec 24, 2025
120b699
fix: Add MCP backend platform-specific implementations
phodal Dec 24, 2025
e61846b
chore: Add Android, iOS, and WASM implementations for ArtifactBundleP…
phodal Dec 24, 2025
a752e72
feat: Add support for ARTIFACT agent type in UI and exclude conflicti…
phodal Dec 24, 2025
ffe2f2f
refactor: Use UIStateManager for tree view toggle in CodingAgentPage
phodal Dec 24, 2025
38f90db
Remove exclusion for kotlinx-serialization-json-io-jvm conflict
phodal Dec 24, 2025
54a5df2
chore: Add Kotlin/Native ignore disabled targets flag
phodal Dec 24, 2025
f2a95a8
chore: Exclude kotlinx-serialization-json-io modules to prevent confl…
phodal Dec 25, 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
3 changes: 3 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ kotlin.js.yarn=false
kotlin.incremental.js=false
kotlin.incremental.js.ir=false

# Ignore disabled Kotlin/Native targets
kotlin.native.ignoreDisabledTargets=true

22 changes: 14 additions & 8 deletions mpp-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {

// Temporarily disabled: npm publish plugin doesn't support wasmJs targets
// TODO: Re-enable once plugin supports wasmJs or split into separate modules
id("dev.petuska.npm.publish") version "3.5.3"
// id("dev.petuska.npm.publish") version "3.5.3"
}

repositories {
Expand Down Expand Up @@ -264,13 +264,6 @@ kotlin {

implementation(npm("wasm-git", "0.0.13"))

// Force kotlin-stdlib to 2.2.0 to match compiler version
implementation("org.jetbrains.kotlin:kotlin-stdlib") {
version {
strictly("2.2.0")
}
}

// WASM specific dependencies if needed
}
}
Expand All @@ -283,6 +276,8 @@ kotlin {
}
}

// Temporarily disabled: npm publish plugin doesn't support wasmJs targets
/*
npmPublish {
organization.set("xiuper")

Expand All @@ -309,6 +304,7 @@ npmPublish {
}
}
}
*/

// Disable wasmJs browser tests due to webpack compatibility issues
// See: https://github.com/webpack/webpack/issues/XXX
Expand All @@ -331,3 +327,13 @@ tasks.register<JavaExec>("runNanoDslScenarioHarness") {
classpath = kotlin.jvm().compilations.getByName("main").runtimeDependencyFiles +
files(kotlin.jvm().compilations.getByName("main").output.classesDirs)
}

// Task to generate a test .unit file
tasks.register<JavaExec>("generateTestUnit") {
group = "verification"
description = "Generate a test .unit file for verification"

val jvmCompilation = kotlin.jvm().compilations.getByName("main")
classpath(jvmCompilation.output, configurations["jvmRuntimeClasspath"])
mainClass.set("cc.unitmesh.agent.artifact.GenerateTestUnitKt")
}
Comment on lines +332 to +339
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add dependsOn("jvmMainClasses") for task reliability.

Unlike the runNanoDslScenarioHarness task (line 330), this task doesn't declare a dependency on jvmMainClasses. This may cause failures when running the task on a clean build.

🔎 Proposed fix
 tasks.register<JavaExec>("generateTestUnit") {
     group = "verification"
     description = "Generate a test .unit file for verification"
+    dependsOn("jvmMainClasses")

     val jvmCompilation = kotlin.jvm().compilations.getByName("main")
     classpath(jvmCompilation.output, configurations["jvmRuntimeClasspath"])
     mainClass.set("cc.unitmesh.agent.artifact.GenerateTestUnitKt")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tasks.register<JavaExec>("generateTestUnit") {
group = "verification"
description = "Generate a test .unit file for verification"
val jvmCompilation = kotlin.jvm().compilations.getByName("main")
classpath(jvmCompilation.output, configurations["jvmRuntimeClasspath"])
mainClass.set("cc.unitmesh.agent.artifact.GenerateTestUnitKt")
}
tasks.register<JavaExec>("generateTestUnit") {
group = "verification"
description = "Generate a test .unit file for verification"
dependsOn("jvmMainClasses")
val jvmCompilation = kotlin.jvm().compilations.getByName("main")
classpath(jvmCompilation.output, configurations["jvmRuntimeClasspath"])
mainClass.set("cc.unitmesh.agent.artifact.GenerateTestUnitKt")
}
🤖 Prompt for AI Agents
In mpp-core/build.gradle.kts around lines 336 to 343, the generateTestUnit
JavaExec task lacks a declared dependency on the jvmMainClasses compilation,
which can cause failures on clean builds; add dependsOn("jvmMainClasses") to the
task registration so the JVM main classes are built before the task runs,
ensuring reliable execution.

Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package cc.unitmesh.agent.artifact

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream

/**
* Android implementation of ArtifactBundlePacker
* Uses java.util.zip for ZIP operations (same as JVM)
*/
actual class ArtifactBundlePacker {

actual suspend fun pack(bundle: ArtifactBundle, outputPath: String): PackResult =
withContext(Dispatchers.IO) {
try {
val outputFile = File(outputPath)
outputFile.parentFile?.mkdirs()

ZipOutputStream(FileOutputStream(outputFile)).use { zipOut ->
// Add ARTIFACT.md
val artifactMd = bundle.generateArtifactMd()
addZipEntry(zipOut, ArtifactBundle.ARTIFACT_MD, artifactMd)

// Add package.json
val packageJson = bundle.generatePackageJson()
addZipEntry(zipOut, ArtifactBundle.PACKAGE_JSON, packageJson)

// Add main content
val mainFileName = bundle.getMainFileName()
addZipEntry(zipOut, mainFileName, bundle.mainContent)

// Add additional files
bundle.files.forEach { (fileName, content) ->
addZipEntry(zipOut, fileName, content)
}

// Add context.json
val contextJson = ArtifactBundleUtils.serializeBundle(bundle)
addZipEntry(zipOut, ArtifactBundle.CONTEXT_JSON, contextJson)
}

PackResult.Success(outputPath)
} catch (e: Exception) {
PackResult.Error("Failed to pack bundle: ${e.message}", e)
}
}

actual suspend fun unpack(inputPath: String): UnpackResult =
withContext(Dispatchers.IO) {
try {
val files = mutableMapOf<String, String>()

ZipInputStream(FileInputStream(inputPath)).use { zipIn ->
var entry: ZipEntry? = zipIn.nextEntry
while (entry != null) {
if (!entry.isDirectory) {
val content = zipIn.readBytes().toString(Charsets.UTF_8)
files[entry.name] = content
}
zipIn.closeEntry()
entry = zipIn.nextEntry
}
}

val bundle = ArtifactBundleUtils.reconstructBundle(files)
if (bundle != null) {
UnpackResult.Success(bundle)
} else {
UnpackResult.Error("Failed to reconstruct bundle from ZIP contents")
}
} catch (e: Exception) {
UnpackResult.Error("Failed to unpack bundle: ${e.message}", e)
}
}

actual suspend fun extractToDirectory(inputPath: String, outputDir: String): PackResult =
withContext(Dispatchers.IO) {
try {
val outputDirectory = File(outputDir)
outputDirectory.mkdirs()

ZipInputStream(FileInputStream(inputPath)).use { zipIn ->
var entry: ZipEntry? = zipIn.nextEntry
while (entry != null) {
if (!entry.isDirectory) {
val outputFile = File(outputDirectory, entry.name)
outputFile.parentFile?.mkdirs()

FileOutputStream(outputFile).use { fileOut ->
zipIn.copyTo(fileOut)
}
}
zipIn.closeEntry()
entry = zipIn.nextEntry
}
}
Comment on lines +85 to +99
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Zip Slip vulnerability: validate entry paths before extraction.

The code directly uses entry.name from the ZIP file without validation. A malicious ZIP could contain entries with path traversal sequences (e.g., ../../../sensitive-file) or absolute paths, allowing files to be written outside the intended output directory.

🔎 Proposed fix to prevent Zip Slip
 ZipInputStream(FileInputStream(inputPath)).use { zipIn ->
     var entry: ZipEntry? = zipIn.nextEntry
     while (entry != null) {
         if (!entry.isDirectory) {
             val outputFile = File(outputDirectory, entry.name)
+            // Prevent Zip Slip: ensure the output file is within the target directory
+            if (!outputFile.canonicalPath.startsWith(outputDirectory.canonicalPath + File.separator)) {
+                throw SecurityException("Zip entry is outside of the target dir: ${entry.name}")
+            }
             outputFile.parentFile?.mkdirs()
             
             FileOutputStream(outputFile).use { fileOut ->
                 zipIn.copyTo(fileOut)
             }
         }
         zipIn.closeEntry()
         entry = zipIn.nextEntry
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ZipInputStream(FileInputStream(inputPath)).use { zipIn ->
var entry: ZipEntry? = zipIn.nextEntry
while (entry != null) {
if (!entry.isDirectory) {
val outputFile = File(outputDirectory, entry.name)
outputFile.parentFile?.mkdirs()
FileOutputStream(outputFile).use { fileOut ->
zipIn.copyTo(fileOut)
}
}
zipIn.closeEntry()
entry = zipIn.nextEntry
}
}
ZipInputStream(FileInputStream(inputPath)).use { zipIn ->
var entry: ZipEntry? = zipIn.nextEntry
while (entry != null) {
if (!entry.isDirectory) {
val outputFile = File(outputDirectory, entry.name)
// Prevent Zip Slip: ensure the output file is within the target directory
if (!outputFile.canonicalPath.startsWith(outputDirectory.canonicalPath + File.separator)) {
throw SecurityException("Zip entry is outside of the target dir: ${entry.name}")
}
outputFile.parentFile?.mkdirs()
FileOutputStream(outputFile).use { fileOut ->
zipIn.copyTo(fileOut)
}
}
zipIn.closeEntry()
entry = zipIn.nextEntry
}
}


PackResult.Success(outputDir)
} catch (e: Exception) {
PackResult.Error("Failed to extract bundle: ${e.message}", e)
}
}

private fun addZipEntry(zipOut: ZipOutputStream, fileName: String, content: String) {
val entry = ZipEntry(fileName)
zipOut.putNextEntry(entry)
zipOut.write(content.toByteArray(Charsets.UTF_8))
zipOut.closeEntry()
}
}
10 changes: 9 additions & 1 deletion mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ enum class AgentType {
/**
* Web edit mode - browse, select DOM elements, and interact with web pages
*/
WEB_EDIT;
WEB_EDIT,

/**
* Artifact mode - generate reversible, executable artifacts (HTML/JS, Python scripts)
* Similar to Claude's Artifacts system
*/
ARTIFACT;

fun getDisplayName(): String = when (this) {
LOCAL_CHAT -> "Chat"
Expand All @@ -55,6 +61,7 @@ enum class AgentType {
CHAT_DB -> "ChatDB"
REMOTE -> "Remote"
WEB_EDIT -> "WebEdit"
ARTIFACT -> "Artifact"
}

companion object {
Expand All @@ -67,6 +74,7 @@ enum class AgentType {
"documentreader", "documents" -> KNOWLEDGE
"chatdb", "database" -> CHAT_DB
"webedit", "web" -> WEB_EDIT
"artifact", "unit" -> ARTIFACT
else -> LOCAL_CHAT
}
}
Expand Down
Loading
Loading