Skip to content

Conversation

@phodal
Copy link
Owner

@phodal phodal commented Dec 25, 2025

Description

This PR implements dynamic run configuration generation using AI analysis, addressing #529.

Features

  • LLM-based project analysis with streaming output

    • Analyzes project structure (package.json, build.gradle, pom.xml, etc.)
    • Uses AI to intelligently generate run configurations
    • Streams analysis reasoning in real-time
  • IDE-like Run Button (floating in top-right corner)

    • Shows different states: Configure, Analyzing, Run/Stop, Error
    • Dropdown menu with all discovered run configurations
    • Visual feedback with appropriate icons and colors
  • Real-time command execution with live streaming output

    • Supports long-running commands (e.g., bootRun)
    • Streams output incrementally for better UX
    • Handles process lifecycle properly
  • Stop/Cancel functionality

    • Stop button in RunOutputDock when command is running
    • Properly terminates processes and cleans up resources
    • Shows cancellation status in output
  • RunOutputDock for displaying:

    • AI analysis logs (shows what AI analyzed and discovered)
    • Command execution output (terminal-like display)
    • Auto-opens when analysis or execution starts

Key Components

Core

  • LLMRunConfigAnalyzer: Streams AI analysis of project structure
  • RunConfigService: Manages run configs with live execution support
  • RunConfig: Data models for run configurations

UI

  • FloatingRunButton: IDE-style floating action button
  • RunConfigViewModel: ViewModel for UI state management
  • RunOutputDock: Terminal-like output display

How It Works

  1. User clicks the Run button (or Configure if not analyzed)
  2. System gathers project context (file list + config file contents)
  3. LLM analyzes the project and generates run configurations
  4. Analysis output streams in real-time to RunOutputDock
  5. User can select and execute any discovered configuration
  6. Command output streams in real-time
  7. User can stop running commands at any time

Screenshots / Demo

  • Floating Run Button in top-right corner
  • Analysis output showing AI reasoning
  • Dropdown menu with discovered configurations
  • Real-time command execution output

Testing

  • ✅ Tested with Gradle projects (Spring Boot)
  • ✅ Tested with Node.js projects
  • ✅ Tested stop/cancel functionality
  • ✅ Tested long-running commands (bootRun)

Related Issues

Closes #529

Notes

This implementation focuses on the "Run Config" aspect of GenAction, providing a concrete example of how Agent-driven actions can be dynamically generated and executed. The architecture can be extended to support other types of IDE actions in the future.

Summary by CodeRabbit

  • New Features

    • AI-powered automatic project run-configuration discovery with streaming progress
    • Run configuration model, persistence, and management UI (add/remove, default selection)
    • Execute run configurations with live output, stop/cancel and timeout handling
    • Floating run button, run menu/button, and run output dock for integrated controls and logs
    • New slash command to generate actions (/gen-actions)
  • Platform

    • Cross-platform process I/O stubs and JVM/Android implementations; tooltip wrapper implementations for multiple targets
  • UI

    • New icons and composables for run workflows

✏️ Tip: You can customize this high-level summary in your review settings.

Implements dynamic run configuration generation using AI analysis, addressing #529.

Features:
- LLM-based project analysis with streaming output
- Floating Run Button in top-right corner (IDE-like UX)
- Real-time command execution with live streaming output
- Stop/Cancel functionality for running commands
- RunOutputDock for displaying analysis and execution logs

Key Components:
- LLMRunConfigAnalyzer: Streams AI analysis of project structure
- RunConfigService: Manages run configs with live execution support
- FloatingRunButton: IDE-style floating action button
- RunOutputDock: Terminal-like output display for analysis and execution

The system automatically analyzes project files (package.json, build.gradle, etc.)
and uses LLM to intelligently generate run configurations, making it easy
to discover and execute project commands without manual configuration.

Closes #529
Copilot AI review requested due to automatic review settings December 25, 2025 12:14
@coderabbitai
Copy link

coderabbitai bot commented Dec 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a run-configuration discovery and execution system: core models and storage, an LLM-driven streaming analyzer, orchestration service with execution and persistence, platform-specific process readers, Compose UI components and view model, a /gen-actions slash command, and cross-platform tooltip/tooltip-wrapper implementations.

Changes

Cohort / File(s) Summary
Core Models & Storage
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfig.kt
New serializable data models and enums: RunConfig, RunConfigType, RunConfigSource, RunConfigState, RunConfigResult, RunConfigStorage.
LLM Analyzer
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/LLMRunConfigAnalyzer.kt
New LLMRunConfigAnalyzer: gathers project context, builds prompt, streams LLM output, extracts JSON block, parses into RunConfig list, and emits streaming progress/completion/error events (AnalysisEvent).
Service & Orchestration
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
New RunConfigService: initialization, loading/saving storage, streaming analysis integration, execution (live/blocking), cancellation, default selection, config management, and state flows for UI.
Process I/O (multiplatform)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.kt, .../jvm.kt, .../android.kt, .../ios.kt, .../js.kt, .../wasmJs.kt
Introduces expect declarations and platform-specific actuals for reading process output, killing processes, native-process checks, and current time; JVM/Android implement streaming readers, other platforms return no-op/stub behaviors.
ViewModel
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunConfigViewModel.kt
New RunConfigViewModel wrapping RunConfigService, exposing state flows (state, configs, defaultConfig, isRunning, runningConfigId, output, analysisLog) and UI actions (analyze, run, stop, manage configs).
UI Composables
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt, .../RunButton.kt, .../RunOutputDock.kt
New composables: FloatingRunButton, RunButton, and RunOutputDock for analyze/run controls, menus, and output docking with adaptive visuals and actions.
CodingAgent Integration
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt
Integrates RunConfigViewModel, RunOutputDock, and FloatingRunButton into CodingAgentPage; wires outputs, dock visibility, and run controls into the editor UI.
Slash Command: GenAction
mpp-ui/src/jsMain/typescript/processors/SlashCommandProcessor.ts
Adds /gen-actions (aliases ga, actions) and handler handleGenActionsCommand with arg parsing, project validation, GenActionService invocation, progress logging, and helper getCategoryIcon().
Tooltip Wrapper (expect/actual)
mpp-ui/src/commonMain/.../TooltipWrapper.kt, .../TooltipWrapper.jvm.kt, .../TooltipWrapper.android.kt, .../TooltipWrapper.ios.kt, .../TooltipWrapper.js.kt, .../TooltipWrapper.wasmJs.kt
Adds an expect API for TooltipWrapper and platform actuals: JVM uses TooltipArea, others render content without tooltip.
Icons
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
Adds Science and Extension icon properties mapping to Icons.Default.*. ⚠️ Duplicate declarations detected; reconcile duplicates to avoid symbol conflicts.
Minor UI comment
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenu.kt
Added a comment above the WASM branch invocation; no behavioral change.

Sequence Diagram(s)

sequenceDiagram
    participant UI as UI / ViewModel
    participant Service as RunConfigService
    participant Analyzer as LLMRunConfigAnalyzer
    participant LLM as LLM Service
    participant Storage as File Storage
    participant Executor as Shell Executor

    rect rgb(235, 245, 255)
    Note over UI, Storage: Analysis flow (streaming)
    UI->>Service: analyzeProject()
    Service->>Analyzer: analyzeStreaming()
    Analyzer->>Analyzer: gatherProjectContext()
    Analyzer->>LLM: send prompt / stream responses
    LLM-->>Analyzer: streaming text (progress + JSON block)
    Analyzer->>Analyzer: extract JSON block -> parseRunConfigs()
    Analyzer-->>Service: emit Complete(configs)
    Service->>Storage: saveConfigs()
    Service-->>UI: update state & configs
    end

    rect rgb(235, 255, 235)
    Note over UI, Executor: Execution flow
    UI->>Service: execute(configId)
    Service->>Executor: start process (live or blocking)
    Executor->>Service: stream stdout/stderr
    Service-->>UI: forward output events
    Executor-->>Service: exitCode/result
    Service-->>UI: emit RunConfigResult
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I sniffed the tree of code and found,
I hopped through prompts both deep and round,
Buttons, docks, and configs spun —
LLM-picked runs now ready, done.
A tiny rabbit cheers, well-ground! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements run configuration features but partially addresses linked issue #529 (GenAction). The /gen-actions slash command and related context-aware generation are implemented, but GenAction right-click context menu entry points for CodingPage are not visible in the changes. Ensure CodingPage context menu integration for GenAction is fully implemented as specified in issue #529, including right-click menu for editor, file, directory, and module scopes.
Out of Scope Changes check ⚠️ Warning Most changes directly support run config analysis and execution, but multiple files (TooltipWrapper implementations and AutoDevComposeIcons duplicates) appear tangential to the main PR objectives. Remove or clarify the necessity of TooltipWrapper cross-platform implementations and fix duplicate icon definitions in AutoDevComposeIcons that are not essential to the run config feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 63.24% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: LLM-based run config analysis with IDE-like run button, which aligns with the primary changes across all files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/run-config-llm-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@augmentcode
Copy link

augmentcode bot commented Dec 25, 2025

🤖 Augment PR Summary

Summary: This PR adds AI-driven run configuration discovery and an IDE-like Run UX to AutoDev.

Changes:

  • Introduces LLMRunConfigAnalyzer to stream LLM project analysis and parse JSON run configs.
  • Adds RunConfig models and storage schema (.xiuper/run-configs.json).
  • Implements RunConfigService for loading/saving configs and executing commands with live output + stop support.
  • Adds Compose UI pieces (FloatingRunButton, RunOutputDock) and a RunConfigViewModel for UI state.
  • Wires the floating run button + output dock into CodingAgentPage for Coding agent mode.
  • Extends the JS slash command processor with /gen-actions for generating IDE actions from AGENTS.md rules.

Technical Notes: Uses Kotlin Flow streaming for LLM reasoning/output and StateFlow for UI state; shell execution can use live sessions when supported.

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 6 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.


// Parse final response
val fullResponse = responseBuffer.toString()
logger.debug { "LLM response:\n$fullResponse" }
Copy link

Choose a reason for hiding this comment

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

fullResponse can contain project config contents (and potentially secrets), so logging the entire LLM response risks sensitive data exposure. (Guideline: no_sensitive_logging)

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

emit(AnalysisEvent.Error("Could not parse run configurations from AI response"))
}

} catch (e: Exception) {
Copy link

Choose a reason for hiding this comment

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

This catch (e: Exception) will also catch CancellationException, which can prevent proper coroutine cancellation and streaming cleanup. (Guideline: streaming_cleanup)

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎


// Get top-level file list
try {
val topFiles = fileSystem.listFiles(projectPath)
Copy link

Choose a reason for hiding this comment

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

DefaultToolFileSystem.listFiles(projectPath) returns absolute child paths, so filters like startsWith(".") / it != "node_modules" won’t work and the prompt may leak full local paths to the LLM.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

stdoutReader.join(2000)
stderrReader.join(2000)

if (!completed) {
Copy link

Choose a reason for hiding this comment

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

When waitFor times out, this returns success while the process keeps running, but execute()’s finally clears _isRunning/currentProcess/currentSession, so the UI can no longer stop the still-running process (or track it).

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

var cancelled = false

// Read stdout in a separate thread
val stdoutReader = Thread {
Copy link

Choose a reason for hiding this comment

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

Spawning raw Threads for stdout/stderr here can leak threads for long-running commands (they’re non-daemon and not reliably stopped), and cancelled is shared across threads without synchronization. (Guideline: thread_safety)

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

projectPath: String,
llmService: KoogLLMService? = null
) {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
Copy link

Choose a reason for hiding this comment

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

This ViewModel owns a CoroutineScope but there’s no cancellation/dispose hook; if CodingAgentPage recreates the VM (workspace/LLM change), the old scope may keep running and leak work/resources. (Guideline: no_memory_leaks)

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (8)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenu.kt (1)

66-92: Consider using expect/actual pattern for platform-specific implementations.

The platform detection logic using Platform.isAndroid and Platform.isWasm works but violates the coding guidelines, which specify using the expect/actual pattern for platform-specific code. Refactoring to separate platform-specific implementations would improve type safety and compile-time verification.

The clarifying comment on line 67 is helpful, but the underlying structure would benefit from this pattern.

🔎 Suggested refactoring approach

Instead of runtime platform checks, create platform-specific expect/actual declarations:

Common (commonMain):

@Composable
expect fun TopBarMenu(
    hasHistory: Boolean,
    hasDebugInfo: Boolean,
    // ... other parameters
)

Android (androidMain):

@Composable
actual fun TopBarMenu(
    // ... parameters
) {
    TopBarMenuMobile(
        // ... pass parameters
    )
}

WASM (wasmJsMain):

@Composable
actual fun TopBarMenu(
    // ... parameters
) {
    TopBarMenuDesktop(
        // ... pass parameters
    )
}

This approach provides compile-time guarantees and improves code organization.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunOutputDock.kt (1)

48-58: String-based type detection works but could be more explicit.

Using title.contains("Analysis") is pragmatic but couples the behavior to the title string. Consider accepting a isAnalysis: Boolean parameter directly if this distinction becomes more important in the future.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt (1)

145-209: Well-implemented run configuration menu with proper state handling.

The menu correctly displays all configurations with appropriate icons and colors based on running/default state. The toggle between run and stop actions per configuration works as expected.

Consider replacing println at line 182 with a proper logger for production code.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/LLMRunConfigAnalyzer.kt (1)

56-85: JSON block detection could be refined but is functionally correct.

The inJsonBlock tracking filters JSON from display output. The end detection at lines 82-84 doesn't reset inJsonBlock to false, meaning once JSON starts, all subsequent content is hidden. This is likely intentional since JSON typically comes at the end, but if the LLM adds text after JSON, it won't be displayed.

🔎 Potential refinement if post-JSON text display is needed
                 // Check if JSON block ended
                 if (inJsonBlock && chunk.contains("```") && !chunk.contains("```json")) {
-                    // JSON block might have ended, but keep inJsonBlock true to avoid further text
+                    inJsonBlock = false
                 }
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt (4)

187-188: Consider documenting the job capture pattern.

Capturing the job via currentCoroutineContext()[Job] inside coroutineScope works but is non-obvious. A brief comment explaining this is for stopRunning() coordination would improve maintainability.


271-273: Mutable vars accessed without synchronization.

currentSession, currentProcess, and executionJob are accessed from both execute() and stopRunning() potentially on different threads. While the cleanup operations are idempotent, consider using AtomicReference or ensuring access happens on a single dispatcher for cleaner concurrency semantics.


432-439: Returning success=true on exception may be misleading.

When an exception occurs (e.g., timeout), returning success=true could mislead callers. Consider using a distinct result state (e.g., RunConfigResult with status = TIMEOUT or RUNNING) to differentiate from actual successful completion.


265-268: Redundant cleanup in nested finally blocks.

Both executeWithLiveStreaming() (lines 265-268) and the outer execute() (lines 230-232) null out currentSession and currentProcess. The outer finally is sufficient.

🔎 Proposed simplification
             }
-        } finally {
-            currentSession = null
-            currentProcess = null
-        }
+        }
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e195a23 and 9deeabb.

📒 Files selected for processing (11)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/LLMRunConfigAnalyzer.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfig.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenu.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunButton.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunConfigViewModel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunOutputDock.kt
  • mpp-ui/src/jsMain/typescript/processors/SlashCommandProcessor.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use expect/actual pattern for platform-specific code (for example: Platform implementations)
Use concrete classes (not interfaces) and Promise (not Flow) for @JsExport annotations
Avoid emoji and UTF-8 characters in WASM code
When modifying CodingAgentRenderer, update ALL implementations: DefaultCodingAgentRenderer, ComposeRenderer, JewelRenderer, ServerSideRenderer, and JsRendererAdapter in Kotlin
When modifying CodingAgentRenderer, update JVM CLI implementations: CodingCliRenderer and ConsoleRenderer
Use AutoDevColors or MaterialTheme.colorScheme for Compose styling

Files:

  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunOutputDock.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenu.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfig.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunButton.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunConfigViewModel.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/LLMRunConfigAnalyzer.kt
**/*.{kt,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never use hardcoded colors - always use design tokens from the design system

Files:

  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunOutputDock.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenu.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfig.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunButton.kt
  • mpp-ui/src/jsMain/typescript/processors/SlashCommandProcessor.ts
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunConfigViewModel.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/LLMRunConfigAnalyzer.kt
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

When modifying CodingAgentRenderer, update ALL TypeScript implementations: BaseRenderer.ts, CliRenderer.ts, ServerRenderer.ts, and TuiRenderer.ts

Files:

  • mpp-ui/src/jsMain/typescript/processors/SlashCommandProcessor.ts
mpp-ui/src/jsMain/typescript/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use semanticInk/semanticChalk design tokens from mpp-ui/src/jsMain/typescript/design-system/ for CLI/TUI styling

Files:

  • mpp-ui/src/jsMain/typescript/processors/SlashCommandProcessor.ts
🧠 Learnings (3)
📚 Learning: 2025-12-07T08:40:10.859Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.859Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, and `JsRendererAdapter` in Kotlin

Applied to files:

  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt
📚 Learning: 2025-12-07T08:40:10.859Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.859Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update JVM CLI implementations: `CodingCliRenderer` and `ConsoleRenderer`

Applied to files:

  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt
📚 Learning: 2025-12-07T08:40:10.859Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.859Z
Learning: Applies to mpp-vscode/src/**/*.ts : When modifying `CodingAgentRenderer`, update VSCode implementations: `mpp-vscode/src/bridge/mpp-core.ts` and `mpp-vscode/src/providers/chat-view.ts`

Applied to files:

  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt
🧬 Code graph analysis (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt (2)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunOutputDock.kt (1)
  • RunOutputDock (37-141)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt (1)
  • FloatingRunButton (49-212)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Agent
  • GitHub Check: Build
  • GitHub Check: Test mpp-core (KMP)
🔇 Additional comments (34)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt (1)

117-119: Code implementation is correct; no changes needed.

The additions follow the established pattern correctly, and the inconsistency flagged in the AI summary is valid—no duplicate declarations exist. Both Icons.Default.Science and Icons.Default.Extension are available in the Material Icons Extended library, which the project already includes as a dependency (compose.materialIconsExtended).

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunOutputDock.kt (3)

37-47: Well-structured component signature with appropriate defaults.

The composable signature follows Compose best practices with sensible defaults for optional parameters and clear callback naming.


87-113: Correct conditional rendering for Stop button.

The logic properly hides the Stop button during analysis and when onStop callback is null, which aligns with the PR's requirement to support both analysis and run output modes.


116-137: LGTM!

Dynamic height based on content type and appropriate placeholder messages provide good UX. The PlatformTerminalDisplay integration enables cross-platform compatibility as documented.

mpp-ui/src/jsMain/typescript/processors/SlashCommandProcessor.ts (5)

114-120: LGTM!

Command registration follows the established pattern with appropriate description and useful aliases.


504-538: Consistent error handling and argument parsing.

The handler follows the same validation pattern as other commands in this file, with proper error returns for invalid project paths and missing LLM configuration.


575-616: Well-structured action generation with informative output.

The implementation provides clear progress feedback, formatted action listings with category icons, and helpful guidance for the no-actions case.


618-625: LGTM!

Error handling follows the established pattern with proper logging and user-friendly error messages.


628-644: Clean helper function with sensible defaults.

The category-to-icon mapping is comprehensive and the fallback ensures graceful handling of unknown categories.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt (2)

49-62: LGTM!

The composable signature provides all necessary state and callbacks for the floating run button functionality, with appropriate defaults.


80-130: Good use of animation and tooltip for analyzing state.

The spinning animation provides clear visual feedback, and the tooltip with scrollable analysis log gives users insight into the LLM reasoning process without cluttering the UI.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunConfigViewModel.kt (3)

74-106: Analysis method handles streaming progress appropriately.

The method correctly clears previous state, streams progress updates, and handles errors from the service. The summary of discovered configurations at the end provides useful feedback.


115-150: LGTM!

The run and stop methods provide proper lifecycle management with clear status messages for success, failure, and cancellation states.


152-209: LGTM!

Utility methods properly delegate to the service layer and maintain consistent state management.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunButton.kt (4)

60-165: Well-organized dropdown with type grouping.

The CONFIGURED state provides a clean UX with the main run button for quick access and a dropdown with configs grouped by type. The dividers and headers help users navigate multiple configurations.


216-251: LGTM!

The analyzing state animation is consistent with the FloatingRunButton implementation, providing a unified visual experience.


302-372: LGTM!

The menu item provides comprehensive information with proper visual hierarchy, overflow handling, and state indicators.


374-408: LGTM!

Type-to-icon and type-to-color mappings are comprehensive and use the design system tokens (AutoDevColors) as per coding guidelines.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/CodingAgentPage.kt (5)

18-20: LGTM!

New imports for run configuration components are correctly added.


142-157: ViewModel state collection is comprehensive.

All necessary state flows are properly collected for UI consumption. However, note that the ViewModel's coroutine scope should be cancelled when no longer needed (flagged in RunConfigViewModel.kt review).


167-171: LGTM!

The auto-open logic correctly triggers for any relevant activity (running, analyzing, or having output), providing immediate feedback to users.


263-279: Correct dock configuration with appropriate callbacks.

The dock correctly displays either analysis log or run output based on state, and the onStop callback is properly conditioned to only appear during run execution (not analysis).


569-585: Well-integrated floating run button with proper conditional visibility.

The button is appropriately shown only for CODING agent mode and correctly wired to the ViewModel actions. The positioning with Alignment.TopEnd aligns with the PR objective of an IDE-like floating Run button.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfig.kt (3)

16-50: LGTM!

Well-designed data class with appropriate fields and sensible defaults. The separation of required vs optional fields with defaults follows Kotlin best practices.


52-119: LGTM!

The enum types are comprehensive, well-documented, and cover the expected use cases for run configuration management.


121-164: LGTM!

RunConfigResult captures execution outcomes comprehensively, and RunConfigStorage is well-designed for persistence with version compatibility.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/LLMRunConfigAnalyzer.kt (5)

25-35: LGTM!

The class setup with appropriate JSON configuration for lenient parsing handles the variability of LLM responses well.


126-177: LGTM!

Context gathering is thorough with appropriate filtering and truncation to manage LLM context limits. The list of config files covers common project types (Node.js, Gradle, Maven, Cargo, Go, Python, etc.).


182-228: Well-crafted LLM prompt with clear instructions.

The prompt provides comprehensive context and explicit output format requirements. The practical guidance (max 10 commands, package manager detection, gradlew preference) helps generate useful configurations.


233-276: LGTM!

JSON parsing with fallback for raw arrays is robust. The ID generation and default assignment logic are sensible for typical project workflows.


279-311: LGTM!

The sealed class for events provides type-safe handling, and the supporting types are appropriately encapsulated as private implementation details.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt (3)

48-66: LGTM!

State management using MutableStateFlow with read-only StateFlow exposure follows Kotlin best practices for observable state.


574-579: reset() doesn't clear persisted configs.

This method clears in-memory state but leaves .xiuper/run-configs.json intact. Calling initialize() afterward will reload the old configs. Is this the intended behavior, or should reset() also delete the storage file?


530-569: LGTM!

Good persistence implementation with proper directory creation, null safety for missing files, and exception handling that doesn't crash the application.

LLMRunConfigAnalyzer(projectPath, fileSystem, it)
}

private val configStoragePath = "$projectPath/.xiuper/run-configs.json"
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

🧩 Analysis chain

🏁 Script executed:

# First, let's read the file to see the actual code
cat -n mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt | head -60

Repository: phodal/auto-dev

Length of output: 2854


🏁 Script executed:

# Check if this is a multiplatform project and what platforms are targeted
find . -name "build.gradle.kts" -o -name "build.gradle" -o -name "gradle.properties" | head -5

Repository: phodal/auto-dev

Length of output: 186


🏁 Script executed:

# Search for existing path handling patterns in the codebase
rg "File\.separator|Paths\.get|Path\.of|normalize|replaceBackslashes" --type=kt --max-count=20 -C1

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Check if there's any platform-specific code in the project
rg "expect|actual" --type=kt mpp-core/ --max-count=10 -C1

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Check the project structure to understand target platforms
cat -n mpp-core/build.gradle.kts 2>/dev/null | head -80

Repository: phodal/auto-dev

Length of output: 2540


🏁 Script executed:

# Search for ToolFileSystem interface/class definition and usage
find . -name "*.kt" -type f | xargs grep -l "ToolFileSystem" | head -10

Repository: phodal/auto-dev

Length of output: 897


🏁 Script executed:

# Look at DefaultToolFileSystem implementation
find . -name "DefaultToolFileSystem.kt" -type f -exec cat {} \;

Repository: phodal/auto-dev

Length of output: 41


🏁 Script executed:

# Search for how paths are constructed elsewhere in the codebase
find . -name "*.kt" -type f | xargs grep -n "\.xiuper\|projectPath.*/" | head -20

Repository: phodal/auto-dev

Length of output: 2938


🏁 Script executed:

# Check if there are other instances of path joining in RunConfigService
find . -name "RunConfigService.kt" -exec cat -n {} \;

Repository: phodal/auto-dev

Length of output: 24971


🏁 Script executed:

# Check ToolFileSystem interface for available methods
cat mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/filesystem/ToolFileSystem.kt

Repository: phodal/auto-dev

Length of output: 9519


🏁 Script executed:

# Look at how DefaultToolFileSystem implements path handling
cat mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/filesystem/DefaultToolFileSystem.kt

Repository: phodal/auto-dev

Length of output: 180


🏁 Script executed:

# Check if there are platform-specific implementations of path joining
find . -name "*.kt" -type f | xargs grep -l "resolve\|joinPath\|File\.separator" | head -15

Repository: phodal/auto-dev

Length of output: 1358


🏁 Script executed:

# Check how Path is being used in resolvePath and if it normalizes separators correctly
grep -A5 "Path(" mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/filesystem/ToolFileSystem.kt | head -20

Repository: phodal/auto-dev

Length of output: 655


🏁 Script executed:

# Verify that the issue affects the actual file operations
grep -B2 -A2 "fileSystem\." mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt | head -30

Repository: phodal/auto-dev

Length of output: 925


Use platform-independent path joining instead of hardcoded "/" separator.

Lines 46, 194, and 533 use hardcoded "/" for path construction. Since this is multiplatform code (commonMain) targeting JVM, Android, iOS, and other platforms, paths should use the available fileSystem.resolvePath() method which handles platform separators correctly via kotlinx.io.

Replace:

  • Line 46: "$projectPath/.xiuper/run-configs.json" with fileSystem.resolvePath(".xiuper/run-configs.json")
  • Line 533: "$projectPath/.xiuper" with fileSystem.resolvePath(".xiuper")
  • Line 194: "$projectPath/${config.workingDir}" with fileSystem.resolvePath(config.workingDir)
🤖 Prompt for AI Agents
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
lines 46, 194, 533: replace hardcoded path string interpolation that uses "/"
separators with platform-independent fileSystem.resolvePath(...) calls — change
the configStoragePath to use fileSystem.resolvePath(".xiuper/run-configs.json"),
change any "$projectPath/${config.workingDir}" usage to
fileSystem.resolvePath(config.workingDir), and change "$projectPath/.xiuper" to
fileSystem.resolvePath(".xiuper"); ensure the existing fileSystem instance is in
scope (or inject/rename it consistently) so these calls compile across targets.

Comment on lines +109 to +117
when (event) {
is AnalysisEvent.Progress -> onProgress(event.message)
is AnalysisEvent.Complete -> discoveredConfigs = event.configs
is AnalysisEvent.Error -> {
logger.warn { "LLM analysis failed: ${event.message}" }
_errorMessage.value = event.message
}
}
}
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

State not updated to ERROR on analysis failure.

When AnalysisEvent.Error is received, only _errorMessage is set. The _state remains ANALYZING because no exception is thrown. This can leave the UI stuck in the analyzing state indefinitely.

🔎 Proposed fix
                     is AnalysisEvent.Error -> {
                         logger.warn { "LLM analysis failed: ${event.message}" }
                         _errorMessage.value = event.message
+                        _state.value = RunConfigState.ERROR
                     }
🤖 Prompt for AI Agents
In
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
around lines 109 to 117, the handler for AnalysisEvent.Error only sets
_errorMessage and leaves _state as ANALYZING; update the state to the
appropriate error state so the UI won't remain stuck. In the AnalysisEvent.Error
branch set _state.value = RunConfigState.ERROR (or the project's equivalent
error enum/state) in addition to logging and setting _errorMessage, ensuring any
necessary cleanup or flags are updated so analysis is no longer considered
ongoing.

Comment on lines 330 to 366
var cancelled = false

// Read stdout in a separate thread
val stdoutReader = Thread {
try {
process.inputStream.bufferedReader().use { reader ->
val buffer = CharArray(1024)
while (!cancelled) {
val charsRead = reader.read(buffer)
if (charsRead == -1) break
val chunk = String(buffer, 0, charsRead)
outputBuilder.append(chunk)
onOutput(chunk)
}
}
} catch (e: Exception) {
// Stream closed, ignore
}
}.apply { start() }

// Read stderr in a separate thread
val stderrReader = Thread {
try {
process.errorStream.bufferedReader().use { reader ->
val buffer = CharArray(1024)
while (!cancelled) {
val charsRead = reader.read(buffer)
if (charsRead == -1) break
val chunk = String(buffer, 0, charsRead)
outputBuilder.append(chunk)
onOutput(chunk)
}
}
} catch (e: Exception) {
// Stream closed, ignore
}
}.apply { start() }
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

Thread visibility issue with cancelled flag.

The cancelled variable is accessed from multiple threads without synchronization. Changes made by the main thread may not be visible to the reader threads due to CPU caching.

🔎 Proposed fix - use @volatile or AtomicBoolean
-        var cancelled = false
+        val cancelled = java.util.concurrent.atomic.AtomicBoolean(false)
         
         // Read stdout in a separate thread
         val stdoutReader = Thread {
             try {
                 process.inputStream.bufferedReader().use { reader ->
                     val buffer = CharArray(1024)
-                    while (!cancelled) {
+                    while (!cancelled.get()) {
                         val charsRead = reader.read(buffer)
                         if (charsRead == -1) break
                         val chunk = String(buffer, 0, charsRead)
                         outputBuilder.append(chunk)
                         onOutput(chunk)
                     }
                 }
             } catch (e: Exception) {
                 // Stream closed, ignore
             }
         }.apply { start() }
         
         // Read stderr in a separate thread
         val stderrReader = Thread {
             try {
                 process.errorStream.bufferedReader().use { reader ->
                     val buffer = CharArray(1024)
-                    while (!cancelled) {
+                    while (!cancelled.get()) {
                         val charsRead = reader.read(buffer)
                         if (charsRead == -1) break
                         ...
                     }
                 }
             } catch (e: Exception) {
                 // Stream closed, ignore
             }
         }.apply { start() }
         
         // ... later in catch block:
         } catch (e: kotlinx.coroutines.CancellationException) {
-            cancelled = true
+            cancelled.set(true)
             // Interrupt readers

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
around lines 330 to 366, the local var `cancelled` is accessed by multiple
threads without synchronization which can cause visibility issues; change `var
cancelled = false` to a thread-safe flag (either declare it as `@Volatile
private var cancelled = false` or replace it with an `AtomicBoolean(false)`),
update reader loops to check `cancelled.get()` if using AtomicBoolean (or keep
`while (!cancelled)` if volatile), and ensure any code that sets cancellation
uses `cancelled = true` for volatile or `cancelled.set(true)` for AtomicBoolean
so the reader threads reliably observe the change.

Comment on lines +484 to +518
fun setDefaultConfig(configId: String) {
val config = _configs.value.find { it.id == configId }
if (config != null) {
// Update default flags
_configs.value = _configs.value.map { c ->
c.copy(isDefault = c.id == configId)
}
_defaultConfig.value = config.copy(isDefault = true)
}
}

/**
* Add a custom run configuration
*/
fun addConfig(config: RunConfig) {
_configs.value = _configs.value + config.copy(source = RunConfigSource.USER_DEFINED)
if (config.isDefault || _defaultConfig.value == null) {
_defaultConfig.value = config
}
_state.value = RunConfigState.CONFIGURED
}

/**
* Remove a run configuration
*/
fun removeConfig(configId: String) {
_configs.value = _configs.value.filter { it.id != configId }
if (_defaultConfig.value?.id == configId) {
_defaultConfig.value = _configs.value.firstOrNull { it.isDefault }
?: _configs.value.firstOrNull()
}
if (_configs.value.isEmpty()) {
_state.value = RunConfigState.NOT_CONFIGURED
}
}
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

Config modifications are not persisted.

setDefaultConfig(), addConfig(), and removeConfig() update in-memory state but don't call saveConfigs(). User changes will be lost on application restart.

🔎 Proposed fix - persist changes

Since saveConfigs() is a suspend function, these methods need to be converted to suspend functions or trigger persistence asynchronously:

-    fun setDefaultConfig(configId: String) {
+    suspend fun setDefaultConfig(configId: String) {
         val config = _configs.value.find { it.id == configId }
         if (config != null) {
             // Update default flags
             _configs.value = _configs.value.map { c ->
                 c.copy(isDefault = c.id == configId)
             }
             _defaultConfig.value = config.copy(isDefault = true)
+            saveConfigs()
         }
     }
     
-    fun addConfig(config: RunConfig) {
+    suspend fun addConfig(config: RunConfig) {
         _configs.value = _configs.value + config.copy(source = RunConfigSource.USER_DEFINED)
         if (config.isDefault || _defaultConfig.value == null) {
             _defaultConfig.value = config
         }
         _state.value = RunConfigState.CONFIGURED
+        saveConfigs()
     }
     
-    fun removeConfig(configId: String) {
+    suspend fun removeConfig(configId: String) {
         _configs.value = _configs.value.filter { it.id != configId }
         if (_defaultConfig.value?.id == configId) {
             _defaultConfig.value = _configs.value.firstOrNull { it.isDefault }
                 ?: _configs.value.firstOrNull()
         }
         if (_configs.value.isEmpty()) {
             _state.value = RunConfigState.NOT_CONFIGURED
         }
+        saveConfigs()
     }
🤖 Prompt for AI Agents
In
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
around lines 484 to 518, the methods setDefaultConfig, addConfig, and
removeConfig mutate in-memory state but do not persist changes via
saveConfigs(), so user changes are lost; convert each method to a suspend
function (or alternatively launch a coroutine) and invoke saveConfigs() after
updating _configs/_defaultConfig/_state, and update all call sites to handle the
suspend signature (or ensure the coroutine is launched on the appropriate scope
and error-handled) so modifications are persisted.

Comment on lines +28 to +39
class RunConfigViewModel(
projectPath: String,
llmService: KoogLLMService? = null
) {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

private val service = RunConfigService(
projectPath = projectPath,
fileSystem = DefaultToolFileSystem(projectPath = projectPath),
shellExecutor = DefaultShellExecutor(),
llmService = llmService
)
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

Missing CoroutineScope cancellation may cause resource leaks.

The scope CoroutineScope is created with SupervisorJob() but there's no mechanism to cancel it when the ViewModel is no longer needed. In a Compose context, consider using rememberCoroutineScope() at the call site or implementing a dispose()/close() method that cancels the scope.

🔎 Proposed fix: Add cleanup method
 class RunConfigViewModel(
     projectPath: String,
     llmService: KoogLLMService? = null
 ) {
     private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+    
+    /**
+     * Cancel all ongoing operations and cleanup resources.
+     * Should be called when the ViewModel is no longer needed.
+     */
+    fun dispose() {
+        scope.cancel()
+    }
🤖 Prompt for AI Agents
In
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/RunConfigViewModel.kt
around lines 28–39, the CoroutineScope named scope is created with
SupervisorJob() but never cancelled; add a public cleanup method (e.g.,
close()/dispose()/onCleared()) that calls scope.cancel() (and any
Service/Executor cleanup if available) to avoid leaking coroutines, or
alternatively accept an external CoroutineScope at construction so lifecycle is
managed by the caller; ensure the new method is called from the Compose host or
where the ViewModel is disposed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements dynamic run configuration generation using AI analysis, introducing an IDE-like run button interface for executing project commands. The implementation includes LLM-based project analysis with streaming output, real-time command execution with live output streaming, and process lifecycle management with stop/cancel functionality.

Key Changes:

  • LLM-based run configuration analysis with streaming output to provide real-time feedback
  • IDE-style floating run button with state management (Configure, Analyzing, Run/Stop, Error)
  • Real-time command execution supporting long-running processes with incremental output streaming
  • RunOutputDock component for displaying both AI analysis logs and command execution output

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
RunConfig.kt Data models for run configurations including types, sources, states, and storage format
LLMRunConfigAnalyzer.kt LLM-based analyzer for discovering run configurations with streaming support
RunConfigService.kt Service layer managing config lifecycle, execution, and process control
RunConfigViewModel.kt ViewModel exposing state flows and handling user interactions
RunOutputDock.kt Terminal-like UI component for displaying output with platform-specific rendering
RunButton.kt Dropdown button component showing run configurations with grouping and state
FloatingRunButton.kt Floating action button providing quick access to run configurations
CodingAgentPage.kt Integration of run config features into the main coding agent UI
SlashCommandProcessor.ts Added /gen-actions command for generating IDE actions
AutoDevComposeIcons.kt Added Science and Extension icons for GenAction support
TopBarMenu.kt Updated comment clarifying WASM-only usage

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

val suggestions = json.decodeFromString<List<LLMRunConfigSuggestion>>(jsonStr)
suggestions.mapIndexed { index, suggestion ->
RunConfig(
id = "ai-${suggestion.name.lowercase().replace(Regex("[^a-z0-9]"), "-")}-$index",
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The ID generation uses a simple concatenation with the index, which could lead to collisions if the same name is analyzed multiple times or if names are very similar. Consider using a more robust ID generation strategy, such as including a timestamp or UUID component to ensure uniqueness across multiple analysis runs.

Copilot uses AI. Check for mistakes.
Comment on lines +196 to +199

val shellConfig = ShellExecutionConfig(
workingDirectory = workDir,
timeoutMs = 600000L, // 10 minutes for long-running tasks
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The timeout is set to 10 minutes (600000ms) for all commands. However, some long-running server processes like Spring Boot's bootRun may need to run indefinitely. The timeout should either be configurable per RunConfig, or there should be a way to specify that certain commands should not have a timeout. Consider adding a timeout field to RunConfig or using a special value (like -1 or 0) to indicate no timeout.

Suggested change
val shellConfig = ShellExecutionConfig(
workingDirectory = workDir,
timeoutMs = 600000L, // 10 minutes for long-running tasks
// Determine timeout per run config, defaulting to 10 minutes.
// If RUN_CONFIG_TIMEOUT_MS is set to 0 or a negative value, it will be
// passed through to ShellExecutionConfig to indicate no timeout.
val timeoutMs = config.env["RUN_CONFIG_TIMEOUT_MS"]?.toLongOrNull()
?: 600000L
val shellConfig = ShellExecutionConfig(
workingDirectory = workDir,
timeoutMs = timeoutMs,

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +84
if (inJsonBlock && chunk.contains("```") && !chunk.contains("```json")) {
// JSON block might have ended, but keep inJsonBlock true to avoid further text
}
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

This logic has a potential issue where if the JSON block contains nested backticks or the pattern "```" appears in comments or strings, it may incorrectly mark the end of the JSON block. This could cause JSON parsing to fail. Consider using a more robust JSON extraction approach, such as counting opening and closing braces to identify complete JSON objects.

Copilot uses AI. Check for mistakes.
Comment on lines +235 to +242
val jsonPattern = Regex("```json\\s*([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
val match = jsonPattern.find(response)

val jsonStr = match?.groupValues?.get(1)?.trim()
?: run {
// Try to find raw JSON array
val arrayPattern = Regex("\\[\\s*\\{[\\s\\S]*?}\\s*]")
arrayPattern.find(response)?.value
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

This regex pattern will only match if there's no closing backticks after the JSON block, which is inconsistent with the JSON extraction logic. The pattern should consistently handle the markdown code fence format. Consider revising to handle both complete and incomplete JSON blocks more reliably.

Suggested change
val jsonPattern = Regex("```json\\s*([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
val match = jsonPattern.find(response)
val jsonStr = match?.groupValues?.get(1)?.trim()
?: run {
// Try to find raw JSON array
val arrayPattern = Regex("\\[\\s*\\{[\\s\\S]*?}\\s*]")
arrayPattern.find(response)?.value
// Match a ```json fenced block, allowing for a missing closing fence by stopping at ``` or end of input.
val jsonPattern = Regex("```json\\s*([\\s\\S]*?)(```|$)", RegexOption.IGNORE_CASE)
val match = jsonPattern.find(response)
val jsonStr = match?.groupValues?.get(1)?.trim()
?: run {
// Try to find raw JSON array, either bare or inside a (possibly incomplete) ```json fence
val arrayPattern = Regex(
"```json\\s*(\\[\\s*\\{[\\s\\S]*?}\\s*])(```|$)|\\[\\s*\\{[\\s\\S]*?}\\s*]",
RegexOption.IGNORE_CASE
)
val arrayMatch = arrayPattern.find(response)
// Prefer the captured array (group 1, fenced case); otherwise fall back to the full match
arrayMatch?.groups?.get(1)?.value?.trim()
?: arrayMatch?.value?.trim()

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +48
modifier: Modifier = Modifier
) {
val isAnalysis = title.contains("Analysis", ignoreCase = true)
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

This determines whether the title represents analysis output or run output by checking if the title contains "Analysis" (case-insensitive). This is fragile and could break if the title is changed or localized. Consider passing an explicit boolean flag or enum parameter to indicate the type of output being displayed instead of relying on string matching.

Suggested change
modifier: Modifier = Modifier
) {
val isAnalysis = title.contains("Analysis", ignoreCase = true)
modifier: Modifier = Modifier,
isAnalysis: Boolean = title.contains("Analysis", ignoreCase = true),
) {

Copilot uses AI. Check for mistakes.
Comment on lines 272 to 273
private var currentProcess: Process? = null
private var executionJob: kotlinx.coroutines.Job? = null
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

These mutable properties are used to manage the lifecycle of running processes. However, they are not thread-safe and could lead to race conditions in concurrent scenarios. Consider using atomic references or synchronization mechanisms to ensure thread safety, especially since the execution and stopping of processes may happen from different coroutine contexts.

Copilot uses AI. Check for mistakes.
Comment on lines 333 to 366
val stdoutReader = Thread {
try {
process.inputStream.bufferedReader().use { reader ->
val buffer = CharArray(1024)
while (!cancelled) {
val charsRead = reader.read(buffer)
if (charsRead == -1) break
val chunk = String(buffer, 0, charsRead)
outputBuilder.append(chunk)
onOutput(chunk)
}
}
} catch (e: Exception) {
// Stream closed, ignore
}
}.apply { start() }

// Read stderr in a separate thread
val stderrReader = Thread {
try {
process.errorStream.bufferedReader().use { reader ->
val buffer = CharArray(1024)
while (!cancelled) {
val charsRead = reader.read(buffer)
if (charsRead == -1) break
val chunk = String(buffer, 0, charsRead)
outputBuilder.append(chunk)
onOutput(chunk)
}
}
} catch (e: Exception) {
// Stream closed, ignore
}
}.apply { start() }
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

Threads are created but there's no error handling if the threads fail to start or encounter issues during execution. Additionally, using daemon threads or properly managing thread lifecycle would be more appropriate here. Consider using Kotlin's coroutine-based approach with Dispatchers.IO instead of creating raw threads, which would provide better resource management and error handling.

Copilot uses AI. Check for mistakes.
Comment on lines 381 to 382
success = true,
message = "Process started (may still be running)",
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The timeout handling here is potentially confusing. When the timeout is reached, the method returns success=true with a message "Process started (may still be running)", but this doesn't actually indicate success of the command execution. This could mislead users into thinking the command completed successfully when it actually timed out. Consider returning a different status or adding a field to RunConfigResult to distinguish between completed, timed out, and still running states.

Suggested change
success = true,
message = "Process started (may still be running)",
success = false,
message = "Process did not complete within timeout (may still be running)",

Copilot uses AI. Check for mistakes.
println("[RunConfigViewModel] Starting project analysis...")

service.analyzeProject { progress ->
progressMessage = progress.take(50)
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The progress message is truncated to 50 characters, which may result in incomplete or misleading information being displayed to the user. This arbitrary limit could cut off important context. Consider either increasing this limit to a more reasonable value, or using a different truncation strategy that preserves complete words or sentences.

Copilot uses AI. Check for mistakes.
}
HorizontalDivider()
// Dynamic height based on content type
val dockHeight = if (isAnalysis) 220.dp else 180.dp
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

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

The fixed height values (220.dp for analysis, 180.dp for run output) may not be appropriate for all screen sizes or content volumes. Consider making the height responsive or configurable, or using a percentage of the available space. This would provide better flexibility across different device sizes and user preferences.

Copilot uses AI. Check for mistakes.
- Implement platform-specific ProcessOutputReader for JVM, Android, iOS, JS, and WASM
- Add TooltipWrapper component with desktop tooltip support and fallback on other platforms
- Refactor RunConfigService to use platform-specific process handling
- Replace direct Process usage with platform-agnostic processHandle
- Add process timeout handling and cancellation support across platforms
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (9)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt (5)

46-46: Use platform-independent path joining instead of hardcoded "/" separator.

This is multiplatform code targeting JVM, Android, iOS, JS, and WASM. The hardcoded "/" separator may cause issues on platforms with different path conventions. Use fileSystem.resolvePath() or a similar platform-agnostic approach.

Also applies to line 194 ("$projectPath/${config.workingDir}") and line 441 ("$projectPath/.xiuper").


109-117: State not updated to ERROR on analysis failure.

When AnalysisEvent.Error is received, only _errorMessage is set but _state remains ANALYZING. This leaves the UI stuck in the analyzing state indefinitely.

🔎 Proposed fix
                     is AnalysisEvent.Error -> {
                         logger.warn { "LLM analysis failed: ${event.message}" }
                         _errorMessage.value = event.message
+                        _state.value = RunConfigState.ERROR
                     }

392-426: Config modifications are not persisted.

setDefaultConfig(), addConfig(), and removeConfig() update in-memory state but don't call saveConfigs(). User changes will be lost on application restart.

🔎 Proposed fix - launch coroutine for persistence
+    private val serviceScope = kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.Default)
+    
     fun setDefaultConfig(configId: String) {
         val config = _configs.value.find { it.id == configId }
         if (config != null) {
             _configs.value = _configs.value.map { c ->
                 c.copy(isDefault = c.id == configId)
             }
             _defaultConfig.value = config.copy(isDefault = true)
+            serviceScope.launch { saveConfigs() }
         }
     }
     
     fun addConfig(config: RunConfig) {
         _configs.value = _configs.value + config.copy(source = RunConfigSource.USER_DEFINED)
         if (config.isDefault || _defaultConfig.value == null) {
             _defaultConfig.value = config
         }
         _state.value = RunConfigState.CONFIGURED
+        serviceScope.launch { saveConfigs() }
     }
     
     fun removeConfig(configId: String) {
         _configs.value = _configs.value.filter { it.id != configId }
         if (_defaultConfig.value?.id == configId) {
             _defaultConfig.value = _configs.value.firstOrNull { it.isDefault }
                 ?: _configs.value.firstOrNull()
         }
         if (_configs.value.isEmpty()) {
             _state.value = RunConfigState.NOT_CONFIGURED
         }
+        serviceScope.launch { saveConfigs() }
     }

197-201: Consider making timeout configurable per RunConfig.

The hardcoded 10-minute timeout may not be appropriate for all commands. Long-running server processes like Spring Boot's bootRun may need to run indefinitely. Consider adding a timeoutMs field to RunConfig or using an environment variable to allow customization.


340-347: Returning success = true on exception is misleading.

When an exception occurs (timeout or other error), returning success = true with "Process may still be running" doesn't accurately convey the result. This can mislead callers into thinking the operation succeeded when it actually failed or timed out.

🔎 Proposed fix
         } catch (e: Exception) {
             // Timeout or other error - process may still be running
             onOutput("\n[INFO] ${e.message}\n")
             return RunConfigResult(
-                success = true,
-                message = "Process may still be running"
+                success = false,
+                message = "Process may still be running: ${e.message}",
+                error = e.message
             )
         }
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.jvm.kt (2)

21-39: Thread visibility issue with cancelled flag.

The cancelled variable is accessed from multiple threads (main coroutine and reader threads) without synchronization. Changes made by the main thread may not be visible to the reader threads due to CPU caching.

🔎 Proposed fix - use AtomicBoolean
-    var cancelled = false
+    val cancelled = java.util.concurrent.atomic.AtomicBoolean(false)
     
     // Read stdout in a separate thread
     val stdoutReader = Thread {
         try {
             process.inputStream.bufferedReader().use { reader ->
                 val buffer = CharArray(1024)
-                while (!cancelled) {
+                while (!cancelled.get()) {
                     val charsRead = reader.read(buffer)
                     // ...
                 }
             }
         } catch (_: Exception) {
             // Stream closed, ignore
         }
-    }.apply { start() }
+    }.apply { 
+        isDaemon = true  // Prevent thread from blocking JVM shutdown
+        start() 
+    }

67-76: Returning success = true on timeout is misleading.

When the process doesn't complete within the timeout, returning success = true with "may still be running" doesn't accurately convey the result. The caller cannot distinguish between a successful completion and a timeout.

🔎 Proposed fix
         if (!completed) {
             // Process still running after timeout - report but don't kill
             onOutput("\n[INFO] Process still running after ${timeoutMs / 1000}s timeout.\n")
             onProcessStarted(process.pid().toInt())
             return@withContext RunConfigResult(
-                success = true,
+                success = false,
-                message = "Process started (may still be running)",
+                message = "Process did not complete within timeout (may still be running)",
+                error = "Timeout after ${timeoutMs}ms",
                 pid = process.pid().toInt()
             )
         }
mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.android.kt (2)

21-39: Same thread visibility issue with cancelled flag as JVM implementation.

The cancelled variable is accessed from multiple threads without synchronization. Consider using AtomicBoolean for thread-safe access, and marking threads as daemon to prevent leaks.


67-81: Same timeout handling issue - returning success = true on timeout.

Similar to the JVM implementation, returning success = true when the process times out is misleading. The reflective pid lookup (lines 70-74) is a good approach for Android API compatibility.

🧹 Nitpick comments (4)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.kt (1)

7-19: LGTM! Well-documented cross-platform API.

The expect declaration correctly establishes the cross-platform API surface, and the documentation clearly explains the platform-specific behavior differences.

Optional: Consider default parameter values for better ergonomics

The API could be more ergonomic with default values:

 @Composable
 expect fun TooltipWrapper(
     tooltip: @Composable () -> Unit,
-    tooltipOffset: DpOffset,
-    delayMillis: Int,
-    modifier: Modifier,
+    tooltipOffset: DpOffset = DpOffset.Zero,
+    delayMillis: Int = 500,
+    modifier: Modifier = Modifier,
     content: @Composable () -> Unit
 )

This would reduce boilerplate for callers on platforms where tooltip parameters are ignored.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt (1)

77-128: Consider conditionally rendering TooltipWrapper.

On JVM, the TooltipArea may display an empty tooltip when analysisLog is blank. Consider wrapping the TooltipWrapper conditionally or passing an empty composable when there's no log.

🔎 Proposed conditional tooltip rendering
-                TooltipWrapper(
-                    tooltip = {
-                        if (analysisLog.isNotBlank()) {
+                if (analysisLog.isNotBlank()) {
+                    TooltipWrapper(
+                        tooltip = {
                             Box(
                                 modifier = Modifier
                                     .widthIn(max = 320.dp)
@@ -104,10 +104,7 @@
                                     color = MaterialTheme.colorScheme.inverseOnSurface
                                 )
                             }
-                        }
-                    },
-                    tooltipOffset = DpOffset(0.dp, 16.dp),
-                    delayMillis = 300,
+                        },
+                        tooltipOffset = DpOffset(0.dp, 16.dp),
+                        delayMillis = 300,
                         modifier = Modifier
-                ) {
+                    ) {
                         SmallFloatingActionButton(
                             onClick = {},
@@ -121,5 +118,17 @@
                             )
                         }
                     }
+                } else {
+                    SmallFloatingActionButton(
+                        onClick = {},
+                        containerColor = AutoDevColors.Signal.info.copy(alpha = 0.15f)
+                    ) {
+                        Icon(
+                            imageVector = AutoDevComposeIcons.Sync,
+                            contentDescription = "Analyzing with AI...",
+                            tint = AutoDevColors.Signal.info,
+                            modifier = Modifier.rotate(rotation)
+                        )
+                    }
+                }
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt (1)

270-272: Mutable state fields accessed from multiple coroutine contexts without synchronization.

currentSession, currentProcessHandle, and executionJob are modified in execute() and read/cleared in stopRunning(). Without synchronization, there's a risk of race conditions if stopRunning() is called while execute() is in progress.

Consider using AtomicReference or Mutex-protected access for thread safety.

mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.android.kt (1)

7-101: Consider extracting shared logic with JVM implementation.

The readProcessOutputPlatform implementation is nearly identical to the JVM version (only pid reflection differs). Consider extracting shared logic to reduce duplication, though this may complicate the expect/actual structure.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9deeabb and 13ba206.

📒 Files selected for processing (14)
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.android.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.ios.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.js.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.jvm.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.wasmJs.kt
  • mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.android.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt
  • mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.ios.kt
  • mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.js.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.jvm.kt
  • mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.wasmJs.kt
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use expect/actual pattern for platform-specific code (for example: Platform implementations)
Use concrete classes (not interfaces) and Promise (not Flow) for @JsExport annotations
Avoid emoji and UTF-8 characters in WASM code
When modifying CodingAgentRenderer, update ALL implementations: DefaultCodingAgentRenderer, ComposeRenderer, JewelRenderer, ServerSideRenderer, and JsRendererAdapter in Kotlin
When modifying CodingAgentRenderer, update JVM CLI implementations: CodingCliRenderer and ConsoleRenderer
Use AutoDevColors or MaterialTheme.colorScheme for Compose styling

Files:

  • mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.android.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.kt
  • mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.wasmJs.kt
  • mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.js.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.jvm.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.ios.kt
  • mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.ios.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.wasmJs.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.jvm.kt
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.android.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.js.kt
**/*.{kt,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never use hardcoded colors - always use design tokens from the design system

Files:

  • mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.android.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.kt
  • mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.wasmJs.kt
  • mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.js.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.jvm.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.ios.kt
  • mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.ios.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.wasmJs.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.jvm.kt
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.android.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/RunConfigService.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.js.kt
🧠 Learnings (1)
📚 Learning: 2025-12-07T08:40:10.859Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.859Z
Learning: Applies to **/*.kt : Use `expect`/`actual` pattern for platform-specific code (for example: `Platform` implementations)

Applied to files:

  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.kt
🧬 Code graph analysis (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt (6)
mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.android.kt (1)
  • TooltipWrapper (8-20)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.kt (1)
  • TooltipWrapper (12-19)
mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.ios.kt (1)
  • TooltipWrapper (8-20)
mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.js.kt (1)
  • TooltipWrapper (8-20)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.jvm.kt (1)
  • TooltipWrapper (10-26)
mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.wasmJs.kt (1)
  • TooltipWrapper (8-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test mpp-core (KMP)
  • GitHub Check: Build
🔇 Additional comments (17)
mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.js.kt (1)

8-20: LGTM! Correct JS platform implementation.

The implementation correctly provides a no-op tooltip behavior for the JS platform while maintaining API consistency. The unused parameters are expected and documented.

mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.jvm.kt (1)

10-26: LGTM! Correct JVM/Desktop implementation with full tooltip support.

The implementation correctly leverages TooltipArea to provide actual tooltip functionality on desktop platforms. The use of TooltipPlacement.CursorPoint with the provided offset and all parameter forwarding is appropriate.

mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.wasmJs.kt (1)

8-20: LGTM! Correct WASM platform implementation.

The implementation correctly provides a no-op tooltip behavior for the WASM platform while maintaining API consistency. The unused parameters are expected and documented.

mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.android.kt (1)

8-20: LGTM! Correct Android platform implementation.

The implementation correctly provides a no-op tooltip behavior for Android while maintaining API consistency. The comment noting potential future use of BasicTooltipBox is a helpful note for future improvements.

mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/ui/compose/components/TooltipWrapper.ios.kt (1)

8-20: LGTM! Correct iOS platform implementation.

The implementation correctly provides a no-op tooltip behavior for the iOS platform while maintaining API consistency. The unused parameters are expected and documented.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt (5)

1-36: LGTM! Imports follow guidelines.

The imports correctly use MaterialTheme.colorScheme and AutoDevColors design tokens, and leverage the cross-platform TooltipWrapper component.


38-59: LGTM! Clear function signature.

The function signature is well-documented and follows Compose best practices with proper callback naming and default parameters.


64-75: LGTM! NOT_CONFIGURED state implementation.

Correctly uses MaterialTheme.colorScheme for colors and provides clear affordance with the Settings icon.


130-141: LGTM! ERROR state implementation.

Correctly uses AutoDevColors.Signal.error design tokens and provides clear visual feedback with the Warning icon.


143-156: LGTM! CONFIGURED FAB logic is sound.

The state-dependent icon and color logic correctly reflects whether the default configuration is running, providing clear visual feedback.

mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.js.kt (1)

1-23: LGTM!

The JS platform implementation correctly provides stub implementations that indicate native process execution is not supported, while still providing a functional time provider via Clock.System.now(). The expect/actual pattern is properly used as per coding guidelines.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.kt (1)

1-32: Well-structured expect declarations with clear documentation.

The expect/actual pattern is correctly applied per coding guidelines. The KDoc comments clearly document the expected behavior for each platform, which helps maintainability.

mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.ios.kt (1)

1-23: LGTM!

The iOS platform implementation correctly provides stub implementations consistent with other non-JVM platforms, while providing functional time support via kotlinx-datetime.

mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.jvm.kt (2)

90-95: Cancellation handling is correct but relies on unsynchronized flag.

The cancellation logic correctly propagates the exception after interrupting reader threads. However, setting cancelled = true without synchronization means reader threads may not see the update immediately.


98-111: LGTM for utility functions.

killProcessPlatform, isNativeProcess, and currentTimeMillis are correctly implemented with appropriate error handling.

mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.wasmJs.kt (1)

1-23: LGTM!

The WASM platform implementation follows the same pattern as JS and iOS stubs, correctly indicating that native process execution is not supported while providing functional time utilities.

mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/runconfig/ProcessOutputReader.android.kt (1)

103-116: Android-specific liveness check is appropriate.

Using exitValue() exception to check if the process is alive is a reasonable approach for Android API compatibility where isAlive may not be available on older API levels.

Comment on lines +158 to +206
DropdownMenu(
expanded = menuExpanded,
onDismissRequest = { menuExpanded = false }
) {
// Show all configs (avoid duplicates)
configs.forEach { cfg ->
val running = isRunning && runningConfigId == cfg.id
val isDefault = cfg.id == defaultConfig?.id
val tint = when {
running -> AutoDevColors.Signal.error
isDefault -> AutoDevColors.Signal.success
else -> MaterialTheme.colorScheme.onSurface
}
DropdownMenuItem(
text = {
Text(
text = if (isDefault) "${cfg.name} (default)" else cfg.name,
style = if (isDefault) MaterialTheme.typography.bodyMedium else MaterialTheme.typography.bodySmall
)
},
onClick = {
menuExpanded = false
println("[FloatingRunButton] Running config: ${cfg.name}")
if (running) onStopRunning() else onRunConfig(cfg)
},
leadingIcon = {
Icon(
imageVector = if (running) AutoDevComposeIcons.Stop else AutoDevComposeIcons.PlayArrow,
contentDescription = null,
tint = tint
)
}
)
}

DropdownMenuItem(
text = { Text("Re-analyze Project") },
onClick = {
menuExpanded = false
onConfigure()
},
leadingIcon = {
Icon(
imageVector = AutoDevComposeIcons.Refresh,
contentDescription = null
)
}
)
}
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

Remove debug println statement.

Line 180 contains a debug print statement that should be removed before merging to production.

🔎 Proposed fix
                         onClick = {
                             menuExpanded = false
-                            println("[FloatingRunButton] Running config: ${cfg.name}")
                             if (running) onStopRunning() else onRunConfig(cfg)
                         },
🤖 Prompt for AI Agents
In
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/runconfig/FloatingRunButton.kt
around lines 158 to 206, remove the debug println call on line 180
("println(\"[FloatingRunButton] Running config: ${cfg.name}\")"); delete that
statement and ensure no other side effects are tied to it (if you need to record
this action use the existing logging mechanism instead of println).

@phodal phodal merged commit 991d379 into master Dec 25, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GenAction – Dynamic IDE Action Generation

2 participants