Skip to content

Conversation

@phodal
Copy link
Owner

@phodal phodal commented Dec 9, 2025

Summary

This PR adds a new ChatDB agent type for text-to-SQL interactions, allowing users to connect to databases and use natural language to query them.

Changes

New Files

  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt - Main page with split layout
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt - ViewModel for managing data sources and chat
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt - Left panel for data source management
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt - Dialog for adding/editing data sources
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt - Right panel for chat interactions
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt - Data models for data sources

Modified Files

  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt - Added CHAT_DB agent type
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt - Added routing for ChatDB
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt - Added CHAT_DB to when expression
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuDesktop.kt - Added CHAT_DB icon
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuMobile.kt - Added CHAT_DB icon
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt - Added Database and Schema icons

Features

  • Data Source Management: Add, edit, delete, and connect to database sources
  • Multiple Database Dialects: Support for PostgreSQL, MySQL, SQLite, SQL Server, Oracle, MariaDB
  • Text-to-SQL Chat: Natural language queries converted to SQL using LLM
  • Query Execution: Automatic SQL extraction and execution with result display
  • Schema Display: View connected database schema

Screenshots

(UI implementation - screenshots can be added after testing)

Closes #508

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ChatDB (Text-to-SQL) agent for converting natural language queries to SQL with multi-database support.
    • Implemented data source management UI for creating and configuring database connections.
    • Added SQL validation and automatic revision with error handling.
    • Integrated schema linking to identify relevant database tables for queries.
  • Enhancements

    • Improved agent interface with workspace context support.
    • Fixed database metadata queries to properly scope to current catalog.
  • Style

    • Added Database and Schema icons for UI clarity.

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

Copilot AI review requested due to automatic review settings December 9, 2025 14:35
@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 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

This PR introduces a comprehensive Text2SQL (ChatDB) Agent feature enabling natural language to SQL conversion with schema linking, multi-database support, SQL validation/revision, and optional visualization. It adds new AgentType.CHAT_DB with corresponding UI components, core agent implementations, NLP tokenization, database infrastructure, platform-specific SQL validators, and a multiplatform data source repository with SQLDelight persistence.

Changes

Cohort / File(s) Summary
AgentType Extension
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt
Adds KNOWLEDGE and CHAT_DB enum constants with KDoc, extends getDisplayName() mapping and fromString() parsing to recognize "chatdb" and "database"
ChatDB UI Core Components
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt, ChatDBViewModel.kt, components/ChatDBChatPane.kt, components/DataSourcePanel.kt, components/DataSourceConfigPane.kt, components/ChatDBStepCard.kt
Implements ChatDB page with two-pane layout (data source panel + chat/config pane), view model for state management, chat interface with connection status and message rendering, data source management with CRUD operations, configuration form for database connections, and execution step visualization
ChatDB Data Models
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt
Defines DatabaseDialect enum, DataSourceConfig data class with validation, ChatDBState for UI state management, ChatDBMessage, MessageRole, QueryResultDisplay, ConnectionStatus, and ValidationResult types
ChatDB Core Agent
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt, ChatDBAgentExecutor.kt, MultiDatabaseChatDBAgent.kt, MultiDatabaseChatDBExecutor.kt
Implements Text2SQL agent orchestration, single and multi-database executors with schema linking, SQL generation/validation/revision workflow, and visualization generation
Schema Linking
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt, KeywordSchemaLinker.kt, LlmSchemaLinker.kt, DatabaseContentSchemaLinker.kt, ChatDBModels.kt
Adds abstract SchemaLinker base class with utility functions, keyword-based implementation, LLM-based implementation with fallback, and content-driven implementation using database samples; defines ChatDBTask, ChatDBResult, SchemaLinkingResult data classes
NLP Tokenization
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.kt, FallbackNlpTokenizer.kt, mpp-core/src/{jvmMain,jsMain,iosMain,androidMain,wasmJsMain}/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.*.kt
Introduces multiplatform NLP tokenizer expect/actual pattern; commonMain provides FallbackNlpTokenizer with Porter Stemmer, Chinese BiMM segmentation, RAKE keyword extraction, and mixed-language support; platform implementations delegate to FallbackNlpTokenizer or use platform-specific libraries (MyNLP for JVM)
SQL Validation & Revision
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.kt, SqlReviseAgent.kt, mpp-core/src/{jvmMain,jsMain,iosMain,androidMain,wasmJsMain}/kotlin/cc/unitmesh/agent/subagent/SqlValidator.*.kt
Adds expect/actual SqlValidator interface with platform implementations (JSqlParser for JVM, regex-based for others), and SqlReviseAgent for iterative SQL correction with optional whitelist validation
Database Infrastructure
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt, mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/ExposedDatabaseConnection.kt
Defines DatabaseConnection interface with executeQuery, getSchema, getSampleRows, getDistinctValues; JVM implementation using Exposed framework; QueryResult.toTableString enhanced with pagination support
Data Source Persistence
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt, mpp-ui/src/sqldelight/cc/unitmesh/devins/db/DataSource.sq, mpp-ui/src/{jvmMain,jsMain,iosMain,wasmJsMain}/kotlin/cc/unitmesh/devins/db/DataSourceRepository.*.kt
Adds expect/actual DataSourceRepository CRUD interface; SQLDelight schema for persistence; platform-specific implementations (JVM with SQL, JS/WASM with localStorage, iOS with database, Android stub)
Rendering Infrastructure
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt, RendererModels.kt
Adds renderInfo() and renderChatDBStep() methods to CodingAgentRenderer; introduces ChatDBStepType, ChatDBStepStatus enums and TimelineItem.ChatDBStepItem, TimelineItem.InfoItem for progress tracking
UI Integration
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt, AgentInterfaceRouter.kt, AutoDevApp.kt, ComposeRenderer.kt, AgentMessageList.kt
Extends agent routing to handle CHAT_DB type, adds workspace parameter to router, implements ChatDB rendering in ComposeRenderer with renderInfo/renderChatDBStep overrides, adds conditional session sidebar visibility, updates message list rendering for new timeline items
Desktop & Mobile UI Updates
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuDesktop.kt, TopBarMenuMobile.kt, mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
Adds CHAT_DB case with Database icon mapping in agent type tabs
IDEA Integration
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt, toolwindow/IdeaAgentApp.kt, toolwindow/IdeaComposeIcons.kt, toolwindow/IdeaAgentViewModel.kt
Adds CHAT_DB icon (cyan color) and Database icon definition, placeholder UI for ChatDB agent, refactors agent type persistence to use getDisplayName()
Desktop State & App Integration
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/state/DesktopUiState.kt, mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/app/SessionApp.kt
Adds coroutine-based persistence for agent type preference in DesktopUiState; removes SessionApp.kt entire file (auth flow, project/task management)
CLI & Server Integration
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/ChatDBCli.kt, mpp-ui/build.gradle.kts
Adds ChatDBCli main entry point for testing ChatDB workflow via CLI with database/LLM configuration; adds runChatDBCli Gradle task
LLM Executor Factory
mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt, mpp-core/src/{jvmMain,jsMain,iosMain,wasmJsMain}/kotlin/cc/unitmesh/llm/ExecutorFactory.*.kt
Introduces platform-specific createExecutorBlocking() API for blocking executor creation; JVM uses runBlocking, non-JVM platforms return null with documentation
Database Connection (IDEA)
mpp-idea/mpp-idea-exts/ext-database/src/main/kotlin/cc/unitmesh/database/connection/IdeaDatabaseConnection.kt
Updates package path and createFromIdea() signature to accept DbDataSource object instead of name string
Testing
mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/SchemaLinkerTest.kt, FallbackNlpTokenizerTest.kt, mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgentTest.kt, mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizerTest.kt, JSqlParserValidatorTest.kt, mpp-idea/mpp-idea-core/src/test/kotlin/.../*Test.kt
Adds comprehensive test suites for schema linking, NLP tokenization, SQL revision, SQL validation, and updates existing tests to use Kotlin assertions instead of AssertJ
Build Configuration
mpp-core/build.gradle.kts, mpp-ui/build.gradle.kts, mpp-idea/build.gradle.kts
Adds MyNLP dependency for Chinese tokenization, ChatDB CLI Gradle task, AssertJ test dependency; duplicates runChatDBCli task definition

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as ChatDB UI
    participant VM as ChatDBViewModel
    participant Agent as ChatDBAgent
    participant Executor as ChatDBAgentExecutor
    participant SchemaLinker as SchemaLinker
    participant LLM as LLM Service
    participant Validator as SQL Validator
    participant Reviser as SqlReviseAgent
    participant DB as Database

    User->>UI: Enter natural language query
    UI->>VM: sendMessage(text)
    VM->>Agent: execute(ChatDBTask)
    Agent->>Executor: execute(task)
    
    rect rgb(200, 220, 255)
    Note over Executor,DB: Schema Linking Phase
    Executor->>SchemaLinker: link(query, schema)
    SchemaLinker->>SchemaLinker: extractKeywords(query)
    SchemaLinker->>DB: (optional) fetch schema context
    SchemaLinker-->>Executor: SchemaLinkingResult
    Executor->>VM: renderChatDBStep(SCHEMA_LINKING, SUCCESS)
    end
    
    rect rgb(200, 255, 220)
    Note over Executor,LLM: SQL Generation Phase
    Executor->>Executor: buildInitialUserMessage(task, schema)
    Executor->>LLM: generateSQL(systemPrompt, userMessage)
    LLM-->>Executor: response with SQL
    Executor->>Executor: extractSqlFromResponse(response)
    Executor->>VM: renderChatDBStep(GENERATE_SQL, SUCCESS)
    end
    
    rect rgb(255, 240, 200)
    Note over Executor,Validator: Validation & Revision Phase
    Executor->>Validator: validate(sql)
    alt SQL Invalid
        Executor->>Reviser: execute(SqlRevisionInput)
        Reviser->>LLM: askLLMForRevision(context)
        LLM-->>Reviser: revised sql
        Reviser->>Validator: validate(revisedSql)
        Validator-->>Reviser: result
        Reviser-->>Executor: revised sql or error
        Executor->>VM: renderChatDBStep(REVISE_SQL, SUCCESS)
    else SQL Valid
        Executor->>VM: renderChatDBStep(VALIDATE_SQL, SUCCESS)
    end
    end
    
    rect rgb(255, 220, 220)
    Note over Executor,DB: Execution Phase
    Executor->>DB: executeQuery(sql)
    DB-->>Executor: QueryResult
    Executor->>VM: renderChatDBStep(EXECUTE_SQL, SUCCESS)
    end
    
    rect rgb(220, 220, 255)
    Note over Executor,LLM: Visualization Phase (optional)
    alt generateVisualization flag
        Executor->>Executor: buildTableContext(result)
        Executor->>LLM: generateVisualization(data)
        LLM-->>Executor: PlotDSL code
        Executor->>VM: renderChatDBStep(GENERATE_VISUALIZATION, SUCCESS)
    end
    end
    
    Executor-->>Agent: ChatDBResult
    Agent-->>VM: ToolResult.AgentResult
    VM->>UI: update state with result
    UI->>User: Display SQL, results, visualization
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Schema Linking implementations (SchemaLinker.kt, KeywordSchemaLinker.kt, LlmSchemaLinker.kt, DatabaseContentSchemaLinker.kt) — Multiple competing strategies with LLM integration and fallback chains; ensure correctness of keyword extraction and schema matching logic
  • ChatDB Agent & Executor classes (ChatDBAgent.kt, ChatDBAgentExecutor.kt, MultiDatabaseChatDBAgent.kt, MultiDatabaseChatDBExecutor.kt) — Complex orchestration with iterative loops, SQL extraction/validation/revision workflows; verify error handling and termination conditions
  • NLP Tokenization (FallbackNlpTokenizer.kt) — Substantial NLP logic including Porter Stemmer, Chinese BiMM segmentation, RAKE keyword extraction; validate correctness against test cases
  • SQL Validators (platform-specific implementations) — Multiple regex-based and parser-based approaches across platforms; ensure consistent whitelist and table extraction behavior
  • Multiplatform data source persistence (DataSourceRepository.kt and platform implementations) — Different backends (SQL, localStorage, stub implementations); verify CRUD operations and default handling across platforms
  • SessionApp.kt removal — Entire file deleted; verify no orphaned references or broken imports elsewhere in codebase
  • Gradle configurationrunChatDBCli task defined twice in build.gradle.kts (potential duplicate); MyNLP dependency added for Chinese tokenization
  • Database infrastructure changes (ExposedDatabaseConnection.kt) — Catalog-scoped metadata queries; ensure compatibility with target databases

Possibly related PRs

Poem

🐰 A database whisperer emerges today,
ChatDB hops through schema in its clever way,
From keywords to SQL, through revision's dance,
Multi-database queries get their second chance—
LLM magic meets the table's truth,
Auto-Dev gains wisdom in its youthful youth! 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding a ChatDB agent for text-to-SQL interactions. It is specific, avoids vague terms, and accurately reflects the primary focus of the changeset.
Linked Issues check ✅ Passed The PR substantially implements the core coding requirements from issue #508, including cross-platform database provider infrastructure, JVM/IDEA implementations, schema linking components, SQL validation and revision agents, NLP tokenization, multi-database support, and corresponding UI integration.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives of adding a comprehensive ChatDB agent feature. Modifications to AgentType, routing, UI components, database abstractions, NLP tokenization, SQL validation, and supporting infrastructure are all directly related to implementing text-to-SQL functionality as specified in issue #508.
✨ 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/chatdb-agent

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

@phodal
Copy link
Owner Author

phodal commented Dec 9, 2025

augment review

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. 3 suggestions posted.

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

if (match != null && currentConnection != null) {
val sql = match.groupValues[1].trim()
try {
val result = currentConnection!!.executeQuery(sql)
Copy link

Choose a reason for hiding this comment

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

Consider enforcing read-only execution before running LLM-generated SQL; currently any SQL inside the code fence will be executed, which risks destructive statements despite the prompt guidance. Add a validation (e.g., ensure it starts with SELECT and contains no write/DDL keywords) before calling executeQuery.

🤖 Was this useful? React with 👍 or 👎

dataSources = state.dataSources.filter { it.id != id },
selectedDataSourceId = if (state.selectedDataSourceId == id) null else state.selectedDataSourceId
)
if (state.selectedDataSourceId == id) {
Copy link

Choose a reason for hiding this comment

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

The check for state.selectedDataSourceId == id happens after you set it to null, so disconnect() won’t run when deleting the currently selected source; check before updating state or cache the previous selection to ensure the connection is closed. This avoids leaking an open DB connection (also relates to resource cleanup) (Guideline: no_memory_leaks).

🤖 Was this useful? React with 👍 or 👎

@Composable
fun ChatDBPage(
workspace: Workspace,
llmService: KoogLLMService?,
Copy link

Choose a reason for hiding this comment

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

llmService parameter is currently unused; the ViewModel creates its own instance instead. Consider threading the provided llmService through to avoid duplicate initialization and ensure consistent configuration.

🤖 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: 9

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

256-263: Consider extracting duplicated icon mapping logic.

The same AgentType -> Icon mapping appears in three places now (desktop tab, mobile header, mobile submenu). Consider extracting a shared helper function to reduce duplication and ensure consistency.

// In AutoDevComposeIcons.kt or a shared location
fun AgentType.toIcon(): ImageVector = when (this) {
    AgentType.REMOTE -> AutoDevComposeIcons.Cloud
    AgentType.CODE_REVIEW -> AutoDevComposeIcons.RateReview
    AgentType.KNOWLEDGE -> AutoDevComposeIcons.Article
    AgentType.CODING -> AutoDevComposeIcons.Code
    AgentType.LOCAL_CHAT -> AutoDevComposeIcons.Chat
    AgentType.CHAT_DB -> AutoDevComposeIcons.Database
}
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (2)

23-23: Unused import.

Platform is imported but not used in this file. Consider removing it to keep imports clean.

-import cc.unitmesh.agent.Platform

151-152: Consider handling Connecting and Error states explicitly.

The empty else branch silently ignores ConnectionStatus.Connecting and ConnectionStatus.Error states. While these may be handled elsewhere, the user may benefit from visual feedback in the chat pane during these states (e.g., a "Connecting..." indicator or error message banner).

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt (1)

212-213: Consider providing validation feedback to users.

Currently, the Save button is simply disabled when validation fails. Users may not understand why they can't save. Consider showing inline error messages or validation hints for required fields.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (2)

231-245: Consider adding delete confirmation.

The delete action executes immediately without confirmation. Users could accidentally delete data source configurations. Consider showing a confirmation dialog before deletion.


283-290: CircularProgressIndicator color may not be visible on disabled button.

The indicator uses MaterialTheme.colorScheme.onPrimary, but the button is disabled, which typically reduces the container color opacity. The indicator might be hard to see. Consider using MaterialTheme.colorScheme.onSurface or the primary color instead.

                     CircularProgressIndicator(
                         modifier = Modifier.size(16.dp),
                         strokeWidth = 2.dp,
-                        color = MaterialTheme.colorScheme.onPrimary
+                        color = MaterialTheme.colorScheme.primary
                     )
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt (2)

16-23: Silent fallback to MYSQL may cause confusion.

fromString silently returns MYSQL when no match is found. This could mask data corruption or configuration errors. Consider throwing an exception or returning null to make failures explicit.

     companion object {
-        fun fromString(value: String): DatabaseDialect {
-            return entries.find { 
+        fun fromString(value: String): DatabaseDialect? {
+            return entries.find {
                 it.name.equals(value, ignoreCase = true) || 
                 it.displayName.equals(value, ignoreCase = true) 
-            } ?: MYSQL
+            }
         }
     }

Alternatively, if a default is preferred, add logging or documentation:

fun fromString(value: String): DatabaseDialect {
    return entries.find { ... } 
        ?: MYSQL.also { /* log warning about unknown dialect */ }
}

30-35: Default port doesn't match dialect default.

The default port = 3306 is hardcoded, but this doesn't match all dialects (e.g., PostgreSQL uses 5432). While dialect.defaultPort exists, the class default could mislead. This is minor since the dialog updates the port when dialect changes.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (2)

64-66: Use structured logging instead of println.

println is not suitable for production logging. Consider using a proper logging framework that supports log levels and structured output.

             } catch (e: Exception) {
-                println("[ChatDB] Failed to initialize LLM service: ${e.message}")
+                // TODO: Use proper logging framework
+                // logger.error("[ChatDB] Failed to initialize LLM service", e)
             }

198-199: Pass system prompt as a separate message instead of concatenating with user input.

The streamPrompt method accepts historyMessages: List<Message> which supports proper message roles via MessageRole.SYSTEM. Instead of concatenating the system prompt with the user text (userPrompt = "$systemPrompt\n\nUser: $text"), pass it as a separate message:

service.streamPrompt(
    userPrompt = text,
    historyMessages = listOf(Message(role = MessageRole.SYSTEM, content = systemPrompt)),
    compileDevIns = false
).collect { chunk ->
    // ...
}

This allows the LLM to properly distinguish system instructions from user queries, following the standard message role pattern already implemented in KoogLLMService.buildPrompt.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7ea233 and 5229c24.

📒 Files selected for processing (12)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt (4 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt (2 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuDesktop.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuMobile.kt (2 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt (1 hunks)
🧰 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/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuDesktop.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuMobile.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.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/agent/chatdb/components/ChatDBChatPane.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuDesktop.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuMobile.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
🧠 Learnings (3)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AgentInterfaceRouter.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AgentChatInterface.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AgentChatInterface.kt
🧬 Code graph analysis (3)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (2)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt (1)
  • AgentMessageList (31-158)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (1)
  • sendMessage (177-221)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (3)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (1)
  • DataSourcePanel (23-82)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1)
  • ChatDBChatPane (33-78)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt (1)
  • DataSourceConfigDialog (23-221)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (1)
  • ChatDBPage (22-126)
⏰ 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: Test mpp-core (KMP)
  • GitHub Check: Build
🔇 Additional comments (19)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt (1)

116-118: LGTM!

The new Database and Schema icon aliases are well-placed under a clear category comment and follow the established pattern of mapping to Material icons. Using Storage for database and TableChart for schema are semantically appropriate choices.

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

283-290: LGTM!

The CHAT_DB icon mapping is consistent with other agent type mappings in the when expression. Uses the design token AutoDevComposeIcons.Database as per coding guidelines.

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

221-228: LGTM!

The CHAT_DB icon mapping is correctly added for the Agent Type header display, consistent with the desktop implementation.

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

6-9: LGTM on new imports.

The imports for ChatDBPage and Workspace are necessary for the new routing logic.

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

35-38: LGTM!

The CHAT_DB enum constant is well-documented and follows the established pattern. The KDoc clearly describes its purpose for text-to-SQL interactions.


45-52: LGTM on display name mapping.

The getDisplayName() extension correctly returns "ChatDB" for the new agent type, maintaining consistency with the feature naming throughout the PR.


54-65: LGTM on string parsing.

The fromString() method handles both "chatdb" and "database" as valid inputs for CHAT_DB, providing flexibility for different input sources.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (2)

264-271: Keyboard handling looks correct.

Enter sends the message, Shift+Enter allows newlines. The implementation correctly prevents send when shift is pressed.


33-78: Well-structured composable layout.

The ChatDBChatPane follows good Compose patterns with clear separation of concerns: status banner, message area with welcome overlay, and input area. The use of MaterialTheme.colorScheme throughout adheres to the design system guidelines.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt (2)

155-170: Password field implementation looks good.

The password field correctly uses PasswordVisualTransformation with a visibility toggle. Not trimming the password value (line 207) is the correct behavior.


199-210: The empty ID is properly handled by the viewModel layer. When DataSourceConfigDialog passes id = "" to onSave, it calls viewModel.addDataSource(), which generates a unique UUID before storing: id = if (config.id.isBlank()) Uuid.random().toString() else config.id. This ensures no empty IDs reach the LazyColumn, which uses key = { it.id } for recomposition. The design correctly separates concerns: UI layer doesn't generate IDs; viewModel ensures uniqueness before state updates.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (2)

128-168: SchemaInfoDialog implementation is clean and functional.

The dialog properly handles null schema, limits display to 10 tables with an overflow indicator, and uses Material theme styling correctly. Good implementation.


31-31: No issue: llmService is not a constructor parameter.

The ChatDBViewModel constructor intentionally takes only the workspace parameter. The llmService is initialized internally via the initializeLLMService() method in the init block, which loads configuration from ConfigManager and creates the service asynchronously. The sendMessage() function properly handles the case where the service is null and displays a user-friendly error message (lines 185-190).

Likely an incorrect or invalid review comment.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (1)

23-82: Well-structured panel component.

The DataSourcePanel follows good practices with clear separation into private composables (DataSourceHeader, SearchField, DataSourceItem, ConnectionControls). Using key = { it.id } in LazyColumn ensures efficient recomposition. The component correctly uses MaterialTheme.colorScheme for all colors per the coding guidelines.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt (2)

92-105: Good use of sealed classes for type-safe state modeling.

ValidationResult and ConnectionStatus are well-designed sealed classes that provide exhaustive pattern matching and clear state representation. This is idiomatic Kotlin.


144-167: ChatDBState is well-structured.

The state class correctly uses derived properties for selectedDataSource and filteredDataSources. For small to moderate data source lists, the on-access computation is acceptable. If performance becomes an issue with large lists, these could be cached.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (3)

118-139: LGTM!

The data source selection and dialog management logic is clean and correctly handles disconnection before switching data sources.


260-263: Redundant state update in stopGeneration().

Setting isGenerating = false is redundant since the cancelled job's finally block already sets it. However, it provides immediate feedback if the cancellation is slow, so this is acceptable.


267-271: LGTM!

The dispose() method properly cleans up all resources: stops generation, disconnects the database, and cancels the coroutine scope.

Comment on lines 200 to 205
AgentType.CODE_REVIEW,
AgentType.KNOWLEDGE -> {
// CODE_REVIEW and DOCUMENT_READER have their own full-page interfaces
AgentType.KNOWLEDGE,
AgentType.CHAT_DB -> {
// CODE_REVIEW, DOCUMENT_READER and CHAT_DB have their own full-page interfaces
// They should not reach here - handled by AgentInterfaceRouter
}
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

Fix comment inconsistency.

The comment references "DOCUMENT_READER" but the enum constant is KNOWLEDGE. Update for clarity.

         AgentType.CODE_REVIEW,
         AgentType.KNOWLEDGE,
         AgentType.CHAT_DB -> {
-            // CODE_REVIEW, DOCUMENT_READER and CHAT_DB have their own full-page interfaces
+            // CODE_REVIEW, KNOWLEDGE and CHAT_DB have their own full-page interfaces
             // They should not reach here - handled by AgentInterfaceRouter
         }
🤖 Prompt for AI Agents
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt
around lines 200 to 205: the inline comment mentions "DOCUMENT_READER" which is
inconsistent with the enum constant used (`KNOWLEDGE`); update the comment to
reference `KNOWLEDGE` (or a neutral phrase like "DOCUMENT_READER/KNOWLEDGE" if
both are meaningful) so it matches the code and avoids confusion.

Comment on lines 59 to 69
if (state.connectionStatus is ConnectionStatus.Connected) {
var showSchemaDialog by remember { mutableStateOf(false) }
IconButton(onClick = { showSchemaDialog = true }) {
Icon(AutoDevComposeIcons.Schema, contentDescription = "View Schema")
}
if (showSchemaDialog) {
SchemaInfoDialog(
schema = viewModel.getSchema(),
onDismiss = { showSchemaDialog = false }
)
}
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

State variable showSchemaDialog is scoped inside conditional block.

showSchemaDialog is declared inside the if (state.connectionStatus is ConnectionStatus.Connected) block using remember. This means the state will be recreated each time the condition becomes true again after being false, which could cause unexpected dialog dismissals. Hoist this state to the ChatDBPage level.

+    var showSchemaDialog by remember { mutableStateOf(false) }
+    
     Scaffold(
         topBar = {
             TopAppBar(
                 // ...
                 actions = {
                     // Schema info button when connected
                     if (state.connectionStatus is ConnectionStatus.Connected) {
-                        var showSchemaDialog by remember { mutableStateOf(false) }
                         IconButton(onClick = { showSchemaDialog = true }) {
                             Icon(AutoDevComposeIcons.Schema, contentDescription = "View Schema")
                         }
-                        if (showSchemaDialog) {
-                            SchemaInfoDialog(
-                                schema = viewModel.getSchema(),
-                                onDismiss = { showSchemaDialog = false }
-                            )
-                        }
                     }
                 }
             )
-        },
+        }
+    // ... after Scaffold closing brace
+    if (showSchemaDialog) {
+        SchemaInfoDialog(
+            schema = viewModel.getSchema(),
+            onDismiss = { showSchemaDialog = false }
+        )
+    }

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

🤖 Prompt for AI Agents
In
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt
around lines 59 to 69, the remember-backed state variable showSchemaDialog is
declared inside the conditional that checks ConnectionStatus.Connected which
causes the state to be recreated when the condition toggles; move (hoist) the
declaration of showSchemaDialog to the ChatDBPage composable scope above the
conditional (e.g., before checking state.connectionStatus) so it persists across
recompositions and condition changes, then keep using the same variable inside
the conditional for IconButton and SchemaInfoDialog and update it via the same
onClick/onDismiss handlers.

class ChatDBViewModel(
private val workspace: Workspace
) {
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.

⚠️ Potential issue | 🟠 Major

Compose state updates should occur on the main dispatcher.

The scope uses Dispatchers.Default, but the ViewModel modifies Compose mutableStateOf properties (state, isGenerating) from coroutines launched on this scope. Compose state should be updated from the main thread to avoid potential threading issues.

Consider using Dispatchers.Main for state updates:

-    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

Alternatively, keep Dispatchers.Default for background work but switch to Main when updating state:

withContext(Dispatchers.Main) {
    state = state.copy(...)
}

Comment on lines 141 to 162
fun connect() {
val dataSource = state.selectedDataSource ?: return

scope.launch {
state = state.copy(connectionStatus = ConnectionStatus.Connecting)

try {
val connection = createDatabaseConnection(dataSource.toDatabaseConfig())
if (connection.isConnected()) {
currentConnection = connection
currentSchema = connection.getSchema()
state = state.copy(connectionStatus = ConnectionStatus.Connected)
_notificationEvent.emit("Connected" to "Successfully connected to ${dataSource.name}")
} else {
state = state.copy(connectionStatus = ConnectionStatus.Error("Failed to connect"))
}
} catch (e: Exception) {
state = state.copy(connectionStatus = ConnectionStatus.Error(e.message ?: "Unknown error"))
_notificationEvent.emit("Connection Failed" to (e.message ?: "Unknown error"))
}
}
}
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

Potential race condition with rapid connect() calls.

If connect() is called multiple times rapidly (e.g., user double-clicks), multiple connections could be created concurrently. Only the last connection is stored in currentConnection, leaving others orphaned and unclosed.

Consider cancelling any in-flight connection attempt before starting a new one:

+    private var connectionJob: Job? = null
+
     fun connect() {
         val dataSource = state.selectedDataSource ?: return
 
-        scope.launch {
+        connectionJob?.cancel()
+        connectionJob = scope.launch {
             state = state.copy(connectionStatus = ConnectionStatus.Connecting)
 
             try {
📝 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
fun connect() {
val dataSource = state.selectedDataSource ?: return
scope.launch {
state = state.copy(connectionStatus = ConnectionStatus.Connecting)
try {
val connection = createDatabaseConnection(dataSource.toDatabaseConfig())
if (connection.isConnected()) {
currentConnection = connection
currentSchema = connection.getSchema()
state = state.copy(connectionStatus = ConnectionStatus.Connected)
_notificationEvent.emit("Connected" to "Successfully connected to ${dataSource.name}")
} else {
state = state.copy(connectionStatus = ConnectionStatus.Error("Failed to connect"))
}
} catch (e: Exception) {
state = state.copy(connectionStatus = ConnectionStatus.Error(e.message ?: "Unknown error"))
_notificationEvent.emit("Connection Failed" to (e.message ?: "Unknown error"))
}
}
}
private var connectionJob: Job? = null
fun connect() {
val dataSource = state.selectedDataSource ?: return
connectionJob?.cancel()
connectionJob = scope.launch {
state = state.copy(connectionStatus = ConnectionStatus.Connecting)
try {
val connection = createDatabaseConnection(dataSource.toDatabaseConfig())
if (connection.isConnected()) {
currentConnection = connection
currentSchema = connection.getSchema()
state = state.copy(connectionStatus = ConnectionStatus.Connected)
_notificationEvent.emit("Connected" to "Successfully connected to ${dataSource.name}")
} else {
state = state.copy(connectionStatus = ConnectionStatus.Error("Failed to connect"))
}
} catch (e: Exception) {
state = state.copy(connectionStatus = ConnectionStatus.Error(e.message ?: "Unknown error"))
_notificationEvent.emit("Connection Failed" to (e.message ?: "Unknown error"))
}
}
}
🤖 Prompt for AI Agents
In
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
around lines 141-162, connect() can be invoked concurrently causing multiple
in-flight connection coroutines and orphaned connections; fix by tracking the
active connect Job and cancelling it before starting a new one, and ensure any
previous temporary connection is closed when superseded: add a private var
connectJob: Job? and before scope.launch call connectJob?.cancel(), then assign
connectJob = scope.launch { ... }; additionally, if a new connection succeeds,
close any existing currentConnection before replacing it (and ensure cancelled
coroutine closes its partially created connection in a finally block), or
protect connection assignment with a Mutex to avoid races.

Comment on lines 242 to 258
private suspend fun extractAndExecuteSQL(response: String) {
val sqlPattern = Regex("```sql\\n([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
val match = sqlPattern.find(response)

if (match != null && currentConnection != null) {
val sql = match.groupValues[1].trim()
try {
val result = currentConnection!!.executeQuery(sql)
// Display query result as a new message
renderer.renderLLMResponseStart()
renderer.renderLLMResponseChunk("\n\n**Query Result:**\n```\n${result.toTableString()}\n```")
renderer.renderLLMResponseEnd()
} catch (e: Exception) {
renderer.renderError("Query Error: ${e.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

Critical security risk: No server-side SQL validation before execution.

The system prompt requests "only SELECT queries," but there's no actual validation. A malicious user could craft prompts that trick the LLM into generating destructive SQL (DROP, DELETE, UPDATE, etc.) via prompt injection. This is explicitly mentioned as a requirement in the linked issue: "AST-based SQL validation (prevent destructive statements)."

Add SQL validation before execution:

     private suspend fun extractAndExecuteSQL(response: String) {
         val sqlPattern = Regex("```sql\\n([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
         val match = sqlPattern.find(response)
 
         if (match != null && currentConnection != null) {
             val sql = match.groupValues[1].trim()
+            
+            // Validate SQL is read-only (SELECT only)
+            val normalizedSql = sql.trim().uppercase()
+            if (!normalizedSql.startsWith("SELECT") || 
+                normalizedSql.contains(Regex("\\b(INSERT|UPDATE|DELETE|DROP|TRUNCATE|ALTER|CREATE)\\b"))) {
+                renderer.renderError("Security: Only SELECT queries are allowed")
+                return
+            }
+            
             try {
                 val result = currentConnection!!.executeQuery(sql)

For production, consider AST-based SQL parsing for more robust validation.

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 introduces a new ChatDB agent type that enables text-to-SQL interactions, allowing users to connect to databases and query them using natural language. The implementation includes a full-featured UI with data source management, connection handling, and a chat interface for natural language queries that are automatically converted to SQL and executed.

Key Changes

  • New AgentType: Added CHAT_DB enum value with routing support for database chat functionality
  • Data Source Management: Complete UI for adding, editing, deleting, and connecting to multiple database types (MySQL, PostgreSQL, SQLite, Oracle, SQL Server, MariaDB)
  • Text-to-SQL Chat: LLM-powered natural language to SQL conversion with automatic query extraction and execution, displaying results in formatted tables

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt Added CHAT_DB enum value with display name and fromString mapping
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt Added Database and Schema icons using Material Icons
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuDesktop.kt Added Database icon mapping for CHAT_DB agent type
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuMobile.kt Added Database icon mapping for CHAT_DB agent type (2 locations)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt Defines data models for database configurations, connection status, messages, and UI state
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt Left panel component for managing data source list, search, and connection controls
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt Dialog for adding/editing database connection configurations with dialect-specific fields
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt Right panel chat interface with connection status banner, welcome message, and input area
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt ViewModel managing data sources, database connections, LLM interactions, and SQL execution
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt Main page with split layout, top bar, and schema dialog
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt Added CHAT_DB routing with workspace parameter
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt Added CHAT_DB to the full-page interface exclusion list

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

Comment on lines 244 to 256
val match = sqlPattern.find(response)

if (match != null && currentConnection != null) {
val sql = match.groupValues[1].trim()
try {
val result = currentConnection!!.executeQuery(sql)
// Display query result as a new message
renderer.renderLLMResponseStart()
renderer.renderLLMResponseChunk("\n\n**Query Result:**\n```\n${result.toTableString()}\n```")
renderer.renderLLMResponseEnd()
} catch (e: Exception) {
renderer.renderError("Query Error: ${e.message}")
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The SQL extraction regex pattern uses a case-insensitive flag, but there's a potential security issue: the code only extracts and executes the first SQL block. If the LLM response contains multiple SQL blocks, only the first one will be executed. Consider either executing all found SQL blocks or making it explicit to the user that only the first block will be run.

Suggested change
val match = sqlPattern.find(response)
if (match != null && currentConnection != null) {
val sql = match.groupValues[1].trim()
try {
val result = currentConnection!!.executeQuery(sql)
// Display query result as a new message
renderer.renderLLMResponseStart()
renderer.renderLLMResponseChunk("\n\n**Query Result:**\n```\n${result.toTableString()}\n```")
renderer.renderLLMResponseEnd()
} catch (e: Exception) {
renderer.renderError("Query Error: ${e.message}")
}
val matches = sqlPattern.findAll(response).toList()
if (matches.isNotEmpty() && currentConnection != null) {
renderer.renderLLMResponseStart()
for ((index, match) in matches.withIndex()) {
val sql = match.groupValues[1].trim()
try {
val result = currentConnection!!.executeQuery(sql)
// Display query result as a new message
val label = if (matches.size > 1) "\n\n**Query Result ${index + 1}:**\n" else "\n\n**Query Result:**\n"
renderer.renderLLMResponseChunk("$label```\n${result.toTableString()}\n```")
} catch (e: Exception) {
renderer.renderError("Query Error: ${e.message}")
}
}
renderer.renderLLMResponseEnd()

Copilot uses AI. Check for mistakes.
Comment on lines 104 to 110
state = state.copy(
dataSources = state.dataSources.filter { it.id != id },
selectedDataSourceId = if (state.selectedDataSourceId == id) null else state.selectedDataSourceId
)
if (state.selectedDataSourceId == id) {
disconnect()
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The disconnect operation should also be performed when the selected data source is deleted (line 108-110), but the current implementation calls disconnect() inside the condition that checks if the deleted source was selected. However, since state.selectedDataSourceId is updated before the check on line 108, the condition will never be true. Move the disconnect call before updating the state, or use the id parameter for the comparison.

Suggested change
state = state.copy(
dataSources = state.dataSources.filter { it.id != id },
selectedDataSourceId = if (state.selectedDataSourceId == id) null else state.selectedDataSourceId
)
if (state.selectedDataSourceId == id) {
disconnect()
}
if (state.selectedDataSourceId == id) {
disconnect()
}
state = state.copy(
dataSources = state.dataSources.filter { it.id != id },
selectedDataSourceId = if (state.selectedDataSourceId == id) null else state.selectedDataSourceId
)

Copilot uses AI. Check for mistakes.
renderer.renderLLMResponseStart()

service.streamPrompt(
userPrompt = "$systemPrompt\n\nUser: $text",
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The system prompt is included in every user message (line 199), which increases token usage and costs significantly. Consider using a proper conversation history structure where the system prompt is sent once at the beginning, or use the LLM service's system message parameter if available.

Suggested change
userPrompt = "$systemPrompt\n\nUser: $text",
userPrompt = text,
systemPrompt = systemPrompt,

Copilot uses AI. Check for mistakes.
Comment on lines +110 to +119
@Serializable
data class ChatDBMessage(
val id: String,
val role: MessageRole,
val content: String,
val timestamp: Long,
val sql: String? = null,
val queryResult: QueryResultDisplay? = null
)

Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The ChatDBMessage data class (lines 111-118) is defined but never used in the codebase. The chat functionality uses ComposeRenderer which has its own message representation. Consider removing this unused model or implementing proper message persistence if it's intended for future use.

Suggested change
@Serializable
data class ChatDBMessage(
val id: String,
val role: MessageRole,
val content: String,
val timestamp: Long,
val sql: String? = null,
val queryResult: QueryResultDisplay? = null
)

Copilot uses AI. Check for mistakes.
Comment on lines 70 to 116
private fun loadDataSources() {
// TODO: Load from persistent storage
// For now, use empty list
state = state.copy(dataSources = emptyList())
}

@OptIn(ExperimentalUuidApi::class)
fun addDataSource(config: DataSourceConfig) {
val newConfig = config.copy(
id = if (config.id.isBlank()) Uuid.random().toString() else config.id,
createdAt = kotlinx.datetime.Clock.System.now().toEpochMilliseconds(),
updatedAt = kotlinx.datetime.Clock.System.now().toEpochMilliseconds()
)
state = state.copy(
dataSources = state.dataSources + newConfig,
isConfigDialogOpen = false,
editingDataSource = null
)
saveDataSources()
}

fun updateDataSource(config: DataSourceConfig) {
val updated = config.copy(
updatedAt = kotlinx.datetime.Clock.System.now().toEpochMilliseconds()
)
state = state.copy(
dataSources = state.dataSources.map { if (it.id == config.id) updated else it },
isConfigDialogOpen = false,
editingDataSource = null
)
saveDataSources()
}

fun deleteDataSource(id: String) {
state = state.copy(
dataSources = state.dataSources.filter { it.id != id },
selectedDataSourceId = if (state.selectedDataSourceId == id) null else state.selectedDataSourceId
)
if (state.selectedDataSourceId == id) {
disconnect()
}
saveDataSources()
}

private fun saveDataSources() {
// TODO: Persist to storage
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

Data source configurations are not persisted to storage (TODOs on lines 71, 115). This means users will lose all their configured database connections when the application restarts. Consider implementing persistence using ConfigManager or a similar storage mechanism before releasing this feature, as losing database configurations on restart significantly impacts usability.

Copilot uses AI. Check for mistakes.
onNotification = onNotification
)
} else {
// Fallback to coding if workspace is not available
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The fallback to CODING agent when workspace is null (line 75) happens silently by calling onAgentTypeChange. This could be confusing to users who selected ChatDB. Consider showing a notification or error message explaining why ChatDB is unavailable before falling back to the CODING agent.

Suggested change
// Fallback to coding if workspace is not available
// Notify user and fallback to coding if workspace is not available
onNotification(
"ChatDB Unavailable",
"The ChatDB agent requires an open workspace. Falling back to Coding agent."
)

Copilot uses AI. Check for mistakes.
onProjectChange: (String) -> Unit = {},
onGitUrlChange: (String) -> Unit = {},
onNotification: (String, String) -> Unit = { _, _ -> },
workspace: Workspace? = null,
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The workspace parameter is added to AgentInterfaceRouter but is not passed from AutoDevApp.kt where it's called (line 575-641). The currentWorkspace variable exists in AutoDevApp but needs to be added to the AgentInterfaceRouter call around line 640, otherwise ChatDB will always fall back to CODING mode.

Copilot uses AI. Check for mistakes.
Comment on lines 249 to 252
val result = currentConnection!!.executeQuery(sql)
// Display query result as a new message
renderer.renderLLMResponseStart()
renderer.renderLLMResponseChunk("\n\n**Query Result:**\n```\n${result.toTableString()}\n```")
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

Direct SQL execution from LLM output poses SQL injection risks. While the system prompt asks for SELECT-only queries, the LLM could still generate queries with malicious WHERE clauses or subqueries that could cause performance issues or data exposure. Consider using parameterized queries, implementing a SQL parser to validate and sanitize queries, or at minimum adding query timeouts and result size limits.

Copilot uses AI. Check for mistakes.
Comment on lines 242 to 257
private suspend fun extractAndExecuteSQL(response: String) {
val sqlPattern = Regex("```sql\\n([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
val match = sqlPattern.find(response)

if (match != null && currentConnection != null) {
val sql = match.groupValues[1].trim()
try {
val result = currentConnection!!.executeQuery(sql)
// Display query result as a new message
renderer.renderLLMResponseStart()
renderer.renderLLMResponseChunk("\n\n**Query Result:**\n```\n${result.toTableString()}\n```")
renderer.renderLLMResponseEnd()
} catch (e: Exception) {
renderer.renderError("Query Error: ${e.message}")
}
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The system prompt restricts SQL to SELECT queries only for safety, but this restriction is not enforced in the code. The extractAndExecuteSQL function will execute any SQL statement that matches the pattern. Consider adding SQL validation to ensure only SELECT queries are executed, or use a SQL parser to validate the query type before execution.

Copilot uses AI. Check for mistakes.
Comment on lines 147 to 161
try {
val connection = createDatabaseConnection(dataSource.toDatabaseConfig())
if (connection.isConnected()) {
currentConnection = connection
currentSchema = connection.getSchema()
state = state.copy(connectionStatus = ConnectionStatus.Connected)
_notificationEvent.emit("Connected" to "Successfully connected to ${dataSource.name}")
} else {
state = state.copy(connectionStatus = ConnectionStatus.Error("Failed to connect"))
}
} catch (e: Exception) {
state = state.copy(connectionStatus = ConnectionStatus.Error(e.message ?: "Unknown error"))
_notificationEvent.emit("Connection Failed" to (e.message ?: "Unknown error"))
}
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The connection error handling wraps the exception message, but doesn't close the potentially half-opened connection. Consider calling connection.close() in the catch block if the connection object was created but isConnected() returns false or throws an exception.

Copilot uses AI. Check for mistakes.
@phodal phodal force-pushed the feature/chatdb-agent branch from 5229c24 to f0fc6a4 Compare December 9, 2025 14:42
- Add CHAT_DB agent type to AgentType enum
- Create ChatDBPage with split layout (DataSourcePanel + ChatDBChatPane)
- Create ChatDBViewModel for managing data sources and chat
- Create DataSourcePanel for managing database connections
- Create DataSourceConfigDialog for adding/editing data sources
- Create ChatDBChatPane for chat interactions
- Add Database and Schema icons to AutoDevComposeIcons
- Update AgentInterfaceRouter to route to ChatDBPage
- Update when expressions in AgentChatInterface, TopBarMenuDesktop, TopBarMenuMobile

Closes #508
@phodal phodal force-pushed the feature/chatdb-agent branch from f0fc6a4 to eb0d669 Compare December 9, 2025 14:44
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: 3

♻️ Duplicate comments (11)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt (1)

161-188: AgentType string persistence is correct, but ChatDB route never receives a workspace

  • The updated when in handleAgentTypeChange that maps:

    • REMOTE -> "Remote", LOCAL_CHAT -> "Local", CODING -> "Coding", CODE_REVIEW -> "CodeReview", KNOWLEDGE -> "Documents", CHAT_DB -> "ChatDB"
      is a solid improvement over a generic else and matches the mappings used in DesktopUiState and IdeaAgentViewModel.
  • However, AgentInterfaceRouter now takes workspace: Workspace? = null, and the call in AutoDevApp does not pass it, so workspace is always null inside the router. That means the AgentType.CHAT_DB branch will always hit the placeholder "Please select a workspace to use ChatDB" even though AutoDevApp is already tracking currentWorkspace. In practice this makes ChatDB unusable from the desktop UI.

You can fix this by wiring currentWorkspace through to the router:

-                    AgentInterfaceRouter(
+                    AgentInterfaceRouter(
                         llmService = llmService,
                         isTreeViewVisible = isTreeViewVisible,
                         onConfigWarning = { showModelConfigDialog = true },
                         onToggleTreeView = { /* 不需要,由全局状态管理 */ },
                         chatHistoryManager = chatHistoryManager,
                         selectedAgentType = selectedAgentType,
                         onAgentTypeChange = { type ->
                             handleAgentTypeChange(type)
                         },
                         onSessionSelected = { sessionId ->
                             messages = chatHistoryManager.getMessages()
                             currentStreamingOutput = ""
                         },
                         onNewChat = {
                             messages = emptyList()
                             currentStreamingOutput = ""
                         },
                         onInternalSessionSelected = { handler ->
                             agentSessionSelectedHandler = handler
                         },
                         onInternalNewChat = { handler ->
                             agentNewChatHandler = handler
                         },
                         hasHistory = messages.isNotEmpty(),
                         hasDebugInfo = compilerOutput.isNotEmpty(),
                         currentModelConfig = currentModelConfig,
                         selectedAgent = selectedAgent,
                         availableAgents = availableAgents,
                         onOpenDirectory = { openDirectoryChooser() },
                         onClearHistory = {
                             chatHistoryManager.clearCurrentSession()
                             messages = emptyList()
                             currentStreamingOutput = ""
                         },
                         onModelConfigChange = { config ->
                             currentModelConfig = config
                             if (config.isValid()) {
                                 try {
                                     llmService = KoogLLMService.create(config)
                                     println("切换模型: ${config.provider.displayName} / ${config.modelName}")
                                 } catch (e: Exception) {
                                     println("切换模型失败: ${e.message}")
                                 }
                             }
                         },
                         onAgentChange = { agent ->
                             selectedAgent = agent
                         },
                         onConfigureRemote = { showRemoteConfigDialog = true },
                         onShowModelConfig = { showModelConfigDialog = true },
                         onShowToolConfig = { showToolConfigDialog = true },
-                        serverUrl = serverUrl,
+                        serverUrl = serverUrl,
                         useServerConfig = useServerConfig,
                         projectId = remoteProjectId,
                         gitUrl = remoteGitUrl,
                         onProjectChange = { projectId ->
                             remoteProjectId = projectId
                             println("📁 Project ID: $projectId")
                         },
                         onGitUrlChange = { url ->
                             remoteGitUrl = url
                             println("📦 Git URL: $url")
                         },
-                        onNotification = onNotification,
-                        modifier = Modifier.fillMaxSize()
+                        onNotification = onNotification,
+                        workspace = currentWorkspace,
+                        modifier = Modifier.fillMaxSize()
                     )

As a follow-up (non-blocking), consider centralizing this AgentType→string mapping into a small extension or helper (e.g., AgentType.toConfigKey() in a shared module) to avoid having the same when duplicated across multiple files.

Also applies to: 578-644

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt (1)

197-210: Validation logic should use DataSourceConfig.validate() method.

The button enable state only checks basic field presence (lines 212-213), but this bypasses the ValidationResult logic defined in DataSourceConfig.validate(). This means invalid port ranges (e.g., > 65535) won't be caught until save time, and error messages won't be displayed to users.

This issue was previously flagged in past review comments.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (2)

59-69: State variable showSchemaDialog is scoped inside conditional block.

The showSchemaDialog state is declared inside the if (state.connectionStatus is ConnectionStatus.Connected) block. This means the state will be recreated each time the connection status toggles, potentially causing unexpected dialog dismissals. Hoist this state to the ChatDBPage level before the Scaffold.

This issue was previously raised in past review comments.


26-26: llmService parameter is unused.

The llmService parameter is passed to ChatDBPage but never used. The ChatDBViewModel appears to create its own instance. Consider either removing this parameter or threading it through to the ViewModel to avoid duplicate initialization.

This issue was previously raised by augmentcode in past review comments.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (1)

277-291: Consider adding a cancel button for the Connecting state.

The Connecting state shows a disabled button with a spinner, but users cannot cancel a hung connection attempt. This could leave users stuck if the connection times out slowly.

This issue was previously flagged in past review comments.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt (2)

60-66: Clarify validation error message for non-SQLite ports.

Validation checks port only when dialect != DatabaseDialect.SQLITE, but the error message says “Port must be between 1 and 65535” without indicating this is for non-SQLite databases. This can be slightly confusing in mixed-dialect UIs.

You could make this explicit:

-            if (port <= 0 || port > 65535) errors.add("Port must be between 1 and 65535")
+            if (port <= 0 || port > 65535) {
+                errors.add("Port must be between 1 and 65535 (for non-SQLite databases)")
+            }

29-42: Avoid serializing database passwords in plain text in DataSourceConfig.

DataSourceConfig is @Serializable and includes password as a plain String. If this is persisted, credentials will be stored in clear text (and can also end up in logs or crash dumps), which is a significant security risk.

Consider one of:

  • Exclude password from serialization (e.g., @kotlinx.serialization.Transient) and keep it in a dedicated secure store / IDE data source.
  • Store only an opaque credential ID here and resolve the actual secret via a secure provider.
  • If you must serialize it, at least encrypt at rest and lock down file permissions, and document the limitation.
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (4)

103-112: Fix deleteDataSource so it actually disconnects the selected source.

Right now you clear selectedDataSourceId before checking it, so disconnect() is never called when deleting the active data source:

  • Line 106 sets selectedDataSourceId to null when id matches.
  • Line 108 then checks state.selectedDataSourceId == id, which is now always false.

This can leak an open DB connection.

A minimal fix:

    fun deleteDataSource(id: String) {
-        state = state.copy(
-            dataSources = state.dataSources.filter { it.id != id },
-            selectedDataSourceId = if (state.selectedDataSourceId == id) null else state.selectedDataSourceId
-        )
-        if (state.selectedDataSourceId == id) {
-            disconnect()
-        }
+        val wasSelected = state.selectedDataSourceId == id
+        state = state.copy(
+            dataSources = state.dataSources.filter { it.id != id },
+            selectedDataSourceId = if (wasSelected) null else state.selectedDataSourceId
+        )
+        if (wasSelected) {
+            disconnect()
+        }
        saveDataSources()
    }

141-162: Make connect() idempotent and manage concurrent connection attempts.

connect() launches a new coroutine each call, even if a connection is already in progress or established. This can:

  • Create multiple DatabaseConnection instances if the user rapidly retries; only the last is stored in currentConnection, leaving earlier ones unclosed.
  • Re-run createDatabaseConnection when already Connected, which is unnecessary.
  • Leave partially created connections unclosed on exceptions.

Suggested improvements:

  • Short-circuit if already ConnectionStatus.Connecting or Connected.
  • Track the active connect job and cancel/await it before starting a new one.
  • Ensure any newly created connection is closed in catch/finally if you don’t end up assigning it to currentConnection.

For example:

-    fun connect() {
+    private var connectJob: Job? = null
+
+    fun connect() {
         val dataSource = state.selectedDataSource ?: return
-
-        scope.launch {
+        if (state.connectionStatus is ConnectionStatus.Connecting ||
+            state.connectionStatus is ConnectionStatus.Connected
+        ) return
+
+        connectJob?.cancel()
+        connectJob = scope.launch {
             state = state.copy(connectionStatus = ConnectionStatus.Connecting)
 
             try {
-                val connection = createDatabaseConnection(dataSource.toDatabaseConfig())
+                val connection = createDatabaseConnection(dataSource.toDatabaseConfig())
                 if (connection.isConnected()) {
                     currentConnection = connection
                     currentSchema = connection.getSchema()

And close connection in a finally block if you decide not to keep it.

Check the documentation for `cc.unitmesh.agent.database.DatabaseConnection` and `createDatabaseConnection` to see whether they manage pooling/lifecycle automatically or require explicit `close()` for each instance.

242-258: Enforce read-only SQL validation before executing LLM-generated queries.

extractAndExecuteSQL() executes whatever is inside the first sql block via currentConnection!!.executeQuery(sql) with no validation. Even though the system prompt says “Only generate SELECT queries”, that’s not an enforceable guarantee and violates the “AST-based SQL validation (prevent destructive statements)” requirement.

At minimum, add a guard to ensure the query is read-only:

         if (match != null && currentConnection != null) {
             val sql = match.groupValues[1].trim()
-            try {
-                val result = currentConnection!!.executeQuery(sql)
+            // Basic read-only enforcement; replace with a proper SQL parser/AST in production
+            val normalized = sql.trim().uppercase()
+            if (!normalized.startsWith("SELECT") ||
+                Regex("\\b(INSERT|UPDATE|DELETE|DROP|TRUNCATE|ALTER|CREATE)\\b").containsMatchIn(normalized)
+            ) {
+                renderer.renderError("Security: only SELECT queries are allowed.")
+                return
+            }
+
+            try {
+                val result = currentConnection!!.executeQuery(sql)
                 // Display query result as a new message
                 renderer.renderLLMResponseStart()
                 renderer.renderLLMResponseChunk("\n\n**Query Result:**\n```\n${result.toTableString()}\n```")

For production, a real SQL parser/AST check and server-side sandboxing would be preferable.

Look up best practices for validating LLM-generated SQL (e.g., read-only enforcement, AST-based validation) before execution.

28-46: Use the main dispatcher for Compose state and renderer updates.

scope uses Dispatchers.Default, but state, isGenerating, and ComposeRenderer updates are Compose-facing state. Per Compose best practices, state mutations that trigger recomposition must occur on the main dispatcher to avoid race conditions and platform-specific threading issues. Updates to mutableStateOf from background dispatchers can cause subtle synchronization problems.

Use either:

  • A main-based scope with background work isolated:
-    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

and invoke withContext(Dispatchers.Default) only for heavy operations (DB queries, LLM calls), or

  • Introduce a separate uiScope on Dispatchers.Main for state mutations while keeping a background scope for I/O.
🧹 Nitpick comments (3)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt (1)

92-93: CHAT_DB color/icon mapping is good; verify whether it should appear in the IDEA tab strip

The new CHAT_DB mappings to IdeaAutoDevColors.Cyan.c400 and IdeaComposeIcons.Database look consistent with the rest of the design.

Note that the agentTypes list in SegmentedAgentTabs (Line 92) still only includes CODING, CODE_REVIEW, KNOWLEDGE, and REMOTE, so CHAT_DB cannot currently be selected from this header even if it is the active type from config. If IDEA is not meant to expose ChatDB yet, this is fine, but if it should be user-selectable you may want to append AgentType.CHAT_DB to that list or document the intentional omission.

Also applies to: 255-262, 267-274

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1)

151-152: Consider displaying Connecting and Error states in the banner.

The else branch silently ignores ConnectionStatus.Connecting and ConnectionStatus.Error. Users may not receive feedback when a connection attempt is in progress or has failed. Consider adding visual indicators for these states.

-        else -> { /* Connecting or Error handled elsewhere */ }
+        is ConnectionStatus.Connecting -> {
+            Card(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = 12.dp, vertical = 8.dp),
+                colors = CardDefaults.cardColors(
+                    containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.3f)
+                )
+            ) {
+                Row(
+                    modifier = Modifier.padding(12.dp),
+                    horizontalArrangement = Arrangement.spacedBy(12.dp),
+                    verticalAlignment = Alignment.CenterVertically
+                ) {
+                    CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
+                    Text("Connecting...", style = MaterialTheme.typography.bodySmall)
+                }
+            }
+        }
+        is ConnectionStatus.Error -> {
+            Card(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = 12.dp, vertical = 8.dp),
+                colors = CardDefaults.cardColors(
+                    containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.3f)
+                )
+            ) {
+                Row(
+                    modifier = Modifier.padding(12.dp),
+                    horizontalArrangement = Arrangement.spacedBy(12.dp),
+                    verticalAlignment = Alignment.CenterVertically
+                ) {
+                    Icon(
+                        AutoDevComposeIcons.Error,
+                        contentDescription = null,
+                        modifier = Modifier.size(20.dp),
+                        tint = MaterialTheme.colorScheme.error
+                    )
+                    Text(
+                        text = connectionStatus.message,
+                        style = MaterialTheme.typography.bodySmall,
+                        color = MaterialTheme.colorScheme.error
+                    )
+                }
+            }
+        }
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (1)

70-75: Implement data source persistence before shipping the feature.

loadDataSources() and saveDataSources() are TODOs, so all configured connections are lost on restart. For a DB agent, this severely impacts usability.

Hook these into your existing config/storage layer (e.g., ConfigManager or workspace-scoped storage) so that:

  • loadDataSources() restores serialized DataSourceConfig instances for the current workspace.
  • saveDataSources() persists current state.dataSources whenever they change.

Also applies to: 114-116

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5229c24 and eb0d669.

📒 Files selected for processing (17)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt (4 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt (2 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt (2 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuDesktop.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuMobile.kt (2 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/state/DesktopUiState.kt (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuDesktop.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.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-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuMobile.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/state/DesktopUiState.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt
**/*.{kt,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuMobile.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/state/DesktopUiState.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt
🧠 Learnings (3)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, and `JsRendererAdapter` in Kotlin

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/TopBarMenuMobile.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/state/DesktopUiState.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update JVM CLI implementations: `CodingCliRenderer` and `ConsoleRenderer`

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/state/DesktopUiState.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt
🧬 Code graph analysis (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (3)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (1)
  • DataSourcePanel (23-82)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1)
  • ChatDBChatPane (33-78)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt (1)
  • DataSourceConfigDialog (23-221)
⏰ 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 (14)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt (1)

240-253: CHAT_DB agent type persistence mapping looks correct

The new AgentType.CHAT_DB -> "ChatDB" branch keeps the when exhaustive and aligns with the string keys used elsewhere for config persistence. No issues found here.

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

221-228: Database icon wiring for CHAT_DB is consistent and complete

Both when expressions now map AgentType.CHAT_DB to AutoDevComposeIcons.Database, keeping them exhaustive and aligning with the new ChatDB agent type without changing any behavior.

Also applies to: 256-263

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt (1)

1741-1794: Database ImageVector integrates cleanly with existing IDEA icon set

The new Database ImageVector follows the same pattern (24×24 viewport, single black fill path, lazy initialization) as the other icons in IdeaComposeIcons. It should be safe to use from IdeaAgentTabsHeader without issues.

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

5-9: Desktop agent-type persistence is consistent and low-risk

Using a small CoroutineScope(Dispatchers.Default) in DesktopUiState to persist the agent type via AutoDevConfigWrapper.saveAgentTypePreference is reasonable for this lightweight disk write, and the when mapping covers all current AgentType values including CHAT_DB with the same string keys used elsewhere. I do not see functional issues here.

Also applies to: 16-17, 47-64

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

3-11: CHAT_DB routing and workspace fallback are implemented safely

The new AgentType.CHAT_DB branch looks good:

  • When workspace is non-null, it delegates to ChatDBPage and routes onBack back to CODING without side effects in composition.
  • When workspace is null, it renders a simple placeholder Box and no longer mutates agent type during composition, which avoids the recomposition issue highlighted in previous reviews.

Once callers pass a non-null Workspace (e.g., from AutoDevApp’s currentWorkspace), this should provide a clean ChatDB experience.

Also applies to: 23-61, 63-86

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

116-119: ChatDB-related icon aliases are correctly wired to Material icons

Exposing Database as Icons.Default.Storage and Schema as Icons.Default.TableChart in AutoDevComposeIcons gives the ChatDB UI a consistent way to reference these icons without scattering raw Material icon usages. This fits the existing icon pattern and introduces no functional risk.

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

35-38: LGTM!

The CHAT_DB enum value is properly added with consistent documentation, display name mapping, and string parsing (accepting both "chatdb" and "database" aliases). The implementation follows the established pattern of other agent types.

Also applies to: 50-50, 62-62

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1)

33-78: LGTM!

The main ChatDBChatPane composable is well-structured with clear separation of concerns: connection banner, message list with welcome overlay, and input area. The conditional rendering based on connection status and message state is handled appropriately.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigDialog.kt (2)

23-102: LGTM!

The dialog structure is well-organized with appropriate conditional rendering based on the database dialect. The dropdown for dialect selection correctly updates the default port, and the edit/add mode is properly distinguished.


199-201: No action required — ID generation is properly handled.

The addDataSource method already generates a unique UUID for blank IDs before adding the data source to state (line 79: if (config.id.isBlank()) Uuid.random().toString()). No duplicate key conflicts will occur in the LazyColumn since each new data source receives a generated UUID before persistence.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (2)

31-46: LGTM!

The ViewModel lifecycle management is well-implemented: remember for persistence across recompositions, LaunchedEffect for collecting notifications, and DisposableEffect for cleanup on dispose.


128-168: LGTM!

The SchemaInfoDialog appropriately limits table display to 10 entries with an overflow indicator, preventing UI bloat for large schemas. The null-safety check for schema is handled correctly.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (2)

23-82: LGTM!

The DataSourcePanel composable is well-structured with clear separation into header, search field, list, and connection controls. The conditional rendering of connection controls based on selection state is handled appropriately.


145-250: LGTM!

The DataSourceItem composable correctly handles selection styling, status indicator colors mapped from ConnectionStatus, URL truncation with ellipsis, and a dropdown menu for edit/delete actions. The use of MaterialTheme.colorScheme for all colors follows the coding guidelines.

Comment on lines 177 to 201
fun sendMessage(text: String) {
if (isGenerating || text.isBlank()) return

currentExecutionJob = scope.launch {
isGenerating = true
renderer.addUserMessage(text)

try {
val service = llmService
if (service == null) {
renderer.renderError("LLM service not initialized. Please configure your model settings.")
isGenerating = false
return@launch
}

val schemaContext = currentSchema?.getDescription() ?: "No database connected"
val systemPrompt = buildSystemPrompt(schemaContext)

val response = StringBuilder()
renderer.renderLLMResponseStart()

service.streamPrompt(
userPrompt = "$systemPrompt\n\nUser: $text",
compileDevIns = false
).collect { chunk ->
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 find the KoogLLMService definition
fd -t f -e kt | xargs rg "class KoogLLMService|interface KoogLLMService" -A 20

Repository: phodal/auto-dev

Length of output: 4495


🏁 Script executed:

# Also search for streamPrompt method signature
rg "fun streamPrompt" -A 10

Repository: phodal/auto-dev

Length of output: 2242


🏁 Script executed:

# Read the specific lines in ChatDBViewModel.kt around 223-239
fd -t f "ChatDBViewModel.kt" | xargs cat -n | sed -n '220,245p'

Repository: phodal/auto-dev

Length of output: 1058


🏁 Script executed:

# Read the full sendMessage() method context around lines 177-201
fd -t f "ChatDBViewModel.kt" | xargs cat -n | sed -n '170,245p'

Repository: phodal/auto-dev

Length of output: 3197


🏁 Script executed:

# Check if there are other usages of streamPrompt() to understand the pattern
rg "streamPrompt\(" -B 2 -A 5 | head -100

Repository: phodal/auto-dev

Length of output: 9375


🏁 Script executed:

# Check if historyMessages parameter accepts system messages in Message type
rg "class Message|data class Message" -A 5 -B 1

Repository: phodal/auto-dev

Length of output: 8658


🏁 Script executed:

# Look for any Message types with role or system variants
rg "role.*system|system.*role" -t kt | head -20

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Verify the exact Message class definition
cat -n mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/llm/Message.kt

Repository: phodal/auto-dev

Length of output: 2184


Use historyMessages with MessageRole.SYSTEM to separate system instructions from user input.

sendMessage() concatenates the system prompt and user text into a single userPrompt string, which presents a prompt injection risk and resends the full system prompt on every turn:

service.streamPrompt(
    userPrompt = "$systemPrompt\n\nUser: $text",
    compileDevIns = false
)

Instead, prepend a system message to historyMessages before calling streamPrompt():

val systemMessage = Message(MessageRole.SYSTEM, systemPrompt)
service.streamPrompt(
    userPrompt = text,
    historyMessages = listOf(systemMessage),
    compileDevIns = false
)

This provides structural separation between system instructions and user input (reducing prompt injection risk) and avoids resending the system prompt repeatedly. The Message type already supports MessageRole.SYSTEM for this purpose.

🤖 Prompt for AI Agents
In
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
around lines 177-201, the code concatenates the system prompt and user text into
userPrompt (prompt injection risk and redundant resending); instead create a
Message with MessageRole.SYSTEM containing the systemPrompt and call
service.streamPrompt with userPrompt = text and historyMessages =
listOf(systemMessage) (preserve compileDevIns = false), removing the
"$systemPrompt\n\nUser: $text" concatenation so system instructions are sent as
a separate system message.

Comment on lines 242 to 245
private suspend fun extractAndExecuteSQL(response: String) {
val sqlPattern = Regex("```sql\\n([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
val match = sqlPattern.find(response)

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

🧩 Analysis chain

🏁 Script executed:

fd -t f "ChatDBViewModel" --extension kt

Repository: phodal/auto-dev

Length of output: 149


🏁 Script executed:

wc -l mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt

Repository: phodal/auto-dev

Length of output: 153


🏁 Script executed:

sed -n '230,255p' mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt

Repository: phodal/auto-dev

Length of output: 1174


🌐 Web query:

common markdown code block formatting LLM output ```sql newline styles spacing variants

💡 Result:

Below are common Markdown code-block styles/variants and small formatting tips for LLM-produced SQL outputs (examples use fenced blocks):

  1. Basic fenced block (preferred; enables syntax highlighting)
SELECT id, name
FROM users
WHERE active = true;
  • Use lowercase language tag "sql" for best compatibility. [1][2]
  1. Blank-line padding (recommended for readability in raw Markdown)
  • Put a blank line before and after the fenced block. [1]
  1. Triple-tilde alternative (equivalent to backticks)
SELECT *
FROM orders;
  • Works where Markdown parser supports fenced blocks.
  1. Inline / one-liner (for very short snippets)
    SELECT COUNT(*) FROM users;

  2. Indented fenced block inside lists or block elements

  • Indent the fence by 4 (or 8) spaces when inside a list so it renders correctly. [1]
  1. Clause-newline / expanded style (common SQL-formatter output — preferred for readability)
SELECT u.id,
       u.name,
       o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.active = TRUE
ORDER BY o.total DESC;
  • Put major clauses (FROM, JOIN, WHERE, GROUP BY, ORDER BY) each on their own line, and align or indent expressions. (Typical formatter output.) [2]
  1. Compact / single-statement style (for short queries)
SELECT id, name FROM users WHERE active = TRUE;
  1. Keyword-casing variants (style choice)
  • ALL CAPS keywords: SELECT, FROM, WHERE (common in SQL style guides).
  • lower_case keywords: select, from, where (works everywhere). Use whichever matches project style; some renderers are case-sensitive for language tag but not SQL itself. [3][4]
  1. Trailing semicolon and newline
  • Include semicolon at end of statement(s) and a final newline; many SQL tools expect it.
  1. Comments and metadata
-- Explain: Fetch active users with orders in last 30 days
SELECT ...
  • Use SQL comments to annotate LLM output intent.
  1. Diff / change annotation (for showing edits)
- WHERE active = FALSE
+ WHERE active = TRUE
  • Use diff fences when showing changes.
  1. Tools / syntax-highlighting notes
  • GitHub uses Linguist for highlighting; "sql" (and aliases like pgsql, postgres, pgsql) are supported. Prefer lowercase language id for GitHub Pages/Jekyll. [1][5]
  • Some platforms may treat uppercase language tags inconsistently (see some GitLab reports). Use lowercase to be safe. [4]

Quick practical checklist for LLM SQL output:

  • Use ```sql fenced block with lower-case "sql". [1]
  • Put blank line before/after fences for raw readability. [1]
  • Use multi-line clause-per-line formatting for readability (JOIN/WHERE/GROUP BY on new lines). [2]
  • Include semicolon and final newline.
  • Add short SQL comments to explain intent.
  • If inside lists, indent fences so Markdown renders correctly. [1]

Sources: GitHub fenced-code recommendations and language support; general Markdown code-block best-practice guides. [1][2][5][4]

References
[1] GitHub Docs — Creating and highlighting code blocks.
[2] Complete Guide to Markdown Code Blocks — best practices.
[3] GitHub supported languages (Linguist) list.
[4] GitLab issue re: case-sensitivity of LANGUAGE tag.


🏁 Script executed:

rg "sqlPattern|```sql" mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ -A 2 -B 2

Repository: phodal/auto-dev

Length of output: 127


🏁 Script executed:

rg "sqlPattern" mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ -A 2 -B 2

Repository: phodal/auto-dev

Length of output: 861


🏁 Script executed:

rg "extractAndExecuteSQL" mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ -B 2 -A 5

Repository: phodal/auto-dev

Length of output: 2135


🏁 Script executed:

fd -t f "test" --extension kt | rg "ChatDB" -i

Repository: phodal/auto-dev

Length of output: 41


🏁 Script executed:

rg "Regex.*\`\`\`" --type kt | head -20

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

rg 'Regex.*```' | head -20

Repository: phodal/auto-dev

Length of output: 2986


🏁 Script executed:

sed -n '240,250p' mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt

Repository: phodal/auto-dev

Length of output: 513


Align SQL code block regex with established codebase patterns for robustness.

The current regex requires exactly \n after the language tag, which is inconsistent with similar code extraction patterns elsewhere in the codebase (PlanParser, PlotDSL extraction, ToolCallParser) that use \s*\n to handle optional whitespace. Additionally, LLM outputs may contain \r\n (Windows line endings) or extra spaces, causing valid SQL blocks to be missed.

Change to:

-        val sqlPattern = Regex("```sql\\n([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
+        val sqlPattern = Regex("```sql\\s+([\\s\\S]*?)```", RegexOption.IGNORE_CASE)

This handles \n, \r\n, spaces, tabs, and their combinations while remaining aligned with similar extraction patterns in the codebase.

🤖 Prompt for AI Agents
In
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
around lines 242-245, the SQL code-block regex currently requires an exact "\n"
after the "```sql" tag which misses blocks with extra whitespace or CRLFs;
update the pattern to accept one-or-more whitespace characters after the tag
(e.g., use "\s+" instead of "\n") so it matches "```sql" followed by any
combination of spaces/tabs/newlines (including "\r\n"), keeping
RegexOption.IGNORE_CASE; replace the existing Regex(...) instantiation
accordingly so it aligns with the project's other parsers.

Comment on lines 260 to 271
fun stopGeneration() {
currentExecutionJob?.cancel()
isGenerating = false
}

fun getSchema(): DatabaseSchema? = currentSchema

fun dispose() {
stopGeneration()
disconnect()
scope.cancel()
}
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

Ensure dispose() reliably closes DB connections before cancelling the scope.

dispose() calls disconnect() and then immediately scope.cancel():

fun dispose() {
    stopGeneration()
    disconnect()
    scope.cancel()
}

But disconnect() launches an async coroutine on scope; cancelling the scope right after can prevent that coroutine from running and leave currentConnection open.

Safer patterns:

  • Either inline a synchronous close in dispose():
    fun dispose() {
        stopGeneration()
-       disconnect()
-       scope.cancel()
+       try {
+           currentConnection?.close()
+       } catch (e: Exception) {
+           println("[ChatDB] Error closing connection: ${e.message}")
+       } finally {
+           currentConnection = null
+           currentSchema = null
+       }
+       scope.cancel()
    }
  • Or make disconnect a suspending function and ensure it completes before cancelling the scope.
🤖 Prompt for AI Agents
In
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
around lines 260–271, dispose() cancels the scope immediately after calling
disconnect(), which launches an async coroutine on that scope and can prevent
the DB close from running; change dispose() so the DB connection is closed
reliably before cancelling the scope: either perform the close synchronously in
dispose() by directly checking and closing currentConnection (and clearing
currentConnection/currentSchema) before calling scope.cancel(), or make
disconnect() a suspending function and call it from dispose() in a
blocking/waiting manner (or otherwise await its completion) so the close
completes prior to scope.cancel(); ensure any exceptions during close are
handled/logged and currentConnection is nulled.

Refactor agent type string conversion to use getDisplayName() and make ChatDB workspace parameter optional. Remove SessionApp and streamline ChatDB routing logic.
Show SessionSidebar only for agent types that require it, hiding it on pages with their own navigation such as CHAT_DB, KNOWLEDGE, and CODE_REVIEW.
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: 0

♻️ Duplicate comments (9)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (2)

26-31: llmService parameter is unused; ViewModel creates its own instance.

The llmService parameter is accepted but never passed to the ChatDBViewModel. The ViewModel initializes its own LLM service via initializeLLMService(). Either thread the provided service through to avoid duplicate initialization or remove the unused parameter.

-    val viewModel = remember { ChatDBViewModel(workspace) }
+    val viewModel = remember { ChatDBViewModel(workspace, llmService) }

59-69: Hoist showSchemaDialog state outside the conditional block.

The showSchemaDialog state is declared inside the if (state.connectionStatus is ConnectionStatus.Connected) block. When the connection status toggles, the state will be recreated, potentially causing unexpected dialog dismissals.

Move the state declaration to the ChatDBPage level:

     val viewModel = remember { ChatDBViewModel(workspace) }
     val state = viewModel.state
+    var showSchemaDialog by remember { mutableStateOf(false) }

     // ... later in actions block:
     if (state.connectionStatus is ConnectionStatus.Connected) {
-        var showSchemaDialog by remember { mutableStateOf(false) }
         IconButton(onClick = { showSchemaDialog = true }) {

And move the dialog outside the TopAppBar actions block.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (7)

70-74: Data source persistence is not implemented.

The loadDataSources() and saveDataSources() methods are placeholders (TODO comments). Users will lose all configured database connections when the application restarts. Consider implementing persistence using ConfigManager or similar storage before releasing this feature.

Would you like me to help implement data source persistence, or should I open an issue to track this task?

Also applies to: 114-116


28-28: Compose state updates should occur on the main dispatcher.

The scope uses Dispatchers.Default, but the ViewModel modifies Compose mutableStateOf properties (state, isGenerating) from coroutines launched on this scope. Compose state should be updated from the main thread.

-    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

Alternatively, wrap state updates with withContext(Dispatchers.Main).


103-112: Logic bug: disconnect() is never called when deleting the selected data source.

The check on line 108 uses state.selectedDataSourceId after it was already set to null on line 106. The condition will always be false when deleting the selected data source, leaving the connection open.

     fun deleteDataSource(id: String) {
+        val wasSelected = state.selectedDataSourceId == id
         state = state.copy(
             dataSources = state.dataSources.filter { it.id != id },
             selectedDataSourceId = if (state.selectedDataSourceId == id) null else state.selectedDataSourceId
         )
-        if (state.selectedDataSourceId == id) {
+        if (wasSelected) {
             disconnect()
         }
         saveDataSources()
     }

141-162: Race condition with rapid connect() calls can leak connections.

If connect() is called multiple times rapidly (e.g., user double-clicks), multiple connections could be created concurrently. Only the last connection is stored in currentConnection, leaving others orphaned and unclosed.

Track and cancel in-flight connection attempts:

+    private var connectionJob: Job? = null
+
     fun connect() {
         val dataSource = state.selectedDataSource ?: return
 
-        scope.launch {
+        connectionJob?.cancel()
+        connectionJob = scope.launch {
             state = state.copy(connectionStatus = ConnectionStatus.Connecting)
             // ...

242-257: Critical security risk: No SQL validation before execution.

The system prompt requests "only SELECT queries," but there's no actual validation. Malicious prompts could trick the LLM into generating destructive SQL (DROP, DELETE, UPDATE). The PR objectives explicitly require "AST-based SQL validation (prevent destructive statements)."

Add SQL validation before execution:

     private suspend fun extractAndExecuteSQL(response: String) {
-        val sqlPattern = Regex("```sql\\n([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
+        val sqlPattern = Regex("```sql\\s+([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
         val match = sqlPattern.find(response)

         if (match != null && currentConnection != null) {
             val sql = match.groupValues[1].trim()
+            
+            // Validate SQL is read-only (SELECT only)
+            val normalizedSql = sql.trim().uppercase()
+            if (!normalizedSql.startsWith("SELECT") || 
+                normalizedSql.contains(Regex("\\b(INSERT|UPDATE|DELETE|DROP|TRUNCATE|ALTER|CREATE|GRANT|REVOKE)\\b"))) {
+                renderer.renderError("Security: Only SELECT queries are allowed")
+                return
+            }
+            
             try {
                 val result = currentConnection!!.executeQuery(sql)

Note: Also updated regex from \\n to \\s+ to handle various whitespace patterns.


267-271: dispose() may not reliably close DB connections.

disconnect() launches an async coroutine on scope, but scope.cancel() is called immediately after, potentially preventing the disconnect coroutine from completing and leaving the connection open.

Close the connection synchronously in dispose():

     fun dispose() {
         stopGeneration()
-        disconnect()
+        try {
+            currentConnection?.close()
+        } catch (e: Exception) {
+            println("[ChatDB] Error closing connection: ${e.message}")
+        } finally {
+            currentConnection = null
+            currentSchema = null
+        }
         scope.cancel()
     }

198-201: Use historyMessages with MessageRole.SYSTEM instead of concatenation.

Concatenating the system prompt with user input ("$systemPrompt\n\nUser: $text") presents prompt injection risk and resends the full system prompt on every turn. Use the Message type with MessageRole.SYSTEM for proper separation.

+import cc.unitmesh.devins.llm.Message
+import cc.unitmesh.devins.llm.MessageRole

// In sendMessage():
+val systemMessage = Message(MessageRole.SYSTEM, systemPrompt)
 service.streamPrompt(
-    userPrompt = "$systemPrompt\n\nUser: $text",
+    userPrompt = text,
+    historyMessages = listOf(systemMessage),
     compileDevIns = false
 )
🧹 Nitpick comments (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt (1)

3-3: Unused import detected.

The fillMaxSize import is added but not used in this file. Consider removing it to keep imports clean.

-import androidx.compose.foundation.layout.fillMaxSize
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb0d669 and 2bd950b.

📒 Files selected for processing (7)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/app/SessionApp.kt (0 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt (3 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/state/DesktopUiState.kt (2 hunks)
💤 Files with no reviewable changes (1)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/app/SessionApp.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/state/DesktopUiState.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.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/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.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/agent/AgentInterfaceRouter.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
🧠 Learnings (3)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AgentInterfaceRouter.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AgentInterfaceRouter.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AgentInterfaceRouter.kt
🧬 Code graph analysis (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt (2)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (1)
  • ChatDBPage (22-126)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt (1)
  • onAgentTypeChange (240-253)
⏰ 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 (2)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt (1)

63-73: LGTM! Clean routing implementation for CHAT_DB.

The new CHAT_DB branch properly routes to ChatDBPage, passing the workspace (which is nullable by design), the LLM service, and forwarding the notification callback. The onBack callback correctly navigates back to the CODING agent.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (1)

128-168: LGTM! Well-structured schema dialog implementation.

The SchemaInfoDialog composable properly uses MaterialTheme.colorScheme for the overflow text color (line 154), following the coding guidelines. The dialog appropriately limits display to 10 tables with an overflow indicator.

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: 0

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

448-454: Consider adding a property to AgentType for session sidebar visibility.

The hardcoded list approach works correctly, but it could become a maintenance burden as more agent types are added. Each new type would require updating this conditional logic.

Consider adding a property to the AgentType enum itself:

enum class AgentType {
    CODING(requiresSessionSidebar = true),
    LOCAL_CHAT(requiresSessionSidebar = true),
    REMOTE(requiresSessionSidebar = true),
    CHAT_DB(requiresSessionSidebar = false),
    KNOWLEDGE(requiresSessionSidebar = false),
    CODE_REVIEW(requiresSessionSidebar = false);
    
    // ...
}

Then simplify the logic:

-val shouldShowSessionSidebar = selectedAgentType in listOf(
-    AgentType.CODING,
-    AgentType.LOCAL_CHAT,
-    AgentType.REMOTE
-)
+val shouldShowSessionSidebar = selectedAgentType.requiresSessionSidebar
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2bd950b and 277aff6.

📒 Files selected for processing (1)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt (2 hunks)
🧰 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/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.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/AutoDevApp.kt
🧠 Learnings (3)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AutoDevApp.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AutoDevApp.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AutoDevApp.kt
🧬 Code graph analysis (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/chat/SessionSidebar.kt (1)
  • SessionSidebar (39-106)
⏰ 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 (2)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt (2)

461-488: LGTM! Session sidebar conditional rendering implemented correctly.

The conditional rendering logic properly hides the SessionSidebar for agent types that have their own navigation (CHAT_DB, KNOWLEDGE, CODE_REVIEW). The session selection and new chat handlers are correctly preserved with appropriate fallback behavior.


175-176: No changes needed. The refactoring at lines 175-176 correctly uses type.getDisplayName() for config persistence, and the returned strings are compatible with AutoDevConfigWrapper.saveAgentTypePreference(). The format matches the hardcoded "Remote" string at line 736, confirming consistency across the codebase.

Likely an incorrect or invalid review comment.

Introduce ChatDB agent models, executor, schema linker, and CLI entry point. Update build configuration for new modules.
- Add SqlReviseAgent as a standalone SubAgent for SQL self-correction
- Add JSqlParserValidator for JVM-specific SQL validation using JSqlParser
- Add comprehensive tests for SqlReviseAgent, JSqlParserValidator, and SchemaLinker
- Update ChatDBAgentExecutor to use the new SqlReviseAgent
- Fix CodeFence property reference (languageId instead of language)

Implements the Revise Agent (自我修正闭环) feature from issue #508
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: 4

🧹 Nitpick comments (12)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (1)

239-249: Consider extracting shared SQL extraction logic.

This extractSqlFromResponse method duplicates similar logic found in ChatDBAgentExecutor.kt (lines 197-214). Consider extracting this into a shared utility function to reduce duplication and ensure consistent behavior.

Both implementations use CodeFence parsing followed by regex fallbacks. A shared utility could be placed in a common location like cc.unitmesh.devins.parser alongside CodeFence.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (1)

79-85: Keyword extraction may miss compound identifiers.

The regex [^a-z0-9\\s_] preserves underscores but the subsequent split on \\s+ won't handle snake_case identifiers like order_items. Consider splitting on underscores too to extract individual words (e.g., "order" and "items") for better matching.

 fun extractKeywords(query: String): List<String> {
     return query.lowercase()
         .replace(Regex("[^a-z0-9\\s_]"), " ")
-        .split(Regex("\\s+"))
+        .split(Regex("[\\s_]+"))
         .filter { it.length > 2 && it !in stopWords }
         .distinct()
 }
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/ChatDBCli.kt (2)

112-114: Hardcoded default temperature and maxTokens.

Defaults of 0.7 and 4096 are reasonable, but consider making these configurable via system properties for CLI flexibility, similar to other parameters.


174-175: Metadata access assumes string values for numeric fields.

result.metadata["revisionAttempts"] returns a String, and you're using ?: "0" for display. This works but relies on the executor populating metadata correctly. The ChatDBResult already has a revisionAttempts: Int field—consider using that directly instead of metadata.

-                println("🔄 Revision attempts: ${result.metadata["revisionAttempts"] ?: "0"}")
-                println("📈 Has visualization: ${result.metadata["hasVisualization"] ?: "false"}")
+                println("🔄 Revision attempts: ${result.revisionAttempts}")
+                println("📈 Has visualization: ${result.plotDslCode != null}")
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidator.kt (1)

92-108: String-based warning detection may produce false positives.

Using statement.toString() and contains("SELECT *") could match occurrences in string literals or comments. Consider using JSqlParser's AST visitor pattern to accurately detect SELECT * and missing WHERE clauses.

For more accurate detection, you could check the parsed statement type:

private fun collectWarnings(statement: Statement): List<String> {
    val warnings = mutableListOf<String>()
    
    when (statement) {
        is net.sf.jsqlparser.statement.select.Select -> {
            val selectBody = statement.selectBody
            if (selectBody is net.sf.jsqlparser.statement.select.PlainSelect) {
                if (selectBody.selectItems.any { it is net.sf.jsqlparser.statement.select.AllColumns }) {
                    warnings.add("Consider specifying explicit columns instead of SELECT *")
                }
            }
        }
        is net.sf.jsqlparser.statement.update.Update -> {
            if (statement.where == null) {
                warnings.add("UPDATE without WHERE clause will affect all rows")
            }
        }
        is net.sf.jsqlparser.statement.delete.Delete -> {
            if (statement.where == null) {
                warnings.add("DELETE without WHERE clause will affect all rows")
            }
        }
    }
    return warnings
}
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (1)

211-214: Last-resort SELECT extraction may capture incomplete SQL.

The regex (SELECT[\\s\\S]*?;) requires a semicolon terminator. If the LLM omits the trailing semicolon, valid SQL won't be extracted. Consider making the semicolon optional.

-            val selectPattern = Regex("(SELECT[\\s\\S]*?;)", RegexOption.IGNORE_CASE)
+            val selectPattern = Regex("(SELECT[\\s\\S]*?)(;|$)", RegexOption.IGNORE_CASE)
             val selectMatch = selectPattern.find(response)
-            return selectMatch?.groupValues?.get(1)?.trim()
+            return selectMatch?.groupValues?.get(1)?.trim()?.removeSuffix(";")?.plus(";")?.takeIf { it.length > 7 }

Or simpler:

-            val selectPattern = Regex("(SELECT[\\s\\S]*?;)", RegexOption.IGNORE_CASE)
+            val selectPattern = Regex("(SELECT[\\s\\S]*?;?)", RegexOption.IGNORE_CASE)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBModels.kt (1)

74-86: Potential redundancy between revisionAttempts field and metadata map.

ChatDBResult has a dedicated revisionAttempts: Int field but also a generic metadata: Map<String, String>. The CLI accesses metadata["revisionAttempts"] instead of the direct field. Consider removing the duplication or documenting the intended usage of each.

Either remove metadata if all structured fields are explicit, or document that metadata is for future extensibility and consumers should prefer typed fields.

mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt (5)

40-60: Align maxIterations with RunConfig.maxTurns to avoid config drift

maxIterations is a constructor parameter/override and is passed into ChatDBAgentExecutor, while RunConfig is hard-coded with maxTurns = 10. If a caller provides a different maxIterations, RunConfig will still cap turns at 10, which can be confusing.

Consider deriving RunConfig.maxTurns from maxIterations so they stay in sync, for example:

-        modelConfig = ModelConfig.default(),
-        runConfig = RunConfig(maxTurns = 10, maxTimeMinutes = 5)
+        modelConfig = ModelConfig.default(),
+        runConfig = RunConfig(maxTurns = maxIterations, maxTimeMinutes = 5)

82-88: Clarify and enforce lifecycle between databaseConnection, executor, and close()

The lazy executor captures a DatabaseConnection once:

private val executor: ChatDBAgentExecutor by lazy {
    val connection = databaseConnection ?: createDatabaseConnection(databaseConfig)
    databaseConnection = connection
    ChatDBAgentExecutor(..., databaseConnection = connection, ...)
}

Later, close() sets databaseConnection to null but does not affect the executor, which still holds the original connection reference. Reusing the same ChatDBAgent after close() may then route calls through a closed connection with no explicit error at the agent level.

It would be clearer to either:

  • Treat ChatDBAgent as single-use and throw if execute is called after close(), or
  • Allow reuse by reinitializing the connection / executor after close().

For example, the simplest explicit option is to track a closed flag and guard execute:

-    private var databaseConnection: DatabaseConnection? = null
+    private var databaseConnection: DatabaseConnection? = null
+    private var closed: Boolean = false
@@
-    suspend fun close() {
-        databaseConnection?.close()
-        databaseConnection = null
-    }
+    suspend fun close() {
+        closed = true
+        databaseConnection?.close()
+        databaseConnection = null
+    }
@@
     override suspend fun execute(
         input: ChatDBTask,
         onProgress: (String) -> Unit
     ): ToolResult.AgentResult {
+        check(!closed) { "ChatDBAgent has been closed and cannot be reused" }
         logger.info { "Starting ChatDB Agent for query: ${input.query}" }

Also applies to: 145-148


99-108: Make validateInput more defensive for stringly-typed inputs and bounds

validateInput assumes maxRows is a Number and generateVisualization a Boolean:

maxRows = (input["maxRows"] as? Number)?.toInt() ?: 100,
generateVisualization = input["generateVisualization"] as? Boolean ?: true

If the map comes from JSON/CLI/etc., these may arrive as strings (e.g. "100", "false"), in which case the casts fail and the defaults are silently used. You might also want to clamp maxRows to a reasonable positive range.

Consider parsing strings and enforcing bounds, e.g.:

val maxRowsValue = input["maxRows"]
val maxRows = when (maxRowsValue) {
    is Number -> maxRowsValue.toInt()
    is String -> maxRowsValue.toIntOrNull()
    else -> null
}?.coerceIn(1, 10_000) ?: 100

val generateVisualization = when (val gv = input["generateVisualization"]) {
    is Boolean -> gv
    is String -> gv.toBooleanStrictOrNull() ?: true
    else -> true
}

This makes the agent more robust against loosely-typed callers while retaining sensible defaults.


120-128: Preserve native types in metadata if the API allows it

You currently stringify all metadata values:

metadata = mapOf(
    "generatedSql" to (result.generatedSql ?: ""),
    "rowCount" to (result.queryResult?.rowCount?.toString() ?: "0"),
    "revisionAttempts" to result.revisionAttempts.toString(),
    "hasVisualization" to (result.plotDslCode != null).toString()
)

If ToolResult.AgentResult.metadata supports non-string values (e.g. Map<String, Any?>), it could be more convenient for consumers to keep rowCount as Int?, revisionAttempts as Int, and hasVisualization as Boolean rather than "0" / "true" / "false".

If the type is already Map<String, String>, then this is fine as-is and this comment can be ignored.


150-171: Prompt content looks solid; consider explicitly mentioning validation/sandboxing behavior

The SYSTEM_PROMPT clearly constrains the agent to read-only SELECT queries and adds good guidance on SQL formatting and PlotDSL blocks. Given you also have an AST-based validator and revise loop elsewhere in the system, you might optionally:

  • Explicitly mention that destructive statements (DDL/DML like INSERT, UPDATE, DELETE, DROP, etc.) will be rejected or revised.
  • Hint that the system may automatically adjust or reject unsafe SQL even if the user asks for it.

This can help align the model’s expectations with the actual safety mechanisms in the executor and validator.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 277aff6 and 16c9cc0.

📒 Files selected for processing (11)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBModels.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (1 hunks)
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/SchemaLinkerTest.kt (1 hunks)
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgentTest.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidator.kt (1 hunks)
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt (1 hunks)
  • mpp-ui/build.gradle.kts (1 hunks)
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/ChatDBCli.kt (1 hunks)
🧰 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/jvmMain/kotlin/cc/unitmesh/server/cli/ChatDBCli.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBModels.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidator.kt
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/SchemaLinkerTest.kt
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgentTest.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.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/jvmMain/kotlin/cc/unitmesh/server/cli/ChatDBCli.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBModels.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidator.kt
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/SchemaLinkerTest.kt
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgentTest.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt
🧠 Learnings (2)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update JVM CLI implementations: `CodingCliRenderer` and `ConsoleRenderer`

Applied to files:

  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/ChatDBCli.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Run `./gradlew :mpp-ui:generateI18n4kFiles` command when updating i18n resources

Applied to files:

  • mpp-ui/build.gradle.kts
🧬 Code graph analysis (5)
mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/DevInsTestCase.kt (4)
  • assertTrue (29-33)
  • assertFalse (38-40)
  • assertNotNull (54-59)
  • assertNull (64-68)
mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/SchemaLinkerTest.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/DevInsTestCase.kt (3)
  • assertTrue (29-33)
  • assertNotNull (54-59)
  • assertEquals (45-49)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (3)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/executor/BaseAgentExecutor.kt (1)
  • getLLMResponse (49-85)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (1)
  • extractSqlFromResponse (239-249)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/AstFormatter.kt (1)
  • appendLine (206-209)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (2)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/schema/ToolSchema.kt (2)
  • string (136-155)
  • integer (157-170)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (1)
  • extractSqlFromResponse (198-215)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt (1)
mpp-vscode/src/bridge/mpp-core.ts (1)
  • ToolRegistry (226-306)
⏰ 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 (30)
mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgentTest.kt (4)

1-11: LGTM! Well-organized test structure.

The test class is cleanly organized with clear section headers and comprehensive coverage of the SqlRevisionInput, SqlReviseAgentSchema, and SqlValidationResult data classes.


15-42: Good coverage of input validation and default values.

Tests properly verify both explicit parameter passing and default value behavior for maxAttempts.


46-75: Schema tests validate expected contract.

The tests appropriately check that getExampleUsage returns expected format strings and toJsonSchema() includes all required properties. The description check is appropriately flexible.


79-159: Comprehensive validation result and edge case coverage.

Good coverage of success/failure states, warnings handling, empty schema edge case, and complex SQL input. The tests validate the data structures behave correctly across different scenarios.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (5)

20-48: LGTM! Well-defined tool schema.

The SqlReviseAgentSchema properly defines all required and optional properties using the DeclarativeToolSchema pattern with appropriate property types.


62-82: LGTM! Clean input validation with appropriate defaults.

The validateInput method properly extracts and validates required fields while providing sensible defaults for optional ones.


86-165: LGTM! Solid self-correction loop implementation.

The execute method implements a clean revision loop with:

  • Proper attempt tracking and max attempts guard
  • Clear progress reporting
  • Appropriate handling of both validator-present and validator-absent cases
  • Meaningful metadata in results

268-276: LGTM! Clean, serializable data class.

The SqlRevisionInput data class is properly annotated with @Serializable and provides sensible defaults.


281-293: LGTM! Simple validator interface and result types.

The SqlValidatorInterface provides a clean contract for platform-specific implementations, and SqlValidationResult is properly structured with serialization support.

mpp-ui/build.gradle.kts (1)

627-666: LGTM! Follows established CLI task patterns.

The new runChatDBCli task correctly follows the same structure as other CLI tasks in this file (runDocumentCli, runCodingCli, etc.), properly configuring classpath, main class, and system properties for database connection parameters.

mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt (5)

1-11: LGTM! Well-structured JVM-specific test class.

Good setup with a single validator instance shared across tests. The test organization with clear section headers makes the coverage easy to understand.


15-60: Good basic validation coverage.

Tests properly verify valid SELECT queries, JOIN syntax, syntax errors (typo detection), and empty/blank SQL handling.


64-102: Excellent warning detection tests.

Good coverage of security-sensitive patterns:

  • SELECT * usage warnings
  • UPDATE without WHERE clause
  • DELETE without WHERE clause
  • Verification that safe statements don't trigger false warnings

106-141: Good complex query and DML coverage.

Tests appropriately verify that complex SELECT queries (subqueries, GROUP BY, HAVING, ORDER BY) and INSERT statements are handled correctly.


145-184: Appropriate edge case handling.

The multi-statement test correctly focuses on verifying no crash rather than asserting specific behavior (since JSqlParser behavior may vary). SQL comments handling is also tested.

mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/SchemaLinkerTest.kt (5)

1-16: LGTM! Clean test setup.

Good use of a shared SchemaLinker instance and kotlin.test assertions.


20-54: Good keyword extraction coverage.

Tests properly verify:

  • Basic keyword extraction
  • Stop word filtering
  • Short word filtering (length <= 2)
  • Lowercase normalization

58-90: Well-designed test fixture.

The createTestSchema() helper creates a realistic multi-table schema with appropriate columns, making tests readable and maintainable.


92-149: Comprehensive schema linking tests.

Good coverage of:

  • Direct table name matching
  • Column name matching
  • Fallback behavior when no matches found
  • Partial matching (e.g., "users" matching "user_accounts")

153-203: Solid edge case coverage.

Tests properly handle:

  • Empty query returning empty keywords
  • Empty schema returning empty results
  • Special characters in queries
  • Numeric content in queries

The SchemaLinkingResult test at the end validates the data class structure.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (3)

146-151: Integer division yields zero threshold for short strings.

When both strings are ≤2 characters, min(s1.length, s2.length) / 3 evaluates to 0, making fuzzy match succeed only on exact equality. This might be intentional, but verify it aligns with expected behavior for short identifiers like "id" or "pk".


156-167: Levenshtein implementation is correct and straightforward.

The standard DP approach is appropriate here. For very large schema names this allocates O(n*m) memory, but that's unlikely to be an issue for typical table/column names.


37-74: Link method logic is sound with a sensible fallback.

The fallback to include all tables when no matches are found (lines 62-64) ensures the LLM always has schema context. The confidence calculation correctly normalizes the average score.

mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/ChatDBCli.kt (1)

40-48: Password handling looks safe but consider masking in debug scenarios.

The password is read from system properties/args but not printed to stdout. However, if debug logging is added later, ensure the password isn't accidentally logged. Consider documenting that passwords can also be read from environment variables for better security practices.

mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidator.kt (2)

17-32: Validation correctly returns structured results.

The try-catch structure properly separates success/failure paths, and the SqlValidationResult provides clean error messages. Good separation of concerns.


63-87: Error message extraction handles common JSqlParser patterns well.

The regex patterns for "Encountered" and "Was expecting" cover typical parse errors. The .take(200) fallback prevents excessively long error messages.

mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (2)

48-152: Execute method is well-structured with clear step progression.

The 8-step workflow (schema fetch → linking → context build → SQL generation → extraction → validation/revision → execution → visualization) is logical and well-documented with progress callbacks. Error handling accumulates issues without early termination, allowing partial results.


144-151: Success condition may be too strict.

success = errors.isEmpty() && queryResult != null means any warning or non-fatal issue added to errors will mark the result as failed. Consider distinguishing between fatal errors and warnings, or only checking for query execution success.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBModels.kt (2)

12-40: ChatDBTask model is well-designed with sensible defaults.

The optional schema field allows pre-fetched schemas while defaulting to runtime fetch. Documentation is clear and helpful.


45-86: ChatDBResult provides comprehensive execution information.

The model captures all relevant execution details including SQL, results, visualization, errors, and metadata. The content property accessor pattern would be useful if ChatDBResult implemented a common result interface.

Comment on lines 92 to 93
var validatedSql = generatedSql
val validation = jsqlValidator.validate(validatedSql!!)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Redundant non-null assertion after assignment.

validatedSql is assigned from generatedSql which was just null-checked at line 86-89. The !! is unnecessary and could mask logic errors if future refactoring changes the flow.

-            var validatedSql = generatedSql
-            val validation = jsqlValidator.validate(validatedSql!!)
+            var validatedSql = generatedSql!!  // Safe: null case handled at line 86-89
+            val validation = jsqlValidator.validate(validatedSql)
📝 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
var validatedSql = generatedSql
val validation = jsqlValidator.validate(validatedSql!!)
var validatedSql = generatedSql!! // Safe: null case handled at line 86-89
val validation = jsqlValidator.validate(validatedSql)
🤖 Prompt for AI Agents
In mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
around lines 92-93, remove the redundant non-null assertion on validatedSql when
calling jsqlValidator.validate — call validate with validatedSql directly (or
make validatedSql explicitly non-nullable at assignment if that better reflects
the prior null-check) so you don't use !! at the call site.

Comment on lines 198 to 215
private fun extractSqlFromResponse(response: String): String? {
val codeFence = CodeFence.parse(response)
if (codeFence.languageId.lowercase() == "sql" && codeFence.text.isNotBlank()) {
return codeFence.text.trim()
}

// Try to find SQL block manually
val sqlPattern = Regex("```sql\\s*([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
val match = sqlPattern.find(response)
if (match != null) {
return match.groupValues[1].trim()
}

// Last resort: look for SELECT statement
val selectPattern = Regex("(SELECT[\\s\\S]*?;)", RegexOption.IGNORE_CASE)
val selectMatch = selectPattern.find(response)
return selectMatch?.groupValues?.get(1)?.trim()
}
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

🧩 Analysis chain

🏁 Script executed:

# First, find the CodeFence class and understand parse vs parseAll
rg "fun parse\(|fun parseAll\(" --type=kt -B2 -A8

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Search for SqlReviseAgent and its implementation
fd "SqlReviseAgent" --type=kt

Repository: phodal/auto-dev

Length of output: 228


🏁 Script executed:

# Look for all usages of CodeFence.parse and CodeFence.parseAll
rg "CodeFence\.(parse|parseAll)" --type=kt -B2 -A2

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Search for CodeFence class definition
rg "class CodeFence|fun parse\(|fun parseAll\(" -B2 -A8

Repository: phodal/auto-dev

Length of output: 50373


🏁 Script executed:

# Search for SqlReviseAgent
rg "SqlReviseAgent" -B2 -A5

Repository: phodal/auto-dev

Length of output: 11066


🏁 Script executed:

# Search for extractSqlFromResponse in different files
rg "extractSqlFromResponse" -B2 -A10

Repository: phodal/auto-dev

Length of output: 5510


🏁 Script executed:

# Let's examine the CodeFence.parse implementation to understand the difference
rg -A 30 "fun parse\(content: String\): CodeFence" mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/parser/CodeFence.kt

Repository: phodal/auto-dev

Length of output: 1389


🏁 Script executed:

# And look at parseAll implementation
rg -A 40 "fun parseAll\(content: String\): List<CodeFence>" mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/parser/CodeFence.kt

Repository: phodal/auto-dev

Length of output: 2188


Use CodeFence.parseAll to handle multiple code blocks, consistent with SqlReviseAgent.

The current implementation uses CodeFence.parse which returns only the first code fence from the response. When an LLM response contains multiple code blocks (e.g., explanation followed by SQL), this misses the actual SQL block. SqlReviseAgent correctly uses CodeFence.parseAll with .find { it.languageId.lowercase() == "sql" } to locate the SQL fence among all blocks.

Update extractSqlFromResponse to use parseAll and filter for the SQL fence before falling back to regex patterns.

🤖 Prompt for AI Agents
In mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
around lines 198-215, replace the single-code-fence usage with
CodeFence.parseAll: call CodeFence.parseAll(response).find {
it.languageId.lowercase() == "sql" && it.text.isNotBlank() } and return its
trimmed text if found; only if no SQL fence is found, continue to the existing
regex fallbacks (the ```sql block regex and the SELECT regex). Remove the use of
CodeFence.parse(response) and ensure languageId is lowercased for comparison and
text is trimmed before returning.

Comment on lines +94 to +105
val providerType = when (activeConfig.provider.lowercase()) {
"openai" -> LLMProviderType.OPENAI
"anthropic" -> LLMProviderType.ANTHROPIC
"google" -> LLMProviderType.GOOGLE
"deepseek" -> LLMProviderType.DEEPSEEK
"ollama" -> LLMProviderType.OLLAMA
"openrouter" -> LLMProviderType.OPENROUTER
"glm" -> LLMProviderType.GLM
"qwen" -> LLMProviderType.QWEN
"kimi" -> LLMProviderType.KIMI
else -> LLMProviderType.CUSTOM_OPENAI_BASE
}
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:

# Find the LLMProviderType enum definition and check for companion methods
fd -t f -e kt | xargs rg -l "enum class LLMProviderType|object LLMProviderType" | head -5

Repository: phodal/auto-dev

Length of output: 120


🏁 Script executed:

# Search for LLMProviderType definition with context
rg -A 30 "enum class LLMProviderType" --type=kt

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Search for other instances of provider string mapping (similar when expressions or fromString calls)
rg -n "when.*provider.*lowercase\(\)|LLMProviderType\." --type=kt -B 2 -A 2

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Read the ModelConfig.kt file to see LLMProviderType definition
cat mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ModelConfig.kt

Repository: phodal/auto-dev

Length of output: 4867


🏁 Script executed:

# Search for provider string mapping without type flag
rg -n "when.*provider.*lowercase\(\)" -B 2 -A 10

Repository: phodal/auto-dev

Length of output: 10599


🏁 Script executed:

# Search for fromString or similar companion methods related to provider
rg -n "fromString|valueOf" mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ModelConfig.kt

Repository: phodal/auto-dev

Length of output: 41


Duplicate provider mapping across multiple CLI files; add a dedicated parsing method to LLMProviderType.

This when-expression is duplicated across at least five CLI files (ChatDBCli, DocumentCli, ReviewCli, CodingCli, DomainDictCli), and also in JsExports. While LLMProviderType has a fromDisplayName() companion method, it matches on capitalized display names ("OpenAI", "Anthropic") rather than lowercase strings ("openai", "anthropic"), so it cannot replace this logic.

Add a companion method like fun fromLowercaseString(name: String): LLMProviderType? to centralize the string-to-enum mapping and reduce duplication.

🤖 Prompt for AI Agents
In mpp-ui/src/jvmMain/kotlin/cc/unitmesh/server/cli/ChatDBCli.kt lines 94-105,
the provider lowercase-to-enum when-expression is duplicated across multiple CLI
files; add a companion method to LLMProviderType to centralize this mapping.
Implement fun fromLowercaseString(name: String): LLMProviderType in
LLMProviderType's companion object that matches the same lowercase keys
("openai", "anthropic", "google", "deepseek", "ollama", "openrouter", "glm",
"qwen", "kimi") and returns the corresponding enum (and fall back to
LLMProviderType.CUSTOM_OPENAI_BASE or null if you prefer explicit handling).
Replace the inline when-expressions in ChatDBCli and the other CLI/JsExports
files to call
LLMProviderType.fromLowercaseString(activeConfig.provider.lowercase()) and
handle the fallback/nullable result consistently.

- Fix ExecutorFactory.kt runBlocking issue for WASM by using expect/actual pattern
- Move IdeaDatabaseConnection.kt from mpp-idea-core to ext-database module
- Add AssertJ dependency to mpp-idea for test assertions
- Replace AssertJ assertions with kotlin.test assertions in some test files
- Update test files to use standard Kotlin test assertions
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
mpp-idea/mpp-idea-exts/ext-database/src/main/kotlin/cc/unitmesh/database/connection/IdeaDatabaseConnection.kt (2)

21-55: Resource leak: PreparedStatement and ResultSet not closed on exception.

If an exception occurs during result processing (lines 30-50), neither resultSet nor stmt will be closed. Use Kotlin's use extension to ensure proper cleanup.

Apply this diff to fix the resource leak:

     override suspend fun executeQuery(sql: String): QueryResult = withContext(Dispatchers.IO) {
         try {
-            val stmt = ideaConnection.prepareStatement(
+            ideaConnection.prepareStatement(
                 sql,
                 java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE,
                 java.sql.ResultSet.CONCUR_READ_ONLY
-            )
-            val resultSet = stmt.executeQuery()
-
-            val metaData = resultSet.metaData
-            val columnCount = metaData.columnCount
-            val columns = (1..columnCount).map { metaData.getColumnLabel(it) }
-
-            val rows = mutableListOf<List<String>>()
-            while (resultSet.next()) {
-                val row = mutableListOf<String>()
-                for (i in 1..columnCount) {
-                    val value = resultSet.getObject(i)
-                    row.add(value?.toString() ?: "")
+            ).use { stmt ->
+                stmt.executeQuery().use { resultSet ->
+                    val metaData = resultSet.metaData
+                    val columnCount = metaData.columnCount
+                    val columns = (1..columnCount).map { metaData.getColumnLabel(it) }
+
+                    val rows = mutableListOf<List<String>>()
+                    while (resultSet.next()) {
+                        val row = mutableListOf<String>()
+                        for (i in 1..columnCount) {
+                            val value = resultSet.getObject(i)
+                            row.add(value?.toString() ?: "")
+                        }
+                        rows.add(row)
+                    }
+
+                    QueryResult(
+                        columns = columns,
+                        rows = rows,
+                        rowCount = rows.size
+                    )
                 }
-                rows.add(row)
             }
-
-            resultSet.close()
-            stmt.close()
-
-            QueryResult(
-                columns = columns,
-                rows = rows,
-                rowCount = rows.size
-            )
         } catch (e: Exception) {
             throw DatabaseException.queryFailed(sql, e.message ?: "Unknown error")
         }
     }

57-136: Resource leak: ResultSet objects not closed on exception in getSchema.

Multiple ResultSet objects (tableRs, pkRs, columnRs) are closed only in the happy path. If an exception occurs mid-processing, these resources will leak. Use Kotlin's use extension for each ResultSet.

Apply this diff to fix the primary resource leak pattern (similar changes needed for pkRs and columnRs):

-            val tableRs = metadata.getTables(null, null, "%", tableTypes)
-
-            while (tableRs.next()) {
+            metadata.getTables(null, null, "%", tableTypes).use { tableRs ->
+              while (tableRs.next()) {
                 // ... processing logic
-            }
-            tableRs.close()
+              }
+            }
mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownToHtmlConverterTest.kt (1)

9-10: Add documentation for @ignore or remove the annotation.

This test was added with the @Ignore annotation in the recent CI fix commit, but there's no explanation for why it's ignored. Tests added to the codebase should either:

  1. Run and pass, or
  2. Include a comment explaining why they're ignored (e.g., "TODO: Fix converter to handle X case" or "Blocked by issue #123")

Currently, this ignored test provides no value and masks any future regressions in convertMarkdownToHtml.

🧹 Nitpick comments (2)
mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/sketch/run/ShellSafetyCheckTest.kt (1)

14-136: Assertion migration preserves semantics; consider minor readability tweaks

All migrated assertions correctly check result.first for danger flags and result.second for the expected messages, so behavior is unchanged and clear.

If you want to reduce repetition, you could optionally destructure and/or add a tiny helper:

val (isDangerous, message) = ShellSafetyCheck.checkDangerousCommand(command)
assertTrue(isDangerous)
assertEquals("...", message)

or

private fun assertDanger(command: String, expectedMsg: String) {
    val (isDangerous, message) = ShellSafetyCheck.checkDangerousCommand(command)
    assertTrue(isDangerous)
    assertEquals(expectedMsg, message)
}

Then each test becomes a one-liner call to assertDanger(...). Not required, just a possible cleanup.

mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt (1)

64-71: Clarify error message to distinguish platform limitations from provider failures.

The current error message assumes null always indicates a platform limitation (non-JVM), but on JVM, provider.createExecutor(config) could also return null if the provider fails to create an executor. This could mislead debugging.

Consider refactoring to provide clearer error messages:

 if (registryProvider != null) {
-    // Use platform-specific blocking call for registered providers
-    // This works because on JVM, the provider caches the API token
-    return createExecutorBlocking(registryProvider, config)
-        ?: throw IllegalStateException(
-            "Failed to create executor for ${config.provider.displayName}. " +
-            "Provider is registered but returned null. " +
-            "On non-JVM platforms, use createAsync() instead."
-        )
+    val executor = createExecutorBlocking(registryProvider, config)
+    if (executor == null) {
+        throw IllegalStateException(
+            "Failed to create executor for ${config.provider.displayName}. " +
+            "Possible causes: " +
+            "1) Blocking executor creation not supported on this platform (JS/WASM) - use createAsync() instead. " +
+            "2) Provider failed to create executor - check provider configuration and logs."
+        )
+    }
+    return executor
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16c9cc0 and 56c1925.

📒 Files selected for processing (9)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt (2 hunks)
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.js.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/llm/ExecutorFactory.jvm.kt (2 hunks)
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.wasmJs.kt (1 hunks)
  • mpp-idea/build.gradle.kts (1 hunks)
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/sketch/run/ShellSafetyCheckTest.kt (1 hunks)
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownTest.kt (6 hunks)
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownToHtmlConverterTest.kt (3 hunks)
  • mpp-idea/mpp-idea-exts/ext-database/src/main/kotlin/cc/unitmesh/database/connection/IdeaDatabaseConnection.kt (3 hunks)
🧰 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-core/src/jvmMain/kotlin/cc/unitmesh/llm/ExecutorFactory.jvm.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.wasmJs.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.js.kt
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownTest.kt
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/sketch/run/ShellSafetyCheckTest.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt
  • mpp-idea/mpp-idea-exts/ext-database/src/main/kotlin/cc/unitmesh/database/connection/IdeaDatabaseConnection.kt
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownToHtmlConverterTest.kt
**/*.{kt,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/llm/ExecutorFactory.jvm.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.wasmJs.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.js.kt
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownTest.kt
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/sketch/run/ShellSafetyCheckTest.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt
  • mpp-idea/mpp-idea-exts/ext-database/src/main/kotlin/cc/unitmesh/database/connection/IdeaDatabaseConnection.kt
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownToHtmlConverterTest.kt
🧠 Learnings (1)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : Use `expect`/`actual` pattern for platform-specific code (for example: `Platform` implementations)

Applied to files:

  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownTest.kt
  • mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/sketch/run/ShellSafetyCheckTest.kt
🧬 Code graph analysis (4)
mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownTest.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/DevInsTestCase.kt (2)
  • assertEquals (45-49)
  • assertTrue (29-33)
mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/sketch/run/ShellSafetyCheckTest.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/DevInsTestCase.kt (3)
  • assertTrue (29-33)
  • assertEquals (45-49)
  • assertFalse (38-40)
mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt (3)
mpp-core/src/jsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.js.kt (1)
  • createExecutorBlocking (15-18)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/llm/ExecutorFactory.jvm.kt (1)
  • createExecutorBlocking (29-36)
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.wasmJs.kt (1)
  • createExecutorBlocking (15-18)
mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownToHtmlConverterTest.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/DevInsTestCase.kt (1)
  • assertEquals (45-49)
⏰ 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: Build
  • GitHub Check: Test mpp-core (KMP)
🔇 Additional comments (10)
mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownTest.kt (1)

8-8: Kotlin test assertions and import usage look good

Using kotlin.test.assertEquals and kotlin.test.assertTrue consistently across these tests is idiomatic and matches the rest of the file; the added assertTrue import is correct and there are no behavior changes introduced by these updates.

Also applies to: 20-20, 43-43, 55-55, 70-70, 85-85

mpp-idea/build.gradle.kts (1)

175-175: AssertJ dependency is correctly scoped for existing mpp-idea tests.

AssertJ 3.24.2 is already used in six existing mpp-idea test files (LineInfoTest, DiffSimplifierTest, PlanTaskTest, MarkdownPlanParserTest, ToolCallTest, PromptOptimizerTest). This change explicitly declares it as a shared testImplementation dependency for the mpp-idea module. The three new test classes (SchemaLinkerTest, SqlReviseAgentTest, JSqlParserValidatorTest) are in mpp-core and use kotlin.test assertions, not AssertJ—there is no contradiction.

mpp-idea/mpp-idea-exts/ext-database/src/main/kotlin/cc/unitmesh/database/connection/IdeaDatabaseConnection.kt (2)

138-144: LGTM!

The close method properly wraps the underlying connection close and handles exceptions appropriately.


146-172: Stub methods throw UnsupportedOperationException — consider documenting the implementation timeline.

Both createFromIdea and getAvailableDataSources throw exceptions with messages indicating future implementation. Given the PR's objective to enable ChatDB functionality with IDEA data sources, these stubs may block full integration.

Consider either:

  1. Adding a TODO/FIXME comment with an issue reference for tracking implementation.
  2. Implementing the actual IDEA Database API integration if it's needed for this PR.
     companion object {
+        // TODO: Implement IDEA Database API integration - see issue #XXX
         /**
          * Create database connection from IDEA data source
mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/sketch/run/ShellSafetyCheckTest.kt (1)

4-6: Switch to kotlin.test assertions is appropriate

Using kotlin.test.assertEquals/assertTrue/assertFalse aligns with Kotlin testing conventions and keeps the test dependency surface minimal. No issues here.

mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownToHtmlConverterTest.kt (1)

19-19: LGTM: Test data correction.

The added blank lines in the markdown input correctly align with the blank lines already present in the expected HTML output (lines 40, 45, 50), making the test data more consistent and accurate.

Also applies to: 24-24, 29-29

mpp-core/src/jsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.js.kt (1)

3-18: LGTM! JS platform correctly returns null for blocking executor creation.

The implementation appropriately returns null for the JS platform and provides clear documentation directing users to the asynchronous alternative.

mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/llm/ExecutorFactory.wasmJs.kt (1)

3-18: LGTM! WASM platform correctly returns null for blocking executor creation.

The implementation is consistent with the JS platform approach and appropriately indicates non-support for blocking executor creation.

mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt (1)

20-30: LGTM! The expect declaration is well-documented and correctly typed.

The platform-specific documentation clearly explains the behavior on each platform, and the nullable return type appropriately handles platforms that don't support blocking executor creation.

mpp-core/src/jvmMain/kotlin/cc/unitmesh/llm/ExecutorFactory.jvm.kt (1)

29-36: No timeout required—caching prevents repeated blocking calls.

The runBlocking design is intentional. The GithubCopilotClientProvider caches the API token with expiration checks, so getApiToken() performs an HTTP request only on the first call or when the token expires. Subsequent calls return the cached token within a mutex lock—a fast operation suitable for synchronous blocking. Additionally, in KoogLLMService, the executor is initialized lazily, deferring the blocking call until first use. This is service-level initialization code, not a UI hot path, so brief blocking is acceptable.

import org.assertj.core.api.Assertions.assertThat
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
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

🧩 Analysis chain

🏁 Script executed:

# Check assertion library usage patterns in the test suite
rg -n --type=kotlin 'import.*assertEquals|import.*assertThat' mpp-idea/mpp-idea-core/src/test/ | head -30

Repository: phodal/auto-dev

Length of output: 174


🏁 Script executed:

# Check for any test configuration or conventions in the project
find . -name 'build.gradle*' -o -name 'pom.xml' -o -name 'settings.gradle*' | head -5

Repository: phodal/auto-dev

Length of output: 197


🏁 Script executed:

# Look at the actual test file to confirm the changes and @Ignore status
cat -n mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownToHtmlConverterTest.kt | head -70

Repository: phodal/auto-dev

Length of output: 2174


🏁 Script executed:

# Check assertion library imports across all test files to determine project standard
rg --type=kotlin 'import.*assertEquals|import.*assertThat|import org\.assertj' mpp-idea/mpp-idea-core/src/test/ | cut -d: -f1 | sort | uniq -c | sort -rn

Repository: phodal/auto-dev

Length of output: 2578


🏁 Script executed:

# Get a sample of other test files to see what assertion libraries are used
rg --type=kotlin -l 'class.*Test' mpp-idea/mpp-idea-core/src/test/ | head -10 | xargs -I {} sh -c 'echo "=== {} ===" && head -20 {}'

Repository: phodal/auto-dev

Length of output: 6975


Remove @Ignore annotation to enable test validation.

The assertion library migration to kotlin.test.assertEquals is appropriate (line 5 and 59), with correct argument order (expected, actual). However, the @Ignore annotation (line 10) prevents this test from executing, leaving the changes unvalidated. Remove the annotation to verify the test passes with the markdown input corrections.

🤖 Prompt for AI Agents
In
mpp-idea/mpp-idea-core/src/test/kotlin/cc/unitmesh/devti/util/parser/MarkdownToHtmlConverterTest.kt
around lines 5 to 59, remove the @Ignore annotation on the test method (line
~10) so the test actually runs; the kotlin.test.assertEquals import and calls
are already correct (expected, actual) so simply delete the @Ignore annotation,
run the test suite to verify it passes, and commit the change.

- Add CHAT_DB branch to IdeaAgentApp.kt when expression
- Add createExecutorBlocking actual implementation for iOS platform
- Import SingleLLMPromptExecutor in iOS ExecutorFactory
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

🧹 Nitpick comments (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt (1)

355-361: ChatDB tab is exposed but only shows a “coming soon” placeholder

This branch compiles and follows existing layout patterns, but from a UX/feature‑parity standpoint it surfaces AgentType.CHAT_DB in IDEA without any of the text‑to‑SQL or data‑source UI that exists in mpp‑ui.

Consider either:

  • Wiring this branch to a proper ChatDB ViewModel/compose surface (mirroring the mpp‑ui ChatDB page), or
  • Temporarily hiding AgentType.CHAT_DB from the IDEA header until the implementation is ready, or updating the message to clearly state that ChatDB is not yet available in the IDEA plugin.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56c1925 and b383402.

📒 Files selected for processing (2)
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/llm/ExecutorFactory.ios.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt (1 hunks)
🧰 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-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/llm/ExecutorFactory.ios.kt
**/*.{kt,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/llm/ExecutorFactory.ios.kt
🧠 Learnings (3)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, and `JsRendererAdapter` in Kotlin

Applied to files:

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

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
🧬 Code graph analysis (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt (1)
  • IdeaEmptyStateMessage (242-256)
⏰ 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 (1)
mpp-core/src/iosMain/kotlin/cc/unitmesh/llm/ExecutorFactory.ios.kt (1)

3-3: Import for SingleLLMPromptExecutor is correct and necessary

The import cleanly matches the new iOS actual signature and keeps the file’s dependencies minimal.

Comment on lines +11 to +18
/**
* iOS implementation: Blocking executor creation not supported
* iOS doesn't support runBlocking, so we return null
*/
internal actual fun createExecutorBlocking(
provider: LLMClientProvider,
config: ModelConfig
): SingleLLMPromptExecutor? = 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

🧩 Analysis chain

🏁 Script executed:

# Find the expect declaration of createExecutorBlocking
find . -type f -name "*.kt" | xargs grep -l "expect.*createExecutorBlocking" | head -5

Repository: phodal/auto-dev

Length of output: 126


🏁 Script executed:

# Search for all usages of createExecutorBlocking with context
rg -n 'createExecutorBlocking\(' -C3

Repository: phodal/auto-dev

Length of output: 4745


🏁 Script executed:

# Look for ExecutorFactory files across platforms
fd "ExecutorFactory" -e kt

Repository: phodal/auto-dev

Length of output: 471


🏁 Script executed:

# Read the expect declaration to see full KDoc
cat -n mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt | head -35

Repository: phodal/auto-dev

Length of output: 1465


🏁 Script executed:

# Check Android implementation
cat -n mpp-core/src/androidMain/kotlin/cc/unitmesh/llm/ExecutorFactory.android.kt

Repository: phodal/auto-dev

Length of output: 340


🏁 Script executed:

# Find all call sites where createExecutorBlocking is invoked
rg 'createExecutorBlocking\(' --type kt -B2 -A5

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Retry without --type flag
rg 'createExecutorBlocking\(' -B2 -A5

Repository: phodal/auto-dev

Length of output: 4684


🏁 Script executed:

# Check if Android file has more content (looks incomplete)
wc -l mpp-core/src/androidMain/kotlin/cc/unitmesh/llm/ExecutorFactory.android.kt

Repository: phodal/auto-dev

Length of output: 135


🏁 Script executed:

# Get full Android file
cat mpp-core/src/androidMain/kotlin/cc/unitmesh/llm/ExecutorFactory.android.kt

Repository: phodal/auto-dev

Length of output: 277


🏁 Script executed:

# Check if there's a common/default implementation or if Android uses inheritance
rg -A10 "createExecutorBlocking" mpp-core/src/androidMain/ --type-list | head -20

Repository: phodal/auto-dev

Length of output: 448


🏁 Script executed:

# Look for build configuration to understand how Android source sets work
cat mpp-core/build.gradle.kts | grep -A5 -B5 "androidMain" | head -20

Repository: phodal/auto-dev

Length of output: 446


Update expect declaration KDoc to document all platforms returning null

The expect function in mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt currently documents only "JS/WASM: Returns null" but should also explicitly mention iOS (and Android if applicable) to accurately reflect all non-JVM platforms. The single call site at line 66 correctly handles the nullable result using the Elvis operator with an explicit error message directing users to createAsync(), so null-safety is verified. Update the KDoc to:

* - JVM: Uses runBlocking to create executor synchronously
* - iOS/JS/WASM: Returns null (blocking calls not supported)
🤖 Prompt for AI Agents
In mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/ExecutorFactory.kt (expect
declaration KDoc), update the documentation so it lists all platform behaviors:
replace the existing platform note that only mentions JS/WASM with the following
two bullets — "JVM: Uses runBlocking to create executor synchronously" and
"iOS/JS/WASM: Returns null (blocking calls not supported)"; keep the function
signature and nullable return as-is since call sites handle null via the Elvis
operator and direct users to createAsync().

#508

Introduce LlmSchemaLinker for LLM-powered schema linking with fallback, add table whitelist validation to SQL execution, and refactor SchemaLinker to support multiple strategies. Also improve SQL execution error handling and retries.
Update prompts and context for SQL generation and revision agents to enforce stricter schema usage and concise SQL-only responses.
Introduce DatabaseContentSchemaLinker to enhance schema linking accuracy using table sample data and system table filtering. Add support for English and Chinese keyword extraction and new database methods for retrieving sample rows and distinct values.
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: 6

♻️ Duplicate comments (2)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (2)

98-111: Fix validatedSql nullability and redundant !! to avoid type issues.

validatedSql is inferred as String?:

var validatedSql = generatedSql

but then:

val syntaxValidation = jsqlValidator.validate(validatedSql!!)
val tableValidation = if (syntaxValidation.isValid) {
    jsqlValidator.validateWithTableWhitelist(validatedSql, allTableNames)
} else {
    syntaxValidation
}

This both uses an unnecessary !! and passes a nullable validatedSql to validateWithTableWhitelist(sql: String, ...), which expects a non‑null String. The clean fix is to make validatedSql non‑nullable at the point we know generatedSql is non‑null:

-            var validatedSql = generatedSql
-
-            // Get all table names from schema for whitelist validation
+            var validatedSql = generatedSql!!  // safe: generatedSql null-checked above
+
+            // Get all table names from schema for whitelist validation
             val allTableNames = schema.tables.map { it.name }.toSet()
 
             // First validate syntax, then validate table names
-            val syntaxValidation = jsqlValidator.validate(validatedSql!!)
+            val syntaxValidation = jsqlValidator.validate(validatedSql)
             val tableValidation = if (syntaxValidation.isValid) {
-                jsqlValidator.validateWithTableWhitelist(validatedSql, allTableNames)
+                jsqlValidator.validateWithTableWhitelist(validatedSql, allTableNames)
             } else {
                 syntaxValidation
             }

This removes the redundant assertion and aligns types with the validator API.


252-269: Use CodeFence.parseAll and search for the SQL fence, consistent with SqlReviseAgent.

extractSqlFromResponse currently uses CodeFence.parse(response) and assumes a single code fence with languageId == "sql". As previously noted in an earlier review, this misses valid SQL when the LLM returns multiple code blocks (e.g., explanation + SQL) and relies on a single fence.

You already have a robust pattern in SqlReviseAgent:

val codeFences = CodeFence.parseAll(response)
val sqlFence = codeFences.find { it.languageId.lowercase() == "sql" && it.text.isNotBlank() }
if (sqlFence != null) return sqlFence.text.trim()

Refactor extractSqlFromResponse to use parseAll and only fall back to the regex/SELECT heuristics when no SQL fence is found.

🧹 Nitpick comments (7)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (2)

235-245: Extract duplicated SQL extraction logic to shared utility.

This extractSqlFromResponse method duplicates similar logic found in ChatDBAgentExecutor.kt (lines 251-268). The ChatDBAgentExecutor version includes an additional fallback that searches for SELECT statements, making it more robust.

Consider extracting this to a shared utility class (e.g., SqlExtractor or similar) to maintain DRY principles and ensure consistent SQL extraction behavior across the codebase. The shared version should include all fallback strategies from both implementations.


226-232: Preserve error details when LLM revision fails.

When an exception occurs, the error is logged but only null is returned. The caller receives a generic "Failed to generate revised SQL" message without insight into the actual failure reason (network error, timeout, rate limit, etc.).

Consider propagating error details via the progress callback:

         return try {
             val response = llmService.sendPrompt("$systemPrompt\n\n$userPrompt")
             extractSqlFromResponse(response)
         } catch (e: Exception) {
             logger.error(e) { "LLM revision failed: ${e.message}" }
+            onProgress("[ERROR] LLM call failed: ${e.message}")
             null
         }
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (1)

27-67: Heuristics look solid; consider slightly relaxing stop words and extracting constants for reuse.

Implementation is correct and self‑contained. Two minor tweaks you might consider:

  • Some terms in STOP_WORDS like "total", "amount", "value", "number" are common column names; filtering them may reduce recall when users ask about totals/amounts specifically.
  • The regex and Regex("\\s+") are re‑created on every call to extractKeywords; moving them to companion object constants would avoid repeated compilation on hot paths.

These are non‑blocking; current behavior is fine if you’re happy with the trade‑offs.

Also applies to: 124-186

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (1)

119-130: Strengthen keyword-extraction fallback when JSON parsing fails silently.

extractKeywords only falls back to fallbackLinker.extractKeywords on exceptions, but parseKeywordExtractionResponse catches Exception and returns an empty KeywordExtractionResult, so malformed JSON just yields an empty keyword list with no fallback.

To keep behavior robust when the LLM drifts from the JSON contract, consider:

  • Letting JSON parse errors bubble up so the outer try/catch triggers the fallback, or
  • Checking for an empty (keywords + entities + attributes) list and, in that case, delegating to fallbackLinker.extractKeywords(query).

This keeps LLM‑based extraction while guaranteeing at least keyword‑based behavior when the response is off‑format.

Also applies to: 141-147

mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (2)

140-187: Re-validate revised SQL before executing to keep whitelist and syntax guarantees.

When initial validation fails, you call SqlReviseAgent and, on success, set:

validatedSql = revisionResult.content
generatedSql = validatedSql

but you don’t re-run jsqlValidator.validate / validateWithTableWhitelist on the revised SQL; execution goes straight to databaseConnection.executeQuery. This means revised SQL might bypass your AST-based checks (e.g., table whitelist).

To keep the “validated before execution” guarantee, consider re-running the same validation pipeline on the final validatedSql and short-circuiting execution if it’s still invalid. This is especially relevant if prompts or revise logic evolve over time.


271-305: Align PlotDSL extraction with multi-fence handling to mirror SQL extraction.

generateVisualization calls CodeFence.parse(response) and assumes the first fence is plotdsl. As with SQL extraction, LLM responses can contain multiple code blocks, so this may miss the actual visualization block.

You can make this more robust by:

  • Using CodeFence.parseAll(response) and finding the first fence with languageId.lowercase() == "plotdsl" and non‑blank text.
  • Only then falling back to the regex ```plotdsl ... ``` search.

This keeps behavior consistent with the recommended SQL extraction path and SqlReviseAgent.

mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidator.kt (1)

20-184: Validator implementation matches intended behavior.

The syntax validation, table-whitelist check, and warning extraction are all wired correctly and align with how ChatDBAgentExecutor and SqlReviseAgent use this class. I don’t see functional issues here.

If you ever see perf pressure, you could consider sharing a parsed Statement between validate, validateWithTableWhitelist, and extractTableNames, but that’s not necessary now.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b383402 and bb4b2e3.

📒 Files selected for processing (7)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (1 hunks)
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/SchemaLinkerTest.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidator.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/SchemaLinkerTest.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-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidator.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
**/*.{kt,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidator.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
🧠 Learnings (3)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update JVM CLI implementations: `CodingCliRenderer` and `ConsoleRenderer`

Applied to files:

  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, and `JsRendererAdapter` in Kotlin

Applied to files:

  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
🧬 Code graph analysis (5)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (2)
  • extractKeywords (25-25)
  • extractKeywords (124-130)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (1)
  • extractKeywords (119-130)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (2)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/executor/BaseAgentExecutor.kt (1)
  • getLLMResponse (49-85)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (1)
  • extractSqlFromResponse (235-245)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt (1)
mpp-vscode/src/bridge/mpp-core.ts (1)
  • ToolRegistry (226-306)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (2)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/schema/ToolSchema.kt (2)
  • string (136-155)
  • integer (157-170)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (1)
  • extractSqlFromResponse (252-269)
⏰ 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: Build
  • GitHub Check: Test mpp-core (KMP)
🔇 Additional comments (1)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt (1)

40-171: Agent wiring and configuration look consistent and correct.

The ChatDBAgent setup (ToolRegistry, ToolOrchestrator, lazy ChatDBAgentExecutor, and SYSTEM_PROMPT) is coherent and matches the intended Text2SQL flow. Input validation and metadata mapping for ToolResult.AgentResult are reasonable defaults.

No changes needed from this file’s perspective.

Comment on lines +70 to +110
override suspend fun link(query: String, schema: DatabaseSchema): SchemaLinkingResult {
return try {
// Build schema description for LLM
val schemaDescription = buildSchemaDescription(schema)

// Ask LLM to identify relevant tables and columns
val prompt = SCHEMA_LINKING_PROMPT
.replace("{{SCHEMA}}", schemaDescription)
.replace("{{QUERY}}", query)
val response = llmService.sendPrompt(prompt)

// Parse LLM response
val llmResult = parseSchemaLinkingResponse(response)

// Validate that tables/columns exist in schema
val validTables = llmResult.tables.filter { tableName ->
schema.tables.any { it.name.equals(tableName, ignoreCase = true) }
}
val validColumns = llmResult.columns.filter { colRef ->
val parts = colRef.split(".")
if (parts.size == 2) {
val table = schema.tables.find { it.name.equals(parts[0], ignoreCase = true) }
table?.columns?.any { it.name.equals(parts[1], ignoreCase = true) } == true
} else false
}

// Extract keywords for the result
val keywords = extractKeywords(query)

// If LLM didn't find valid tables, fall back to keyword linker
if (validTables.isEmpty()) {
return fallbackLinker.link(query, schema)
}

SchemaLinkingResult(
relevantTables = validTables,
relevantColumns = validColumns,
keywords = keywords,
confidence = llmResult.confidence
)
} 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.

⚠️ Potential issue | 🟠 Major

Normalize table/column names to schema casing after LLM validation.

You validate LLM‑returned tables/columns against DatabaseSchema using equals(..., ignoreCase = true) but keep the original LLM strings in validTables/validColumns. Downstream, ChatDBAgentExecutor.buildRelevantSchemaDescription filters with it.name in linkingResult.relevantTables (case‑sensitive), so users from the LLM won’t match Users in the schema and the relevant schema section may end up empty while ALLOWED TABLES still lists the LLM names.

Consider mapping back to canonical schema names during validation, e.g.:

val validTables = schema.tables
    .filter { schemaTable -> llmResult.tables.any { it.equals(schemaTable.name, ignoreCase = true) } }
    .map { it.name }

and similarly normalizing columns to tableName.columnName using actual schema casing.

🤖 Prompt for AI Agents
In mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt
around lines 70 to 110, the LLM-validated table and column names are kept with
the original LLM casing which can fail later because other code compares against
schema names case-sensitively; modify the validation to map LLM results back to
the canonical schema casing: for tables, iterate schema.tables and select those
whose name equals any LLM table name ignoring case, then return the
schemaTable.name; for columns, resolve each LLM column reference to the matching
schema table and column (ignore case for matching) and emit a normalized
"TableName.ColumnName" string using the schema's actual table and column names;
ensure fallback behavior remains the same and preserve llmResult.confidence and
keywords.

Comment on lines +35 to +38
"schemaDescription" to string(
description = "Database schema description for context",
required = true
),
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

Inconsistency between schema requirement and data class default.

The schema marks schemaDescription as required = true (line 37), but SqlRevisionInput (line 269) provides a default empty string. This mismatch could cause confusion for tool consumers expecting the field to be mandatory.

Apply this diff to align the schema with the actual data class behavior:

         "schemaDescription" to string(
             description = "Database schema description for context",
-            required = true
+            required = false
         ),
📝 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
"schemaDescription" to string(
description = "Database schema description for context",
required = true
),
"schemaDescription" to string(
description = "Database schema description for context",
required = false
),
🤖 Prompt for AI Agents
In mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
around lines 35 to 38, the OpenAPI/schema entry marks "schemaDescription" as
required = true while the SqlRevisionInput data class provides a default empty
string, creating an inconsistency; update the schema to reflect the data class
by setting required = false (or remove the required flag entirely) so the field
is optional, and keep the description text as-is or clarify it indicates an
optional schema description.

Comment on lines +62 to +67
class SqlReviseAgent(
private val llmService: KoogLLMService,
private val sqlValidator: SqlValidatorInterface? = null
) : SubAgent<SqlRevisionInput, ToolResult.AgentResult>(
definition = createDefinition()
) {
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

Enforce SQL validator to prevent destructive queries.

Issue #508 specifies "AST-based SQL validation to block destructive queries" as an enterprise security requirement. However, SqlValidatorInterface is optional (nullable), meaning destructive SQL (DROP, DELETE, TRUNCATE, etc.) could be generated and returned without validation.

Consider making the validator mandatory or adding a default validator:

 class SqlReviseAgent(
     private val llmService: KoogLLMService,
-    private val sqlValidator: SqlValidatorInterface? = null
+    private val sqlValidator: SqlValidatorInterface
 ) : SubAgent<SqlRevisionInput, ToolResult.AgentResult>(
     definition = createDefinition()
 ) {

Alternatively, if the validator must remain optional, add explicit warnings in logs and metadata when operating without validation.

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

🤖 Prompt for AI Agents
In mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
around lines 62 to 67, the SqlValidatorInterface is nullable allowing
destructive SQL to bypass validation; make the validator mandatory by removing
the nullable type and requiring an instance in the constructor, or supply a
secure default validator implementation when null (e.g., an AST-based blocker
that rejects DROP/DELETE/TRUNCATE), and if you must keep it optional instead add
explicit runtime safeguards: log a prominent warning and set metadata flags
indicating validation is disabled, and ensure any execute/return path checks for
validator presence and refuses to return or execute queries that match
destructive patterns when no validator is present.

Comment on lines +90 to +155
onProgress("🔄 SQL Revise Agent - Starting revision")
onProgress("Original query: ${input.originalQuery.take(50)}...")
onProgress("Error: ${input.errorMessage.take(80)}...")

var currentSql = input.failedSql
var currentError = input.errorMessage
val attempts = input.previousAttempts.toMutableList()
var attemptCount = attempts.size

while (attemptCount < input.maxAttempts) {
attemptCount++
onProgress("📝 Revision attempt $attemptCount/${input.maxAttempts}")

// Build revision context
val context = buildRevisionContext(input, currentSql, currentError, attempts)

// Ask LLM for revised SQL
val revisedSql = askLLMForRevision(context, onProgress)

if (revisedSql == null) {
onProgress("❌ Failed to generate revised SQL")
return ToolResult.AgentResult(
success = false,
content = "Failed to generate revised SQL after $attemptCount attempts",
metadata = mapOf(
"attempts" to attemptCount.toString(),
"lastError" to currentError
)
)
}

attempts.add(revisedSql)

// Validate the revised SQL if validator is available
if (sqlValidator != null) {
val validation = sqlValidator.validate(revisedSql)
if (validation.isValid) {
onProgress("✅ SQL validated successfully")
return ToolResult.AgentResult(
success = true,
content = revisedSql,
metadata = mapOf(
"attempts" to attemptCount.toString(),
"validated" to "true"
)
)
} else {
currentSql = revisedSql
currentError = validation.errors.joinToString("; ")
onProgress("⚠️ Validation failed: ${currentError.take(50)}...")
}
} else {
// No validator, return the revised SQL
onProgress("✅ SQL revised (no validation)")
return ToolResult.AgentResult(
success = true,
content = revisedSql,
metadata = mapOf(
"attempts" to attemptCount.toString(),
"validated" to "false"
)
)
}
}

onProgress("❌ Max revision attempts reached")
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

Remove emoji characters for WASM compatibility.

Progress messages throughout the execute method use emoji characters (🔄, 📝, ❌, ✅, ⚠️). According to the coding guidelines, emoji and UTF-8 characters should be avoided in WASM code. Since this file is in commonMain, it will be compiled for all platforms including WASM.

Replace emoji with plain-text prefixes:

-        onProgress("🔄 SQL Revise Agent - Starting revision")
+        onProgress("[INFO] SQL Revise Agent - Starting revision")
         onProgress("Original query: ${input.originalQuery.take(50)}...")
         onProgress("Error: ${input.errorMessage.take(80)}...")
 
         var currentSql = input.failedSql
         var currentError = input.errorMessage
         val attempts = input.previousAttempts.toMutableList()
         var attemptCount = attempts.size
 
         while (attemptCount < input.maxAttempts) {
             attemptCount++
-            onProgress("📝 Revision attempt $attemptCount/${input.maxAttempts}")
+            onProgress("[ATTEMPT] Revision attempt $attemptCount/${input.maxAttempts}")
 
             // Build revision context
             val context = buildRevisionContext(input, currentSql, currentError, attempts)
 
             // Ask LLM for revised SQL
             val revisedSql = askLLMForRevision(context, onProgress)
 
             if (revisedSql == null) {
-                onProgress("❌ Failed to generate revised SQL")
+                onProgress("[ERROR] Failed to generate revised SQL")
                 return ToolResult.AgentResult(
                     success = false,
                     content = "Failed to generate revised SQL after $attemptCount attempts",
                     metadata = mapOf(
                         "attempts" to attemptCount.toString(),
                         "lastError" to currentError
                     )
                 )
             }
 
             attempts.add(revisedSql)
 
             // Validate the revised SQL if validator is available
             if (sqlValidator != null) {
                 val validation = sqlValidator.validate(revisedSql)
                 if (validation.isValid) {
-                    onProgress("✅ SQL validated successfully")
+                    onProgress("[SUCCESS] SQL validated successfully")
                     return ToolResult.AgentResult(
                         success = true,
                         content = revisedSql,
                         metadata = mapOf(
                             "attempts" to attemptCount.toString(),
                             "validated" to "true"
                         )
                     )
                 } else {
                     currentSql = revisedSql
                     currentError = validation.errors.joinToString("; ")
-                    onProgress("⚠️ Validation failed: ${currentError.take(50)}...")
+                    onProgress("[WARN] Validation failed: ${currentError.take(50)}...")
                 }
             } else {
                 // No validator, return the revised SQL
-                onProgress("✅ SQL revised (no validation)")
+                onProgress("[SUCCESS] SQL revised (no validation)")
                 return ToolResult.AgentResult(
                     success = true,
                     content = revisedSql,
                     metadata = mapOf(
                         "attempts" to attemptCount.toString(),
                         "validated" to "false"
                     )
                 )
             }
         }
 
-        onProgress("❌ Max revision attempts reached")
+        onProgress("[ERROR] Max revision attempts reached")
📝 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
onProgress("🔄 SQL Revise Agent - Starting revision")
onProgress("Original query: ${input.originalQuery.take(50)}...")
onProgress("Error: ${input.errorMessage.take(80)}...")
var currentSql = input.failedSql
var currentError = input.errorMessage
val attempts = input.previousAttempts.toMutableList()
var attemptCount = attempts.size
while (attemptCount < input.maxAttempts) {
attemptCount++
onProgress("📝 Revision attempt $attemptCount/${input.maxAttempts}")
// Build revision context
val context = buildRevisionContext(input, currentSql, currentError, attempts)
// Ask LLM for revised SQL
val revisedSql = askLLMForRevision(context, onProgress)
if (revisedSql == null) {
onProgress(" Failed to generate revised SQL")
return ToolResult.AgentResult(
success = false,
content = "Failed to generate revised SQL after $attemptCount attempts",
metadata = mapOf(
"attempts" to attemptCount.toString(),
"lastError" to currentError
)
)
}
attempts.add(revisedSql)
// Validate the revised SQL if validator is available
if (sqlValidator != null) {
val validation = sqlValidator.validate(revisedSql)
if (validation.isValid) {
onProgress(" SQL validated successfully")
return ToolResult.AgentResult(
success = true,
content = revisedSql,
metadata = mapOf(
"attempts" to attemptCount.toString(),
"validated" to "true"
)
)
} else {
currentSql = revisedSql
currentError = validation.errors.joinToString("; ")
onProgress("⚠️ Validation failed: ${currentError.take(50)}...")
}
} else {
// No validator, return the revised SQL
onProgress(" SQL revised (no validation)")
return ToolResult.AgentResult(
success = true,
content = revisedSql,
metadata = mapOf(
"attempts" to attemptCount.toString(),
"validated" to "false"
)
)
}
}
onProgress(" Max revision attempts reached")
onProgress("[INFO] SQL Revise Agent - Starting revision")
onProgress("Original query: ${input.originalQuery.take(50)}...")
onProgress("Error: ${input.errorMessage.take(80)}...")
var currentSql = input.failedSql
var currentError = input.errorMessage
val attempts = input.previousAttempts.toMutableList()
var attemptCount = attempts.size
while (attemptCount < input.maxAttempts) {
attemptCount++
onProgress("[ATTEMPT] Revision attempt $attemptCount/${input.maxAttempts}")
// Build revision context
val context = buildRevisionContext(input, currentSql, currentError, attempts)
// Ask LLM for revised SQL
val revisedSql = askLLMForRevision(context, onProgress)
if (revisedSql == null) {
onProgress("[ERROR] Failed to generate revised SQL")
return ToolResult.AgentResult(
success = false,
content = "Failed to generate revised SQL after $attemptCount attempts",
metadata = mapOf(
"attempts" to attemptCount.toString(),
"lastError" to currentError
)
)
}
attempts.add(revisedSql)
// Validate the revised SQL if validator is available
if (sqlValidator != null) {
val validation = sqlValidator.validate(revisedSql)
if (validation.isValid) {
onProgress("[SUCCESS] SQL validated successfully")
return ToolResult.AgentResult(
success = true,
content = revisedSql,
metadata = mapOf(
"attempts" to attemptCount.toString(),
"validated" to "true"
)
)
} else {
currentSql = revisedSql
currentError = validation.errors.joinToString("; ")
onProgress("[WARN] Validation failed: ${currentError.take(50)}...")
}
} else {
// No validator, return the revised SQL
onProgress("[SUCCESS] SQL revised (no validation)")
return ToolResult.AgentResult(
success = true,
content = revisedSql,
metadata = mapOf(
"attempts" to attemptCount.toString(),
"validated" to "false"
)
)
}
}
onProgress("[ERROR] Max revision attempts reached")

Comment on lines +180 to +183
appendLine("## Available Schema (USE ONLY THESE TABLES AND COLUMNS)")
appendLine("```")
appendLine(input.schemaDescription.take(2000))
appendLine("```")
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

Schema truncation may break table/column definitions.

The hard-coded 2000-character limit could cut off schema information mid-table or mid-column, leaving the LLM with incomplete context. This may lead to incorrect SQL generation if critical tables or columns are truncated.

Consider one of the following solutions:

  1. Make the limit configurable via SqlRevisionInput
  2. Implement intelligent truncation at table boundaries
  3. Prioritize tables mentioned in the failed SQL

Example for solution 1:

 data class SqlRevisionInput(
     val originalQuery: String,
     val failedSql: String,
     val errorMessage: String,
     val schemaDescription: String = "",
     val previousAttempts: List<String> = emptyList(),
-    val maxAttempts: Int = 3
+    val maxAttempts: Int = 3,
+    val maxSchemaLength: Int = 2000
 )

Then update line 182:

-        appendLine(input.schemaDescription.take(2000))
+        appendLine(input.schemaDescription.take(input.maxSchemaLength))

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

🤖 Prompt for AI Agents
In mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
around lines 180 to 183, the schemaDescription is being naively truncated to
2000 characters which can cut table/column entries mid-definition; update the
code to use a configurable limit supplied via SqlRevisionInput (add a
maxSchemaChars:Int with a sensible default), or implement intelligent truncation
that slices whole table blocks (split by table/column delimiters and include
until limit) and/or prioritizes tables referenced in the failed SQL, and then
replace the hard-coded 2000 with the chosen max before appending the schema so
the LLM receives complete table/column contexts.

Comment on lines 214 to 227
private fun buildRelevantSchemaDescription(
schema: DatabaseSchema,
linkingResult: SchemaLinkingResult
): String {
val relevantTables = schema.tables.filter { it.name in linkingResult.relevantTables }
return buildString {
appendLine("## Database Schema (USE ONLY THESE TABLES)")
appendLine()
relevantTables.forEach { table ->
appendLine("Table: ${table.name}")
appendLine("Columns: ${table.columns.joinToString(", ") { "${it.name} (${it.type})" }}")
appendLine()
}
}
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

Make schema description lookup case-insensitive to match schema linker behavior.

buildRelevantSchemaDescription filters tables with:

val relevantTables = schema.tables.filter { it.name in linkingResult.relevantTables }

but LlmSchemaLinker validates tables using equals(..., ignoreCase = true) and returns the LLM’s original casing. If the LLM returns users but the schema table is Users, this filter yields an empty list and the generated schema description becomes blank even though the linker found relevant tables.

Either:

  • Normalize linkingResult.relevantTables to schema casing in the linker (preferred), or
  • Change this filter to be case‑insensitive, e.g.:
val relevantTableNames = linkingResult.relevantTables.map { it.lowercase() }.toSet()
val relevantTables = schema.tables.filter { it.name.lowercase() in relevantTableNames }

so the prompt always includes the intended tables.

🤖 Prompt for AI Agents
In mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
around lines 214 to 227, the current filter compares table names with
linkingResult.relevantTables using a case-sensitive check which can miss matches
when the LLM returns different casing; update the logic to perform a
case-insensitive match by building a set of lowercased relevant table names from
linkingResult.relevantTables and then filter schema.tables by checking
table.name.lowercase() against that set so the intended tables are included
regardless of casing.

Enforce stricter rules for LLM SQL output and extract only the first SQL statement to prevent multiple statement errors. Also, filter table metadata by current database/catalog.
KeywordSchemaLinker was extracted from SchemaLinker.kt into its own file for better modularity and code organization. Updated references to use SchemaLinker type for fallback linker.
Introduce NlpTokenizer with MyNLP support on JVM for improved Chinese segmentation and fallback regex-based tokenization on other platforms. Updates KeywordSchemaLinker to use platform-specific extraction.
Moves fallback keyword extraction logic to a new FallbackNlpTokenizer object and updates all usages. Adds JVM tests for NLP tokenization and a test script for comparison.
Delete unused SqlRevisionContext to simplify ChatDBModels and reduce code clutter.
Introduce unit tests to verify FallbackNlpTokenizer behavior and update implementation as needed.
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: 4

♻️ Duplicate comments (4)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (1)

96-105: Case-sensitivity issue persists (duplicate of prior feedback).

The validation at lines 96-105 still retains LLM casing for validTables and validColumns. This was flagged in a prior review. The fix should map to canonical schema names.

mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (3)

111-117: Redundant non-null assertion (duplicate of prior feedback).

At line 111, generatedSql is guaranteed non-null due to the early return at lines 105-108. The !! at line 117 is redundant.


234-234: Case-sensitive table lookup (duplicate of prior feedback).

Line 234 uses case-sensitive in operator. If linkingResult.relevantTables contains "users" but the schema has "Users", no tables will match. This was flagged previously.


286-303: Use CodeFence.parseAll to handle multiple code blocks (duplicate of prior feedback).

The current implementation uses CodeFence.parse which returns only the first code fence. When an LLM response contains multiple blocks (e.g., explanation followed by SQL), this may miss the actual SQL block. The SqlReviseAgent correctly uses CodeFence.parseAll with filtering.

🧹 Nitpick comments (7)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizer.kt (1)

439-459: Consider making the dictionary extensible.

The hardcoded dictionary (~100 words) is reasonable for a fallback but limits segmentation quality for domain-specific terms. The comment on line 441 already notes this could be loaded from external resources.

For future extensibility:

internal object ChineseSegmenter {
    private val baseDictionary = setOf(/* current words */)
    private val customDictionary = mutableSetOf<String>()
    
    val dictionary: Set<String> get() = baseDictionary + customDictionary
    
    fun addCustomWords(words: Collection<String>) {
        customDictionary.addAll(words)
    }
    // ...
}
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/KeywordSchemaLinker.kt (1)

69-120: Consider deduplicating column scoring logic for clarity

Both calculateTableRelevance and calculateColumnRelevance embed very similar column-name scoring rules; extracting a shared helper for column scoring would reduce duplication and keep future tuning of weights in a single place.

mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizerTest.kt (1)

11-98: NLP tokenizer JVM tests provide good coverage

The test suite exercises Chinese-only, mixed, and English-only queries plus fallback comparison and several realistic DB-oriented prompts; structure and assertions look solid. You might eventually trim the println diagnostics to keep test logs cleaner, but that’s optional.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/DatabaseContentSchemaLinker.kt (1)

156-164: Consider using a more robust JSON extraction pattern.

The regex \{[^{}]*\} works for flat JSON but will fail if the LLM includes nested objects. Since the current expected format is flat, this should work, but consider aligning with LlmSchemaLinker.extractJsonFromResponse for consistency if that implementation differs.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (1)

185-192: Remove unused method buildSchemaDescriptionSync.

This synchronous variant is defined but never called. If it's intended for future use, consider removing it to avoid dead code accumulation.

-    /**
-     * Build schema description without sample data (synchronous version)
-     */
-    private fun buildSchemaDescriptionSync(schema: DatabaseSchema): String {
-        return schema.tables.joinToString("\n\n") { table ->
-            val columns = table.columns.joinToString(", ") { col ->
-                "${col.name} (${col.type}${if (col.isPrimaryKey) ", PK" else ""})"
-            }
-            "Table: ${table.name}\nColumns: $columns"
-        }
-    }
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (2)

44-50: Fallback chain may cause confusion.

The schema linker initialization creates LlmSchemaLinker as the fallback for DatabaseContentSchemaLinker. However, LlmSchemaLinker already has KeywordSchemaLinker as its fallback. This creates a 3-level chain: DatabaseContentSchemaLinker → LlmSchemaLinker → KeywordSchemaLinker. While functional, consider documenting this chain or simplifying if the layered fallbacks add latency without proportional benefit.


353-361: Apply same CodeFence.parseAll fix for PlotDSL extraction.

Similar to extractSqlFromResponse, this uses CodeFence.parse which may miss the PlotDSL block if the LLM includes multiple code fences. Use CodeFence.parseAll with filtering for consistency.

-            val codeFence = CodeFence.parse(response)
-            if (codeFence.languageId.lowercase() == "plotdsl" && codeFence.text.isNotBlank()) {
-                return codeFence.text.trim()
-            }
+            val codeFences = CodeFence.parseAll(response)
+            val plotFence = codeFences.find { it.languageId.lowercase() == "plotdsl" && it.text.isNotBlank() }
+            if (plotFence != null) {
+                return plotFence.text.trim()
+            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb4b2e3 and 038bd93.

📒 Files selected for processing (18)
  • mpp-core/build.gradle.kts (1 hunks)
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.android.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBModels.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/DatabaseContentSchemaLinker.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizer.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/KeywordSchemaLinker.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt (1 hunks)
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizerTest.kt (1 hunks)
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.ios.kt (1 hunks)
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.js.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.jvm.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/ExposedDatabaseConnection.kt (3 hunks)
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizerTest.kt (1 hunks)
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.wasmJs.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBModels.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-core/src/androidMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.android.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.ios.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.jvm.kt
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizerTest.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/ExposedDatabaseConnection.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.js.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.wasmJs.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/DatabaseContentSchemaLinker.kt
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizerTest.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/KeywordSchemaLinker.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizer.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt
**/*.{kt,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.android.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.ios.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.jvm.kt
  • mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizerTest.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/ExposedDatabaseConnection.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.js.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.wasmJs.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/DatabaseContentSchemaLinker.kt
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizerTest.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/KeywordSchemaLinker.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizer.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt
🧠 Learnings (3)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/chatdb/NlpTokenizer.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : Avoid emoji and UTF-8 characters in WASM code

Applied to files:

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

Applied to files:

  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
🧬 Code graph analysis (6)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt (1)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/ExposedDatabaseConnection.kt (1)
  • executeQuery (19-55)
mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizerTest.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/DevInsTestCase.kt (2)
  • assertEquals (45-49)
  • assertTrue (29-33)
mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizerTest.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/DevInsTestCase.kt (1)
  • assertTrue (29-33)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/KeywordSchemaLinker.kt (4)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.kt (1)
  • extractKeywords (18-18)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.jvm.kt (1)
  • extractKeywords (37-50)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (1)
  • extractKeywords (130-141)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (1)
  • extractKeywords (24-24)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (3)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/KeywordSchemaLinker.kt (1)
  • extractKeywords (62-64)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/DatabaseContentSchemaLinker.kt (1)
  • extractKeywords (152-154)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (1)
  • extractKeywords (24-24)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (2)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/executor/BaseAgentExecutor.kt (1)
  • getLLMResponse (49-85)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (1)
  • extractSqlFromResponse (235-245)
⏰ 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: Build
  • GitHub Check: Test mpp-core (KMP)
🔇 Additional comments (17)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.kt (1)

1-19: LGTM!

The expect declaration correctly follows the expect/actual pattern for platform-specific code. The documentation clearly explains the platform behavior differences. Based on learnings, this is the correct approach for Kotlin Multiplatform.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizer.kt (2)

1-17: Well-structured fallback tokenizer implementation.

The implementation correctly combines multiple NLP techniques (Porter Stemmer, BiMM, RAKE) with proper documentation and algorithm references. This provides reasonable cross-platform keyword extraction without heavy dependencies.


211-433: Porter Stemmer implementation is correct.

The implementation follows the standard Porter Stemming Algorithm with proper boundary checks (e.g., line 406's j >= 0 check before array access). The code is well-organized with clear step separation.

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

1-20: LGTM with UTF-8 consideration.

The implementation correctly delegates to FallbackNlpTokenizer. Note that per coding guidelines, UTF-8 characters should be used cautiously in WASM code. The Chinese characters are in FallbackNlpTokenizer (commonMain) as runtime data strings, which should work correctly, but verify Chinese text processing behaves as expected in WASM targets during testing.

Based on learnings about avoiding UTF-8 characters in WASM code, verify Chinese keyword extraction works correctly in the WASM target.

mpp-core/build.gradle.kts (1)

215-217: Dependency placement is correct.

The MyNLP dependency is properly placed in jvmMain since it's a JVM-only library. Version 4.0.0 is the latest stable release on Maven Central.

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

3-21: Android NlpTokenizer actual correctly delegates to fallback

The Android actual cleanly reuses FallbackNlpTokenizer and keeps behavior aligned with other platforms; no issues spotted.

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

3-18: JS NlpTokenizer actual is consistent and minimal

Delegation to FallbackNlpTokenizer keeps JS behavior simple and consistent with other non-JVM targets; implementation looks good.

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

3-21: iOS NlpTokenizer actual is correctly wired

The iOS implementation delegates to the shared fallback tokenizer and documents future enhancement paths; no functional issues detected.

mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/NlpTokenizer.jvm.kt (1)

8-83: JVM NlpTokenizer has robust init and fallback behavior

Lazy lexer initialization, guarded MyNLP usage, and clean fallback to FallbackNlpTokenizer make this implementation resilient; the mixed Chinese/English handling aligns with the JVM tests.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/SchemaLinker.kt (1)

14-66: SchemaLinker base and shared utilities look solid

The abstract contract, shared STOP_WORDS, and Levenshtein/fuzzy-match helpers are well factored for reuse by concrete linkers; no correctness issues identified here.

mpp-core/src/commonTest/kotlin/cc/unitmesh/agent/chatdb/FallbackNlpTokenizerTest.kt (1)

16-375: Comprehensive FallbackNlpTokenizer test suite

This file provides thorough coverage of the tokenizer’s responsibilities (stemming, segmentation, RAKE keywords, identifier formats, edge cases, and performance sanity), which should greatly help prevent regressions; no blocking issues spotted.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/KeywordSchemaLinker.kt (1)

62-64: Fix unresolved references to STOP_WORDS and fuzzyMatch

STOP_WORDS and fuzzyMatch live on SchemaLinker's companion, so the current unqualified calls will not compile from this subclass.

Use the class-qualified names (or import from the companion) to make these references valid:

-    override suspend fun extractKeywords(query: String): List<String> {
-        return NlpTokenizer.extractKeywords(query, STOP_WORDS)
-    }
+    override suspend fun extractKeywords(query: String): List<String> {
+        return NlpTokenizer.extractKeywords(query, SchemaLinker.STOP_WORDS)
+    }
@@
-            // Fuzzy match (Levenshtein distance)
-            else if (fuzzyMatch(tableName, keyword)) {
+            // Fuzzy match (Levenshtein distance)
+            else if (SchemaLinker.fuzzyMatch(tableName, keyword)) {
                 score += 0.3
             }
@@
-            else if (fuzzyMatch(colName, keyword)) score += 0.3
+            else if (SchemaLinker.fuzzyMatch(colName, keyword)) score += 0.3

Alternatively, add imports at the top of the file:

import cc.unitmesh.agent.chatdb.SchemaLinker.Companion.STOP_WORDS
import cc.unitmesh.agent.chatdb.SchemaLinker.Companion.fuzzyMatch

Also applies to: 74-99, 107-117

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/DatabaseContentSchemaLinker.kt (2)

1-27: LGTM!

The class structure and fallback pattern are well-designed. The use of sample data for enriched schema linking aligns with the RSL-SQL research approach mentioned in the documentation.


72-106: LGTM!

The system table filtering and sample data retrieval are well-implemented with appropriate error handling for cases where tables might not exist.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/LlmSchemaLinker.kt (1)

23-27: LGTM!

The constructor design with optional database connection and configurable fallback linker provides good flexibility for different usage scenarios.

mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (2)

157-199: LGTM!

The execution retry loop with SQL revision attempts is well-structured. The retry logic appropriately backs off when revision doesn't produce different SQL, preventing infinite loops.


368-410: LGTM!

The result building and continuation message override are straightforward and complete.

Comment on lines +130 to +132
val validTables = result.tables.filter { tableName ->
userTables.any { it.name.equals(tableName, ignoreCase = true) }
}
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

Normalize table names to schema casing after validation.

The validation uses case-insensitive matching but retains the LLM's original casing in validTables. If the LLM returns "users" but the schema table is "Users", downstream code using case-sensitive comparisons (e.g., buildRelevantSchemaDescription in ChatDBAgentExecutor) will fail to match.

Apply this diff to map to canonical schema names:

-            val validTables = result.tables.filter { tableName ->
-                userTables.any { it.name.equals(tableName, ignoreCase = true) }
-            }
+            val validTables = userTables
+                .filter { schemaTable -> result.tables.any { it.equals(schemaTable.name, ignoreCase = true) } }
+                .map { it.name }
📝 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
val validTables = result.tables.filter { tableName ->
userTables.any { it.name.equals(tableName, ignoreCase = true) }
}
val validTables = userTables
.filter { schemaTable -> result.tables.any { it.equals(schemaTable.name, ignoreCase = true) } }
.map { it.name }
🤖 Prompt for AI Agents
In
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/DatabaseContentSchemaLinker.kt
around lines 130 to 132, the code filters tables case-insensitively but keeps
the LLM-provided casing; change the logic so after filtering you map each
matched tableName to the canonical schema name from userTables (use a
case-insensitive comparison to find the userTables entry and take its .name),
replacing validTables with the list of canonical names; ensure the mapping
preserves only matched names and handles duplicates consistently.

Comment on lines +62 to +68
suspend fun getSampleRows(tableName: String, limit: Int = 3): QueryResult {
return try {
executeQuery("SELECT * FROM `$tableName` LIMIT $limit")
} catch (e: Exception) {
QueryResult(emptyList(), emptyList(), 0)
}
}
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

SQL dialect incompatibility and potential injection risk.

  1. Dialect issue: Backtick quoting (`tableName`) is MySQL-specific. PostgreSQL uses double quotes, and SQL Server uses square brackets. Since DatabaseConfig supports multiple dialects, this will fail on non-MySQL databases.

  2. Security: Table names should be validated against the schema to prevent injection. Consider using getSchema().getTable(tableName) to verify the table exists before querying.

Consider abstracting dialect-specific quoting:

-    suspend fun getSampleRows(tableName: String, limit: Int = 3): QueryResult {
+    suspend fun getSampleRows(tableName: String, limit: Int = 3): QueryResult {
         return try {
-            executeQuery("SELECT * FROM `$tableName` LIMIT $limit")
+            // Validate table exists in schema first
+            val schema = getSchema()
+            if (schema.getTable(tableName) == null) {
+                return QueryResult(emptyList(), emptyList(), 0)
+            }
+            executeQuery("SELECT * FROM $tableName LIMIT $limit")
         } catch (e: Exception) {
             QueryResult(emptyList(), emptyList(), 0)
         }
     }

Note: Full dialect support would require passing dialect info or using a query builder.

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

🤖 Prompt for AI Agents
In
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt
around lines 62-68, the code interpolates the table name using MySQL backticks
which is dialect-specific and risks SQL injection; instead, first validate the
table exists via getSchema().getTable(tableName) (or throw/return an empty
QueryResult if not found), then construct the identifier safely using the
configured dialect quoting helper (e.g., a
DatabaseConfig.quoteIdentifier(tableName) abstraction or a small switch on
DatabaseConfig.dialect to use backticks, double quotes, or square brackets), do
not accept arbitrary characters (validate against a safe identifier pattern if
no quote helper exists), and only then call executeQuery with the properly
quoted identifier; preserve the existing catch and return behavior on error.

Comment on lines +78 to +85
suspend fun getDistinctValues(tableName: String, columnName: String, limit: Int = 10): List<String> {
return try {
val result = executeQuery("SELECT DISTINCT `$columnName` FROM `$tableName` LIMIT $limit")
result.rows.map { it.firstOrNull() ?: "" }.filter { it.isNotEmpty() }
} catch (e: Exception) {
emptyList()
}
}
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

Same dialect and injection concerns apply here.

Both tableName and columnName are user-controllable and directly interpolated into SQL. Additionally, the silent catch block makes debugging difficult when queries fail unexpectedly.

Consider validating both parameters against the schema:

     suspend fun getDistinctValues(tableName: String, columnName: String, limit: Int = 10): List<String> {
         return try {
-            val result = executeQuery("SELECT DISTINCT `$columnName` FROM `$tableName` LIMIT $limit")
+            val table = getSchema().getTable(tableName)
+            if (table == null || table.columns.none { it.name.equals(columnName, ignoreCase = true) }) {
+                return emptyList()
+            }
+            val result = executeQuery("SELECT DISTINCT $columnName FROM $tableName LIMIT $limit")
             result.rows.map { it.firstOrNull() ?: "" }.filter { it.isNotEmpty() }
         } catch (e: Exception) {
+            // Consider logging the exception for debugging
             emptyList()
         }
     }

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

🤖 Prompt for AI Agents
In
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt
around lines 78 to 85, the code interpolates user-controlled tableName and
columnName into SQL (SQL injection risk) and swallows exceptions silently;
validate tableName and columnName against the known schema or a whitelist before
using them (identifiers cannot be parameterized), build the SQL only after
validation, avoid direct string interpolation of untrusted values, and replace
the empty catch with logging of the exception (and optionally rethrow or return
a clear error/result) so failures are visible for debugging.

Comment on lines +63 to +68
// Get current database/catalog name to filter tables
val currentCatalog = connection.catalog

// Get tables only from current database (catalog)
val tableTypes = arrayOf("TABLE", "VIEW")
val tableRs = metadata.getTables(null, null, "%", tableTypes)
val tableRs = metadata.getTables(currentCatalog, null, "%", tableTypes)
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 verify the file exists and read the getSchema method
find . -path "*/mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/ExposedDatabaseConnection.kt" -type f

Repository: phodal/auto-dev

Length of output: 144


🏁 Script executed:

# Read the file to see the current implementation
cat -n mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/ExposedDatabaseConnection.kt

Repository: phodal/auto-dev

Length of output: 8293


🏁 Script executed:

# Search for tests related to ExposedDatabaseConnection
find . -path "*/test/**" -name "*.kt" -type f | xargs grep -l "ExposedDatabaseConnection" 2>/dev/null

Repository: phodal/auto-dev

Length of output: 43


🏁 Script executed:

# Look for database-specific test configurations
find . -path "*/test/**" -name "*.kt" -type f | xargs grep -l "SQLite\|PostgreSQL\|MySQL\|Oracle" 2>/dev/null | head -20

Repository: phodal/auto-dev

Length of output: 177


🏁 Script executed:

# Search more broadly for database-related tests
find . -path "*/test/**" -name "*Database*" -o -path "*/test/**" -name "*Connection*" | grep -v ".git"

Repository: phodal/auto-dev

Length of output: 41


🏁 Script executed:

# Look for any test files in test directories
find . -path "*/test/**" -name "*.kt" -type f | head -20

Repository: phodal/auto-dev

Length of output: 1896


🏁 Script executed:

# Check DatabaseConfig implementation
find . -name "DatabaseConfig.kt" -type f

Repository: phodal/auto-dev

Length of output: 41


🏁 Script executed:

# Search for null handling patterns in the codebase with catalog or connection.catalog
rg -n "connection\.catalog" --type=kt

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Look for JDBC metadata usage patterns in the codebase
rg -n "getTables\|getPrimaryKeys\|getColumns" --type=kt -B2 -A2

Repository: phodal/auto-dev

Length of output: 85


🏁 Script executed:

# Search for connection.catalog with correct rg syntax
rg "connection\.catalog" -A2 -B2

Repository: phodal/auto-dev

Length of output: 673


🏁 Script executed:

# Find DatabaseConfig
find . -name "*atabaseConfig*" -type f

Repository: phodal/auto-dev

Length of output: 41


🏁 Script executed:

# Search for getTables, getPrimaryKeys, getColumns with correct syntax
rg "getTables|getPrimaryKeys|getColumns" -A2 -B2

Repository: phodal/auto-dev

Length of output: 9675


🏁 Script executed:

# Search for any catalog-related initialization or configuration
rg "catalog" -i

Repository: phodal/auto-dev

Length of output: 3302


🏁 Script executed:

# Search for any test or documentation about database connection setup
find . -name "*.md" -o -name "*.txt" | xargs grep -l "database\|catalog\|ExposedDatabaseConnection" 2>/dev/null

Repository: phodal/auto-dev

Length of output: 222


🏁 Script executed:

# Check if there's any DatabaseConfig or database initialization code
find . -name "*.kt" | xargs grep -l "DatabaseConfig" 2>/dev/null | head -10

Repository: phodal/auto-dev

Length of output: 908


🏁 Script executed:

# Look for any tests or setup code that creates database connections
rg "ExposedDatabaseConnection.create|createDatabaseConnection" -A5

Repository: phodal/auto-dev

Length of output: 7654


🏁 Script executed:

# Check if there are integration tests or examples
find . -path "*/example*" -o -path "*/sample*" -o -path "*/demo*" | head -20

Repository: phodal/auto-dev

Length of output: 733


🏁 Script executed:

# Read the test file to see what scenarios are tested
cat -n mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/database/DatabaseConnectionTest.kt

Repository: phodal/auto-dev

Length of output: 7523


🏁 Script executed:

# Read the example file to understand usage patterns
cat -n mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/DatabaseConnectionExample.kt

Repository: phodal/auto-dev

Length of output: 2071


🏁 Script executed:

# Check for any documentation about database support
cat -n mpp-core/README.md | head -100

Repository: phodal/auto-dev

Length of output: 3239


Add null handling for connection.catalog and expand test coverage for different database types.

The code assumes connection.catalog is always non-null, but JDBC drivers can return null for databases like SQLite or when the catalog isn't explicitly configured. When null is passed to getTables(), getPrimaryKeys(), and getColumns(), driver behavior is inconsistent—some drivers interpret null as "all catalogs," which defeats the stated intent to filter by current database.

There are no integration tests verifying catalog-scoped metadata retrieval across the supported database types (MySQL, PostgreSQL, SQLite, Oracle, SQL Server, MariaDB). Add a null check with a fallback strategy:

val currentCatalog = connection.catalog?.takeIf { it.isNotEmpty() }

Alternatively, add database-specific logic using metadata.databaseProductName to handle edge cases. Then add integration tests covering at least MySQL, PostgreSQL, and SQLite to verify the actual filtering behavior.

🤖 Prompt for AI Agents
In
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/ExposedDatabaseConnection.kt
around lines 63 to 68, the code assumes connection.catalog is non-null which can
cause inconsistent behavior across JDBC drivers; add null/empty handling and
fallback logic so we do not pass null to metadata methods. Replace the direct
use of connection.catalog with a safe value (e.g., connection.catalog?.takeIf {
it.isNotEmpty() } or an empty/placeholder string) and/or branch on
metadata.databaseProductName for vendor-specific behavior, and ensure the same
guarded value is used when calling metadata.getTables(), getPrimaryKeys(), and
getColumns(). Finally, add integration tests for MySQL, PostgreSQL, and SQLite
to assert that catalog-scoped metadata retrieval behaves as expected under each
driver.

Introduce an inline configuration pane for adding and editing data sources, replacing the previous dialog-based approach. The pane appears on the right side and supports saving and connecting actions.
Replaces JSqlParserValidator with platform-specific SqlValidator implementations and moves ChatDBAgent classes to commonMain for improved multiplatform support.
Deleted unused DataSourceConfigDialog to clean up codebase.
Implement cross-platform DataSourceRepository for saving, loading, and managing database connection configurations. Refactor ChatDBViewModel to use repository for persistence. Update DataSourceConfigPane for improved UI.
Deleted the SchemaInfoDialog composable as it is no longer used in ChatDBPage.
Render successful query results as assistant messages to support proper markdown table rendering in the timeline.
Render all agent progress updates and final results directly to the chat timeline via the renderer, providing clearer feedback to users during query execution. Removes redundant result rendering from the view model.
Add detailed rendering for schema, linking, SQL generation, validation, execution, and errors in LLM responses for improved clarity and debugging.
Introduce ChatDBStepItem and ChatDBStepCard for displaying database query execution steps with expandable details and status badges in the timeline. Refactor agent executor and renderer to support step-wise progress updates.
Introduce a "New Session" button that clears the current chat timeline. Also include generated SQL in query result details.
Display detailed table schemas and query result previews in ChatDB steps, including expandable table cards, column info, keyword chips, and data preview tables.
Introduce MultiDatabaseChatDBAgent and executor, update UI and models to handle multiple database sources in chatdb agent workflow.
Enhance MultiDatabaseChatDBExecutor with SQL validation, automatic revision on errors, execution retries, and optional PlotDSL visualization generation for query results.
Integrate PlotDSLAgent to generate chart visualizations for SQL query results. Improve table string formatting with row limits and truncation.
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: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

♻️ Duplicate comments (11)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (1)

23-23: llmService parameter is unused.

The llmService parameter is passed to ChatDBPage but never used. The ChatDBViewModel likely creates its own LLM service instance internally. Either remove the parameter or pass it to the ViewModel to ensure consistent configuration.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt (2)

62-68: SQL dialect incompatibility and potential injection risk.

Backtick quoting is MySQL-specific and will fail on PostgreSQL (uses double quotes) or SQL Server (uses square brackets). Additionally, tableName is directly interpolated without validation. Validate against the schema and use dialect-aware quoting.


78-85: Same dialect and injection concerns apply here.

Both tableName and columnName are user-controllable and directly interpolated. The silent catch also makes debugging difficult.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1)

279-283: Consider revising the first suggestion to be more useful.

The suggestion "Show me all tables in the database" may be redundant since schema information is already available via the connection banner. A more data-focused suggestion would be more helpful.

             val suggestions = listOf(
-                "Show me all tables in the database",
+                "Show me the first 10 rows from the ${schema.tables.firstOrNull()?.name ?: "users"} table",
                 "What are the columns in the ${schema.tables.firstOrNull()?.name ?: "users"} table?",
                 "Count the total number of records"
             )
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (3)

35-35: Compose state updates should occur on the main dispatcher.

This was previously flagged. The scope uses Dispatchers.Default, but the ViewModel modifies Compose mutableStateOf properties (state, isGenerating) from coroutines launched on this scope. Compose state should be updated from the main thread.

-    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

507-511: dispose() cancels scope before disconnectAll() completes.

This was previously flagged. disconnectAll() launches a coroutine on scope, but scope.cancel() is called immediately after, potentially preventing the disconnect coroutine from completing and leaving connections open.

     fun dispose() {
         stopGeneration()
-        disconnectAll()
-        scope.cancel()
+        // Close connections synchronously to ensure cleanup
+        connections.forEach { (id, connection) ->
+            try {
+                runBlocking { connection.close() }
+            } catch (e: Exception) {
+                println("[ChatDB] Error closing connection for $id: ${e.message}")
+            }
+        }
+        connections.clear()
+        schemas.clear()
+        scope.cancel()
     }

319-340: Connection not closed when isConnected() returns false.

If createDatabaseConnection succeeds but isConnected() returns false, the connection object is not closed, potentially leaking resources.

             try {
                 val connection = createDatabaseConnection(dataSource.toDatabaseConfig())
                 if (connection.isConnected()) {
                     connections[id] = connection
                     schemas[id] = connection.getSchema()
                     state = state.copy(
                         connectionStatuses = state.connectionStatuses + (id to ConnectionStatus.Connected),
                         connectionStatus = if (state.hasAnyConnection || true) ConnectionStatus.Connected else state.connectionStatus
                     )
                     _notificationEvent.emit("Connected" to "Successfully connected to ${dataSource.name}")
                 } else {
+                    try { connection.close() } catch (_: Exception) {}
                     state = state.copy(
                         connectionStatuses = state.connectionStatuses + (id to ConnectionStatus.Error("Failed to connect"))
                     )
                 }
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (3)

169-183: Avoid hard-coded schema truncation; make it configurable or smarter.

buildRevisionContext truncates schemaDescription with a fixed 2000-character limit:

appendLine(input.schemaDescription.take(2000))

This can cut table/column definitions mid-block, potentially hiding key columns or relationships from the LLM and hurting revision quality.

Suggested directions:

  • Make the limit part of SqlRevisionInput (e.g., maxSchemaLength: Int = 2000) and use that instead of a magic number.
  • Or implement a simple boundary-aware truncation (e.g., split schema into table blocks and include whole blocks until the limit).

Example minimal change:

data class SqlRevisionInput(
    ...
-   val maxAttempts: Int = 3
+   val maxAttempts: Int = 3,
+   val maxSchemaLength: Int = 2000
)

and:

-        appendLine(input.schemaDescription.take(2000))
+        appendLine(input.schemaDescription.take(input.maxSchemaLength))

This keeps prompts bounded while preserving complete table contexts.


20-44: Align schema definition with SqlRevisionInput behavior.

SqlReviseAgentSchema marks schemaDescription as required = true, but:

  • validateInput treats it as optional (schemaDescription = input["schemaDescription"] as? String ?: "").
  • SqlRevisionInput provides a default schemaDescription: String = "".

This mismatch can confuse tool consumers and schema-based tooling.

Consider updating the schema to reflect optionality:

-        "schemaDescription" to string(
-            description = "Database schema description for context",
-            required = true
-        ),
+        "schemaDescription" to string(
+            description = "Database schema description for context",
+            required = false
+        ),

(Optionally also add a previousAttempts property to the schema if you expect it in input.)

Also applies to: 70-81, 265-272


86-101: Remove emoji from progress messages in commonMain for WASM targets.

Progress strings use emoji markers (🔄, 📝, , , ⚠️) in execute, and this file lives in commonMain, so it will be compiled for WASM. Guidelines say to avoid emoji/UTF‑8 in WASM code.

Recommend replacing them with textual prefixes, e.g.:

-        onProgress("🔄 SQL Revise Agent - Starting revision")
+        onProgress("[INFO] SQL Revise Agent - Starting revision")
...
-            onProgress("📝 Revision attempt $attemptCount/${input.maxAttempts}")
+            onProgress("[ATTEMPT] Revision attempt $attemptCount/${input.maxAttempts}")
...
-                onProgress("❌ Failed to generate revised SQL")
+                onProgress("[ERROR] Failed to generate revised SQL")
...
-                    onProgress("✅ SQL validated successfully")
+                    onProgress("[SUCCESS] SQL validated successfully")
...
-                    onProgress("⚠️ Validation failed: ${currentError.take(50)}...")
+                    onProgress("[WARN] Validation failed: ${currentError.take(50)}...")
...
-                onProgress("✅ SQL revised (no validation)")
+                onProgress("[SUCCESS] SQL revised (no validation)")
...
-        onProgress("❌ Max revision attempts reached")
+        onProgress("[ERROR] Max revision attempts reached")

This keeps semantics while complying with platform constraints.

Also applies to: 109-111, 127-128, 139-140, 143-144, 155-158

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (1)

509-526: Make SQL extraction robust by using CodeFence.parseAll (align with SqlReviseAgent).

Current implementation only inspects a single code fence via CodeFence.parse, so if the LLM returns multiple blocks (e.g., explanation + SQL), you may miss the actual SQL and fall through to brittle regexes. SqlReviseAgent.extractSqlFromResponse already uses CodeFence.parseAll and selects the sql fence, which is more reliable.

Consider mirroring that logic here:

-    private fun extractSqlFromResponse(response: String): String? {
-        val codeFence = CodeFence.parse(response)
-        if (codeFence.languageId.lowercase() == "sql" && codeFence.text.isNotBlank()) {
-            return extractFirstStatement(codeFence.text.trim())
-        }
- 
-        // Try to find SQL block manually
-        val sqlPattern = Regex("```sql\\s*([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
-        val match = sqlPattern.find(response)
-        if (match != null) {
-            return extractFirstStatement(match.groupValues[1].trim())
-        }
- 
-        // Last resort: look for SELECT statement
-        val selectPattern = Regex("(SELECT[\\s\\S]*?;)", RegexOption.IGNORE_CASE)
-        val selectMatch = selectPattern.find(response)
-        return selectMatch?.groupValues?.get(1)?.trim()
-    }
+    private fun extractSqlFromResponse(response: String): String? {
+        val codeFences = CodeFence.parseAll(response)
+        val sqlFence = codeFences.find { it.languageId.lowercase() == "sql" && it.text.isNotBlank() }
+        if (sqlFence != null) {
+            return extractFirstStatement(sqlFence.text.trim())
+        }
+
+        // Try to find SQL block manually
+        val sqlPattern = Regex("```sql\\s*([\\s\\S]*?)```", RegexOption.IGNORE_CASE)
+        val match = sqlPattern.find(response)
+        if (match != null) {
+            return extractFirstStatement(match.groupValues[1].trim())
+        }
+
+        // Last resort: look for SELECT statement
+        val selectPattern = Regex("(SELECT[\\s\\S]*?;)", RegexOption.IGNORE_CASE)
+        val selectMatch = selectPattern.find(response)
+        return selectMatch?.groupValues?.get(1)?.trim()
+    }

This keeps the existing regex fallbacks but makes the primary path consistent with the revision agent.

🟡 Minor comments (12)
mpp-ui/src/commonMain/sqldelight/cc/unitmesh/devins/db/DataSource.sq-35-39 (1)

35-39: Potential race condition in clearDefault + setDefault sequence.

When setting a new default, the caller must execute clearDefault followed by setDefault as two separate operations. Without a transaction wrapper, concurrent calls could leave the database in an inconsistent state (e.g., multiple defaults or no default).

Consider documenting that callers must wrap these in a transaction, or providing a combined query:

setDefaultAtomic:
UPDATE DataSource SET isDefault = CASE WHEN id = ? THEN 1 ELSE 0 END WHERE isDefault = 1 OR id = ?;
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/RendererModels.kt-248-257 (1)

248-257: Emoji characters in icon field may cause issues on WASM platform.

Per coding guidelines, emoji and UTF-8 characters should be avoided in WASM code. Consider using icon identifiers (strings or enum references) that map to platform-specific icon resources instead of embedding emoji directly.

-enum class ChatDBStepType(val displayName: String, val icon: String) {
-    FETCH_SCHEMA("Fetch Database Schema", "📊"),
-    SCHEMA_LINKING("Schema Linking", "🔗"),
+enum class ChatDBStepType(val displayName: String, val iconId: String) {
+    FETCH_SCHEMA("Fetch Database Schema", "chart"),
+    SCHEMA_LINKING("Schema Linking", "link"),
     // ... map iconId to actual icons in UI layer

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

mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.ios.kt-71-81 (1)

71-81: Thread-safety concern in singleton initialization.

The getInstance() method is not thread-safe. If called concurrently from multiple threads, it could create multiple DataSourceRepository instances and database connections.

Consider using a thread-safe initialization pattern:

 actual companion object {
-    private var instance: DataSourceRepository? = null
+    @Volatile
+    private var instance: DataSourceRepository? = null
+    private val lock = Any()

     actual fun getInstance(): DataSourceRepository {
-        return instance ?: run {
-            val driverFactory = DatabaseDriverFactory()
-            val database = createDatabase(driverFactory)
-            DataSourceRepository(database).also { instance = it }
+        return instance ?: synchronized(lock) {
+            instance ?: run {
+                val driverFactory = DatabaseDriverFactory()
+                val database = createDatabase(driverFactory)
+                DataSourceRepository(database).also { instance = it }
+            }
         }
     }
 }
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.jvm.kt-49-52 (1)

49-52: setDefault is not atomic — potential race condition.

The clearDefault() and setDefault(id) calls are not wrapped in a transaction. If another thread reads between these calls, it may see no default data source. Consider using a transaction.

     actual fun setDefault(id: String) {
-        queries.clearDefault()
-        queries.setDefault(id)
+        queries.transaction {
+            queries.clearDefault()
+            queries.setDefault(id)
+        }
     }
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.jvm.kt-71-82 (1)

71-82: Double-checked locking without @Volatile may cause visibility issues.

The instance field should be marked @Volatile to ensure proper visibility across threads in the double-checked locking pattern.

     actual companion object {
-        private var instance: DataSourceRepository? = null
+        @Volatile
+        private var instance: DataSourceRepository? = null

         actual fun getInstance(): DataSourceRepository {
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt-186-190 (1)

186-190: Truncation logic may produce incorrect output when colWidths[idx] is less than 4.

If colWidths[idx] is less than 4, str.take(colWidths[idx] - 3) takes negative or zero characters, producing "..." for any non-empty string. Consider adding a minimum width guard.

-                val displayStr = if (str.length > colWidths[idx]) {
-                    str.take(colWidths[idx] - 3) + "..."
+                val colWidth = colWidths[idx]
+                val displayStr = if (str.length > colWidth && colWidth >= 4) {
+                    str.take(colWidth - 3) + "..."
+                } else if (str.length > colWidth) {
+                    str.take(colWidth)
                 } else {
                     str
                 }
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt-62-68 (1)

62-68: Potential stale state access in onToggleDataSource callback.

The lambda captures state which is read at composition time. When the callback executes, state.dataSources and state.isConfigPaneOpen may be stale. Consider accessing state directly from the ViewModel or using a method that encapsulates this logic.

                     onToggleDataSource = { id ->
                         viewModel.toggleDataSource(id)
-                        // When toggling a data source, optionally show its config in the pane
-                        val selected = state.dataSources.find { it.id == id }
-                        if (selected != null && state.isConfigPaneOpen) {
-                            viewModel.openConfigPane(selected)
-                        }
+                        // Move this logic into ViewModel.toggleDataSource() to avoid stale state
                     },

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

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBExecutor.kt-756-777 (1)

756-777: Potential bug in fallback SQL handling.

At line 771, when cleanSql is blank, the code falls back to the original sql which still contains the -- database: comment. This comment could cause issues with some database dialects or parsers.

                 } else if (databaseConnections.size == 1) {
                     // If only one database, use it as default
                     val defaultDb = databaseConnections.keys.first()
-                    blocks.add(SqlBlock(defaultDb, cleanSql.ifBlank { sql }))
+                    blocks.add(SqlBlock(defaultDb, if (cleanSql.isNotBlank()) cleanSql else sql.trim()))
                 }

Or better, if cleanSql is blank, consider skipping the block entirely since it likely means there's no actual SQL:

                 } else if (databaseConnections.size == 1 && cleanSql.isNotBlank()) {
                     val defaultDb = databaseConnections.keys.first()
                     blocks.add(SqlBlock(defaultDb, cleanSql))
                 }
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBExecutor.kt-798-821 (1)

798-821: Column mismatch between databases not handled.

When combining results from multiple databases, the code assumes all results have the same columns (line 812-814 only uses columns from the first result). If queries return different schemas, the combined result will have misaligned data.

Consider validating column consistency or handling mismatches:

 private fun combineResults(results: Map<String, QueryResult>): QueryResult {
     if (results.isEmpty()) {
         return QueryResult(emptyList(), emptyList(), 0)
     }

     if (results.size == 1) {
         return results.values.first()
     }

     // For multiple results, add a "database" column and combine
     val allColumns = mutableListOf("_database")
     val allRows = mutableListOf<List<String>>()
+    var referenceColumns: List<String>? = null

     for ((dbName, result) in results) {
-        if (allColumns.size == 1) {
+        if (referenceColumns == null) {
             allColumns.addAll(result.columns)
+            referenceColumns = result.columns
+        } else if (result.columns != referenceColumns) {
+            // Log warning about column mismatch
+            logger.warn { "Column mismatch for $dbName: expected $referenceColumns, got ${result.columns}" }
         }
         for (row in result.rows) {
-            allRows.add(listOf(dbName) + row)
+            // Pad or truncate row to match expected column count
+            val paddedRow = row.take(referenceColumns?.size ?: row.size)
+                .toMutableList()
+                .apply { while (size < (referenceColumns?.size ?: 0)) add("") }
+            allRows.add(listOf(dbName) + paddedRow)
         }
     }

     return QueryResult(allColumns, allRows, allRows.size)
 }

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

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBStepCard.kt-596-596 (1)

596-596: Avoid emoji characters in commonMain code per coding guidelines.

The coding guidelines state to "Avoid emoji and UTF-8 characters in WASM code." Since this is in commonMain, it will be compiled for WASM targets where emoji rendering may be problematic.

Consider replacing emoji with icon components:

-                    Text(text = "🗄️", fontSize = 12.sp)
+                    Icon(
+                        AutoDevComposeIcons.Database,
+                        contentDescription = null,
+                        modifier = Modifier.size(12.dp),
+                        tint = MaterialTheme.colorScheme.onSurfaceVariant
+                    )

Apply similar changes to lines 690-692 (📋), 762 (🔑), and 765 (🔗).

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

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBAgent.kt-149-155 (1)

149-155: close() should handle exceptions to ensure all connections are closed.

If one connection's close() throws, the loop exits and remaining connections stay open. Wrap each close in a try-catch.

     suspend fun close() {
-        databaseConnections.values.forEach { it.close() }
+        databaseConnections.values.forEach { connection ->
+            try {
+                connection.close()
+            } catch (e: Exception) {
+                logger.error { "Error closing database connection: ${e.message}" }
+            }
+        }
         databaseConnections.clear()
     }

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

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt-142-148 (1)

142-148: Add exception handling in close().

If close() throws, databaseConnection won't be set to null, leaving the agent in an inconsistent state.

     suspend fun close() {
-        databaseConnection?.close()
-        databaseConnection = null
+        try {
+            databaseConnection?.close()
+        } catch (e: Exception) {
+            logger.error { "Error closing database connection: ${e.message}" }
+        } finally {
+            databaseConnection = null
+        }
     }

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

🧹 Nitpick comments (24)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt (1)

240-244: Consider importing ChatDBStepCard instead of using fully qualified name.

The fully qualified path cc.unitmesh.devins.ui.compose.agent.chatdb.components.ChatDBStepCard works but is verbose. Adding an import would improve readability.

+import cc.unitmesh.devins.ui.compose.agent.chatdb.components.ChatDBStepCard
 // ... in the when block:
         is TimelineItem.ChatDBStepItem -> {
-            cc.unitmesh.devins.ui.compose.agent.chatdb.components.ChatDBStepCard(
+            ChatDBStepCard(
                 step = timelineItem
             )
         }
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/RendererModels.kt (1)

220-225: Consider using Map<String, String> instead of Map<String, Any> for details.

Map<String, Any> can cause serialization issues in multiplatform contexts and makes the API contract unclear. If structured data is needed, consider a sealed class or Map<String, String> with JSON-encoded values for complex types.

     data class ChatDBStepItem(
         val stepType: ChatDBStepType,
         val status: ChatDBStepStatus,
         val title: String,
-        val details: Map<String, Any> = emptyMap(),
+        val details: Map<String, String> = emptyMap(),
         val error: String? = null,
mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt (1)

167-175: Strengthen assertion for multiple statements test.

The test only verifies the result is not null, which doesn't validate the actual behavior. Consider asserting on specific expected behavior (e.g., whether both statements are validated, or if only the first is parsed).

     @Test
     fun testMultipleStatements() {
-        // JSqlParser may handle this differently
         val sql = "SELECT * FROM users; SELECT * FROM orders"
-
         val result = validator.validate(sql)
-        // Just verify it doesn't crash - behavior may vary
-        assertNotNull(result)
+        // Document expected behavior: JSqlParser typically validates only the first statement
+        // or may return an error for multiple statements depending on configuration
+        assertNotNull(result)
+        // Consider: assertTrue(result.isValid) or assertFalse depending on expected behavior
     }
mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.js.kt (1)

10-37: Inconsistent warning behavior across methods.

Only getAll() logs a warning via console.warn. For consistency, either log warnings in all stub methods or remove the warning from getAll(). Silent no-ops can be confusing for developers debugging why data isn't persisting.

Consider a consistent approach:

 actual class DataSourceRepository {
+    private fun logNotImplemented(method: String) {
+        console.warn("DataSourceRepository.$method not implemented for JS platform")
+    }
+
     actual fun getAll(): List<DataSourceConfig> {
-        console.warn("DataSourceRepository not implemented for JS platform")
+        logNotImplemented("getAll")
         return emptyList()
     }

     actual fun save(config: DataSourceConfig) {
-        // No-op
+        logNotImplemented("save")
     }
     // ... apply to other methods
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt (1)

4-4: Remove unused import.

DatabaseDialect is imported but not used in this file.

 import cc.unitmesh.devins.ui.compose.agent.chatdb.model.DataSourceConfig
-import cc.unitmesh.devins.ui.compose.agent.chatdb.model.DatabaseDialect
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigPane.kt (1)

51-64: Consider adding port range validation.

The current validation only checks for required fields. Port numbers should be validated to be within the valid range (1-65535).

-    val isValid = name.isNotBlank() && database.isNotBlank() &&
-        (dialect == DatabaseDialect.SQLITE || host.isNotBlank())
+    val portNum = port.toIntOrNull()
+    val isValidPort = portNum != null && portNum in 1..65535
+    val isValid = name.isNotBlank() && database.isNotBlank() &&
+        (dialect == DatabaseDialect.SQLITE || (host.isNotBlank() && isValidPort))
mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.ios.kt (1)

49-83: Regex-based table extraction has known limitations.

The patterns work for simple cases but won't handle:

  • Schema-qualified tables (schema.table)
  • Quoted identifiers ("table name" or `table`)
  • Subqueries in FROM clause
  • Table aliases with AS keyword

This is acceptable given the documented limitation that non-JVM platforms use basic regex-based extraction.

Consider improving the FROM pattern to handle schema-qualified names:

-        val fromPattern = Regex("""FROM\s+(\w+)""", RegexOption.IGNORE_CASE)
+        val fromPattern = Regex("""FROM\s+(?:(\w+)\.)?(\w+)""", RegexOption.IGNORE_CASE)
         fromPattern.findAll(sql).forEach { match ->
-            tables.add(match.groupValues[1])
+            val tableName = match.groupValues[2].ifEmpty { match.groupValues[1] }
+            tables.add(tableName)
         }
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (1)

28-29: ViewModel not keyed on workspace — won't recreate if workspace changes.

If workspace changes after initial composition, the ChatDBViewModel won't be recreated because remember has no key. Consider using remember(workspace) to ensure the ViewModel is recreated when the workspace changes.

-    val viewModel = remember { ChatDBViewModel(workspace) }
+    val viewModel = remember(workspace) { ChatDBViewModel(workspace) }
mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.android.kt (2)

85-123: Duplicate implementation with JVM variant — consider extracting to shared code.

The extractErrorMessage and collectWarnings methods are nearly identical to the JVM implementation in SqlValidator.jvm.kt. Since both platforms use JSqlParser, consider extracting these private helpers into a shared JVM/Android module (e.g., jvmAndroidMain) to reduce duplication and maintenance burden.


109-123: String-based SQL analysis is fragile.

Using sql.contains("SELECT *") and similar checks can produce false positives when these patterns appear in string literals or comments. Consider using JSqlParser's AST visitor pattern for more accurate detection.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt (1)

157-157: Unused variable totalWidth.

totalWidth is calculated but never used. Remove it to avoid confusion.

-        val totalWidth = colWidths.sumOf { it + 3 } + 1 // +3 for " │ " per column, +1 for final │
-
         val sb = StringBuilder()
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (1)

278-307: Add timeout and cancel mechanism for database connection attempts.

When a data source is in the Connecting state, users see a progress indicator but have no way to cancel if the connection hangs. Industry best practices recommend 5–15 second timeouts for mobile and 15–30 seconds for desktop applications. Consider implementing a timeout (e.g., using withTimeoutOrNull in the coroutine) and allowing users to tap the spinner to cancel the connection attempt—this can reuse the existing disconnectDataSource callback or introduce a new onCancelConnect callback.

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

153-169: Warning detection uses naive string matching which may produce false positives.

The collectWarnings function uses statement.toString() and then checks with contains(). This approach has issues:

  1. SELECT * check may match strings like 'SELECT * is disabled' in string literals
  2. The UPDATE/DELETE without WHERE check may trigger on subqueries or CTEs that legitimately don't have WHERE

Consider using JSqlParser's visitor pattern for more accurate detection:

 private fun collectWarnings(statement: Statement): List<String> {
     val warnings = mutableListOf<String>()
-    
-    // Check for common issues that aren't errors but might be problematic
-    val sql = statement.toString()
-    
-    if (sql.contains("SELECT *")) {
-        warnings.add("Consider specifying explicit columns instead of SELECT *")
-    }
-    
-    if (!sql.contains("WHERE", ignoreCase = true) && 
-        (sql.contains("UPDATE", ignoreCase = true) || sql.contains("DELETE", ignoreCase = true))) {
-        warnings.add("UPDATE/DELETE without WHERE clause will affect all rows")
+    
+    when (statement) {
+        is net.sf.jsqlparser.statement.select.Select -> {
+            // Check for SELECT * using PlainSelect
+            val selectBody = statement.selectBody
+            if (selectBody is net.sf.jsqlparser.statement.select.PlainSelect) {
+                if (selectBody.selectItems.any { it.toString() == "*" }) {
+                    warnings.add("Consider specifying explicit columns instead of SELECT *")
+                }
+            }
+        }
+        is net.sf.jsqlparser.statement.update.Update -> {
+            if (statement.where == null) {
+                warnings.add("UPDATE without WHERE clause will affect all rows")
+            }
+        }
+        is net.sf.jsqlparser.statement.delete.Delete -> {
+            if (statement.where == null) {
+                warnings.add("DELETE without WHERE clause will affect all rows")
+            }
+        }
     }
     
     return warnings
 }

124-148: Duplicate code across JVM and Android implementations.

The extractErrorMessage and collectWarnings functions are nearly identical to those in SqlValidator.android.kt (lines 84-122). Consider extracting shared logic to a common module to reduce maintenance burden. You could:

  1. Create a SqlValidatorHelper object in commonMain with shared utilities
  2. Or use a delegation pattern where platform implementations delegate to shared code

Based on the coding guidelines recommending expect/actual patterns, the shared validation logic could be in commonMain.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBExecutor.kt (2)

529-543: Silent error swallowing may mask connection issues.

When fetching schemas, exceptions are logged but the database is silently skipped. This could lead to confusing behavior where a user expects queries against a database that failed silently.

Consider propagating partial failure information to the caller:

 private suspend fun fetchAndMergeSchemas(): MergedDatabaseSchema {
     val schemas = mutableMapOf<String, DatabaseSchema>()
+    val failedDatabases = mutableListOf<String>()

     for ((dbId, connection) in databaseConnections) {
         try {
             val schema = connection.getSchema()
             schemas[dbId] = schema
             logger.info { "Fetched schema for $dbId: ${schema.tables.size} tables" }
         } catch (e: Exception) {
             logger.error(e) { "Failed to fetch schema for $dbId" }
+            failedDatabases.add(dbId)
         }
     }

+    if (failedDatabases.isNotEmpty()) {
+        logger.warn { "Failed to fetch schemas for: ${failedDatabases.joinToString()}" }
+    }
+
     return MergedDatabaseSchema(schemas)
 }

Or render a warning step in the UI to inform the user.


380-425: Retry logic structure is confusing and could be simplified.

The placement of executionRetries++ at line 424 after the revision logic makes the flow hard to follow. The condition executionRetries < maxExecutionRetries - 1 at line 381 requires mental gymnastics to verify correctness.

Consider restructuring for clarity:

-                    // Try to revise SQL based on execution error
-                    if (executionRetries < maxExecutionRetries - 1) {
+                    executionRetries++
+                    
+                    // Try to revise SQL based on execution error (except on last attempt)
+                    if (executionRetries < maxExecutionRetries) {
                         onProgress("🔄 Attempting to fix SQL based on execution error...")
                         // ... revision logic ...
                     }
-                    executionRetries++

This makes it clearer that we increment first, then decide whether to revise based on remaining attempts.

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

49-83: Table name extraction patterns are too simplistic.

The regex patterns don't handle common SQL constructs:

  • Schema-qualified names: SELECT * FROM schema.users won't extract correctly
  • Quoted identifiers: "table-name" or `table name` won't match
  • The patterns may match keywords in subqueries or string literals

Consider improving the patterns:

     actual override fun extractTableNames(sql: String): List<String> {
         val tables = mutableListOf<String>()
         
-        // Match FROM clause
-        val fromPattern = Regex("""FROM\s+(\w+)""", RegexOption.IGNORE_CASE)
+        // Match FROM clause (handles schema.table and quoted identifiers)
+        val fromPattern = Regex("""FROM\s+(?:\[?(\w+)\]?\.)?[\["`]?(\w+)[\]"`]?""", RegexOption.IGNORE_CASE)
         fromPattern.findAll(sql).forEach { match ->
-            tables.add(match.groupValues[1])
+            val tableName = match.groupValues[2].ifEmpty { match.groupValues[1] }
+            if (tableName.isNotEmpty()) tables.add(tableName)
         }

Given the limitations documented in the expect class ("regex-based extraction which may be less accurate"), this is acceptable for a lightweight WASM implementation, but consider adding a note about known limitations.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1)

23-23: Remove unused import.

Platform is imported but not used in this file.

-import cc.unitmesh.agent.Platform
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBStepCard.kt (1)

176-177: Consider type-safe step details to eliminate unchecked casts.

The numerous @Suppress("UNCHECKED_CAST") annotations indicate a design opportunity. Consider creating sealed classes or data classes for step details:

sealed class ChatDBStepDetails {
    data class FetchSchema(
        val databaseName: String? = null,
        val totalTables: Int = 0,
        val databases: List<DatabaseInfo> = emptyList(),
        val tableSchemas: List<TableSchemaInfo> = emptyList()
    ) : ChatDBStepDetails()
    
    data class SchemaLinking(
        val databasesAnalyzed: List<String> = emptyList(),
        val keywords: List<String> = emptyList(),
        val relevantTableSchemas: List<TableSchemaInfo> = emptyList()
    ) : ChatDBStepDetails()
    
    // ... other step types
}

This would make the code more type-safe and self-documenting. However, this would require changes to the renderer API, so it's a larger refactor.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBAgent.kt (2)

1-9: Unused import: DatabaseSchema

The import DatabaseSchema on line 7 is not used in this file.

 import cc.unitmesh.agent.database.DatabaseConfig
 import cc.unitmesh.agent.database.DatabaseConnection
-import cc.unitmesh.agent.database.DatabaseSchema
 import cc.unitmesh.agent.database.createDatabaseConnection

139-141: buildSystemPrompt() is trivial and could be inlined.

The method simply returns MULTI_DB_SYSTEM_PROMPT. Consider removing the method and using the constant directly in execute(), or keeping it if you plan to add dynamic schema information later.

mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt (1)

72-87: Silent failure in save() - consider propagating errors.

The save() method catches exceptions and only logs them. The caller has no way to know if the save succeeded. Consider throwing or returning a result type so the UI can notify users of save failures.

-    actual fun save(config: DataSourceConfig) {
+    actual fun save(config: DataSourceConfig): Boolean {
         try {
             val storage = loadStorage()
             val stored = config.toStoredDataSource()
             val existing = storage.dataSources.indexOfFirst { it.id == config.id }
             val updatedList = if (existing >= 0) {
                 storage.dataSources.toMutableList().apply { set(existing, stored) }
             } else {
                 storage.dataSources + stored
             }
             saveStorage(DataSourceStorage(updatedList))
             console.log("WASM: Data source saved: ${config.id}")
+            return true
         } catch (e: Exception) {
             console.error("WASM: Error saving data source: ${e.message}")
+            return false
         }
     }
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (1)

489-495: getSchema() only returns first schema in multi-datasource mode.

The comment acknowledges this limitation. For multi-datasource support, consider returning a combined schema or deprecating this method in favor of getAllSchemas().

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt (1)

57-66: Clarify port validation message for non-SQLite dialects.

Validation skips host/port checks when dialect == DatabaseDialect.SQLITE, but the error message says “Port must be between 1 and 65535” without that caveat.

To avoid confusion, you could clarify the message:

-            if (port <= 0 || port > 65535) errors.add("Port must be between 1 and 65535")
+            if (port <= 0 || port > 65535) {
+                errors.add("Port must be between 1 and 65535 for non-SQLite databases")
+            }

This better reflects the special-case handling for SQLite.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 038bd93 and 97c1793.

📒 Files selected for processing (30)
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.android.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBAgent.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBExecutor.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt (3 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt (2 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/RendererModels.kt (3 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.kt (1 hunks)
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.ios.kt (1 hunks)
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.js.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.jvm.kt (1 hunks)
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt (1 hunks)
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.wasmJs.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt (5 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBStepCard.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigPane.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt (1 hunks)
  • mpp-ui/src/commonMain/sqldelight/cc/unitmesh/devins/db/DataSource.sq (1 hunks)
  • mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.ios.kt (1 hunks)
  • mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.js.kt (1 hunks)
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.jvm.kt (1 hunks)
  • mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt (1 hunks)
🧰 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-core/src/iosMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.ios.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigPane.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt
  • mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.ios.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBStepCard.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBExecutor.kt
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.jvm.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt
  • mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.js.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/RendererModels.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.wasmJs.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
  • mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.android.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.js.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBAgent.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.jvm.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt
**/*.{kt,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.ios.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigPane.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt
  • mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.ios.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBStepCard.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBExecutor.kt
  • mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.jvm.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt
  • mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.js.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/RendererModels.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.wasmJs.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt
  • mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.android.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.js.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBAgent.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.jvm.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt
🧠 Learnings (6)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/AgentMessageList.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/RendererModels.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.js.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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/subagent/SqlValidator.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update JVM CLI implementations: `CodingCliRenderer` and `ConsoleRenderer`

Applied to files:

  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/RendererModels.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
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-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.ts : When modifying `CodingAgentRenderer`, update ALL TypeScript implementations: `BaseRenderer.ts`, `CliRenderer.ts`, `ServerRenderer.ts`, and `TuiRenderer.ts`

Applied to files:

  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : Avoid emoji and UTF-8 characters in WASM code

Applied to files:

  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
🧬 Code graph analysis (17)
mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.ios.kt (4)
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.wasmJs.kt (4)
  • performBasicValidation (85-138)
  • validate (9-139)
  • validate (11-20)
  • extractTableNames (49-83)
mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.android.kt (3)
  • validate (17-124)
  • validate (19-34)
  • extractTableNames (75-83)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.kt (3)
  • validate (20-51)
  • validate (27-27)
  • extractTableNames (50-50)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.jvm.kt (3)
  • validate (17-184)
  • validate (19-34)
  • extractTableNames (85-93)
mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.ios.kt (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/db/DatabaseDriverFactory.kt (1)
  • createDatabase (44-97)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBExecutor.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/executor/BaseAgentExecutor.kt (1)
  • getLLMResponse (49-85)
mpp-core/src/jvmTest/kotlin/cc/unitmesh/agent/subagent/JSqlParserValidatorTest.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/DevInsTestCase.kt (4)
  • assertTrue (29-33)
  • assertFalse (38-40)
  • assertNotNull (54-59)
  • assertNull (64-68)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.jvm.kt (1)
mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.android.kt (2)
  • collectWarnings (109-123)
  • extractErrorMessage (85-107)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt (1)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/database/ExposedDatabaseConnection.kt (1)
  • executeQuery (19-55)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt (1)
mpp-vscode/src/bridge/mpp-core.ts (1)
  • ToolRegistry (226-306)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/RendererModels.kt (3)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/plan/PlanStep.kt (1)
  • generateId (116-118)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/plan/AgentPlan.kt (1)
  • generateId (135-137)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/plan/PlanTask.kt (1)
  • generateId (151-153)
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.wasmJs.kt (5)
mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.ios.kt (4)
  • performBasicValidation (85-138)
  • validate (9-139)
  • validate (11-20)
  • extractTableNames (49-83)
mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.android.kt (3)
  • validate (17-124)
  • validate (19-34)
  • extractTableNames (75-83)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (3)
  • validate (277-281)
  • validate (278-278)
  • extractTableNames (280-280)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.kt (3)
  • validate (20-51)
  • validate (27-27)
  • extractTableNames (50-50)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.jvm.kt (3)
  • validate (17-184)
  • validate (19-34)
  • extractTableNames (85-93)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (1)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/database/DatabaseConnection.kt (1)
  • createDatabaseConnection (379-379)
mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.android.kt (1)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/subagent/SqlValidator.jvm.kt (2)
  • collectWarnings (153-169)
  • extractErrorMessage (124-148)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (2)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt (1)
  • AgentMessageList (31-158)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBViewModel.kt (1)
  • sendMessage (397-469)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/ChatDBPage.kt (4)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/base/ResizableSplitPane.kt (1)
  • ResizableSplitPane (38-186)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourcePanel.kt (1)
  • DataSourcePanel (26-97)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/DataSourceConfigPane.kt (1)
  • DataSourceConfigPane (27-296)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/components/ChatDBChatPane.kt (1)
  • ChatDBChatPane (35-91)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (2)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/schema/ToolSchema.kt (2)
  • string (136-155)
  • integer (157-170)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/AstFormatter.kt (1)
  • appendLine (206-209)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/MultiDatabaseChatDBAgent.kt (1)
mpp-vscode/src/bridge/mpp-core.ts (1)
  • ToolRegistry (226-306)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.jvm.kt (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/db/DatabaseDriverFactory.kt (1)
  • createDatabase (44-97)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt (2)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/executor/BaseAgentExecutor.kt (1)
  • getLLMResponse (49-85)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt (1)
  • extractSqlFromResponse (235-245)
⏰ 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

Comment on lines +84 to +97
private val executor: ChatDBAgentExecutor by lazy {
val connection = databaseConnection ?: createDatabaseConnection(databaseConfig)
databaseConnection = connection

ChatDBAgentExecutor(
projectPath = projectPath,
llmService = llmService,
toolOrchestrator = toolOrchestrator,
renderer = renderer,
databaseConnection = connection,
maxIterations = maxIterations,
enableLLMStreaming = enableLLMStreaming
)
}
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

Consider adding error handling for connection creation.

Similar to MultiDatabaseChatDBAgent, the lazy initialization of executor creates a database connection without error handling. If createDatabaseConnection fails, the agent becomes unusable.

     private val executor: ChatDBAgentExecutor by lazy {
-        val connection = databaseConnection ?: createDatabaseConnection(databaseConfig)
-        databaseConnection = connection
+        val connection = databaseConnection ?: try {
+            createDatabaseConnection(databaseConfig)
+        } catch (e: Exception) {
+            logger.error { "Failed to create database connection: ${e.message}" }
+            throw IllegalStateException("Cannot create database connection", e)
+        }
+        databaseConnection = connection
         
         ChatDBAgentExecutor(
🤖 Prompt for AI Agents
In mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgent.kt around
lines 84 to 97, the lazy-initialized executor calls createDatabaseConnection
without handling failures; wrap the creation in a try/catch, log or surface the
error with context, and fail fast (rethrow or throw a descriptive custom
exception) so the agent does not silently become unusable; ensure
databaseConnection is not set when creation fails and preserve the original
exception as cause for easier debugging.

Comment on lines +80 to +81
onProgress("📊 Fetching database schema...")

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

Remove emoji from commonMain strings for WASM compatibility.

This commonMain file is compiled for WASM, but multiple user-facing strings use emoji characters (progress messages and headings like “✅/❌/📊/📈”). Project guidelines say to avoid emoji/UTF‑8 in WASM code; these should be replaced with plain-text prefixes (e.g., [INFO], [SUCCESS], [ERROR]) or similar.

Recommend systematically replacing emoji in:

  • onProgress messages (schema fetching, schema linking, SQL generation, validation/revision, execution, visualization).
  • buildResult headings (“## ✅ Query Executed Successfully”, “## ❌ Query Failed”).

This keeps behavior while complying with platform constraints.

Also applies to: 103-104, 138-139, 200-201, 235-236, 271-272, 288-289, 306-307, 339-340, 369-370, 601-602, 637-642

🤖 Prompt for AI Agents
In
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/chatdb/ChatDBAgentExecutor.kt
around lines 80-81 and also at 103-104, 138-139, 200-201, 235-236, 271-272,
288-289, 306-307, 339-340, 369-370, 601-602 and 637-642, remove emoji characters
from all user-facing strings and replace them with plain-text prefixes for WASM
compatibility (e.g., replace "📊", "📈", "✅", "❌" in onProgress calls and
buildResult headings with "[INFO]", "[PROGRESS]", "[SUCCESS]", "[ERROR]" or
similar); update each onProgress message and the buildResult headings ("## ✅
Query Executed Successfully", "## ❌ Query Failed") to use the chosen plain-text
tokens while preserving the original message text and semantics.

Comment on lines +84 to +102
private val executor: MultiDatabaseChatDBExecutor by lazy {
// Create connections for all configured databases
databaseConfigs.forEach { (id, config) ->
if (!databaseConnections.containsKey(id)) {
databaseConnections[id] = createDatabaseConnection(config)
}
}

MultiDatabaseChatDBExecutor(
projectPath = projectPath,
llmService = llmService,
toolOrchestrator = toolOrchestrator,
renderer = renderer,
databaseConnections = databaseConnections,
databaseConfigs = databaseConfigs,
maxIterations = maxIterations,
enableLLMStreaming = enableLLMStreaming
)
}
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

Lazy initialization creates connections without error handling.

If createDatabaseConnection throws for any config, the lazy initialization fails partway, leaving databaseConnections in an inconsistent state. Consider adding error handling or connecting in parallel with proper exception isolation.

     private val executor: MultiDatabaseChatDBExecutor by lazy {
         // Create connections for all configured databases
         databaseConfigs.forEach { (id, config) ->
             if (!databaseConnections.containsKey(id)) {
-                databaseConnections[id] = createDatabaseConnection(config)
+                try {
+                    databaseConnections[id] = createDatabaseConnection(config)
+                } catch (e: Exception) {
+                    logger.error { "Failed to connect to database $id: ${e.message}" }
+                    // Continue with other databases
+                }
             }
         }
+        
+        if (databaseConnections.isEmpty()) {
+            throw IllegalStateException("Failed to connect to any configured database")
+        }
         
         MultiDatabaseChatDBExecutor(

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

Comment on lines +78 to +97
/**
* Render a ChatDB execution step.
* This is an optional method primarily used by UI renderers (ComposeRenderer, JewelRenderer).
* Console renderers can ignore this or provide simple text output.
*
* @param stepType The type of step being executed
* @param status The current status of the step
* @param title The display title for the step (defaults to stepType.displayName)
* @param details Additional details about the step (e.g., table names, row counts, SQL)
* @param error Error message if the step failed
*/
fun renderChatDBStep(
stepType: ChatDBStepType,
status: ChatDBStepStatus,
title: String = stepType.displayName,
details: Map<String, Any> = emptyMap(),
error: String? = null
) {
// Default: no-op for renderers that don't support ChatDB steps
}
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

Missing imports for ChatDBStepType and ChatDBStepStatus.

The method references ChatDBStepType and ChatDBStepStatus but these types are not imported. This will cause compilation errors.

Add the missing imports at the top of the file:

 package cc.unitmesh.agent.render
 
 import cc.unitmesh.agent.plan.PlanSummaryData
 import cc.unitmesh.agent.tool.ToolResult
 import cc.unitmesh.llm.compression.TokenInfo
+import cc.unitmesh.agent.render.ChatDBStepType
+import cc.unitmesh.agent.render.ChatDBStepStatus

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

🤖 Prompt for AI Agents
In
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt
around lines 78 to 97, the renderChatDBStep method references ChatDBStepType and
ChatDBStepStatus but they are not imported; add the correct import statements
for ChatDBStepType and ChatDBStepStatus at the top of the file (import their
package where those types are defined) so the file compiles, then re-run the
build to verify no unused import warnings remain.

Comment on lines +62 to +65
class SqlReviseAgent(
private val llmService: KoogLLMService,
private val sqlValidator: SqlValidatorInterface? = null
) : SubAgent<SqlRevisionInput, ToolResult.AgentResult>(
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:

# Find SqlReviseAgent instantiations to check if validator is always provided
rg -n "SqlReviseAgent\(" mpp-core -S -C2

Repository: phodal/auto-dev

Length of output: 1976


🏁 Script executed:

# Examine the SqlReviseAgent implementation to verify the validation logic
cat -n mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt | head -160

Repository: phodal/auto-dev

Length of output: 7765


Make the SQL validator mandatory to prevent unvalidated query execution.

The SqlReviseAgent accepts a nullable sqlValidator parameter with a default value of null. When the validator is null (lines 141-151), the agent returns revised SQL immediately without any validation, which violates the security goal of using AST-based validation to block destructive queries like DROP, DELETE, or TRUNCATE.

While current instantiation sites in ChatDBAgentExecutor and MultiDatabaseChatDBExecutor do provide a SqlValidator, the API design itself permits the dangerous null path and should be fixed.

Recommended fix: Remove the nullable default and make the validator mandatory:

class SqlReviseAgent(
    private val llmService: KoogLLMService,
    private val sqlValidator: SqlValidatorInterface
) : SubAgent<SqlRevisionInput, ToolResult.AgentResult>(

This ensures validation always occurs and eliminates the unintended code path at lines 141-151 that returns unvalidated SQL.

🤖 Prompt for AI Agents
In mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/subagent/SqlReviseAgent.kt
around lines 62-65 (and eliminate the null-path at ~141-151), the sqlValidator
parameter is nullable with a default of null allowing skipped validation; change
the constructor to require a non-null SqlValidatorInterface (remove the default
and nullable type) so callers must provide a validator, then remove or simplify
the conditional branch that returns revised SQL when validator is null (update
call sites if necessary) to ensure AST-based validation always runs.

Comment on lines +29 to +43
@Serializable
data class DataSourceConfig(
val id: String,
val name: String,
val dialect: DatabaseDialect = DatabaseDialect.MYSQL,
val host: String = "localhost",
val port: Int = 3306,
val database: String = "",
val username: String = "",
val password: String = "",
val description: String = "",
val isDefault: Boolean = false,
val createdAt: Long = 0L,
val updatedAt: Long = 0L
) {
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

Avoid serializing database passwords in plain text.

DataSourceConfig is @Serializable and includes password: String, which means credentials will be persisted and possibly transmitted in clear text. This is a significant security risk (config files, sync, logs, backups, memory dumps).

Consider one of the following:

  • Store only a reference/key to a secure credential store (OS keychain, encrypted storage) instead of the raw password.
  • Exclude password from the serialized form and handle it via a separate, secured channel/structure.
  • At minimum, clearly document the risk and ensure configs are stored in a protected location with strict permissions, and avoid logging any DataSourceConfig instances.

If short-term constraints force plain-text storage, treating this as technical debt with a clear roadmap to a secure store would be prudent.

Also applies to: 77-86

🤖 Prompt for AI Agents
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/chatdb/model/DataSourceModels.kt
lines 29-43 (and similarly lines 77-86): the DataSourceConfig currently
serializes a plain-text password which is a security risk; change the model to
avoid persisting/transmitting raw credentials by (1) removing password from the
serialized form — mark it non-serializable (use kotlinx.serialization @Transient
or equivalent) or remove the property from the @Serializable data class, (2) add
a serialized credential reference field (e.g., credentialKey: String?) that
points to a secure store, and (3) handle the actual password only in-memory via
a separate secure mechanism (set transient password at runtime from the store);
also add a short TODO comment documenting migration to a secure credential store
and avoid logging or printing DataSourceConfig instances.

Comment on lines +8 to +9
username TEXT NOT NULL,
password TEXT NOT 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 | 🟠 Major

Security: Password stored in plaintext.

Storing database passwords as plain text is a security risk. Consider encrypting the password before storage or using platform-specific secure storage mechanisms (e.g., Keychain on iOS, EncryptedSharedPreferences on Android, system credential managers on desktop).

At minimum, the password should be encrypted with a key derived from platform-specific secure storage. For a multiplatform solution, consider:

  1. Adding an expect/actual encryption utility
  2. Using SQLCipher for encrypted database storage
  3. Storing only a reference/alias to credentials in secure platform storage

Comment on lines +24 to +39
actual fun save(config: DataSourceConfig) {
queries.insertOrReplace(
id = config.id,
name = config.name,
dialect = config.dialect.name,
host = config.host,
port = config.port.toLong(),
databaseName = config.database,
username = config.username,
password = config.password,
description = config.description,
isDefault = if (config.isDefault) 1L else 0L,
createdAt = config.createdAt,
updatedAt = config.updatedAt
)
}
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

Security concern: Password stored in plain text.

Database credentials are stored directly in SQLite without encryption. For enterprise security requirements mentioned in the PR objectives, consider using iOS Keychain for sensitive credential storage.

This is acceptable for an initial implementation, but for production use, you should:

  1. Store only a reference/key in SQLite
  2. Store the actual password in iOS Keychain using Security.framework
  3. Retrieve credentials from Keychain when building connection strings

Would you like me to open an issue to track implementing secure credential storage using iOS Keychain?

🤖 Prompt for AI Agents
mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.ios.kt
around lines 24-39: the current save(...) persists the plaintext password into
SQLite; replace this by storing only a reference key in the DB and moving the
actual secret into the iOS Keychain via Security.framework: generate a stable
unique key (e.g. "datasource-{config.id}-password"), save the password to
Keychain using SecItemAdd/SecItemUpdate with appropriate accessibility and
attributes, persist that key string (not the password) in the database, on reads
retrieve the password from Keychain using SecItemCopyMatching when constructing
connection details, ensure no logging of secrets, handle Keychain errors and
migration for existing records (on save, if a plaintext password exists, write
it to Keychain and overwrite DB field with the reference, and on delete remove
the Keychain item).

Comment on lines +32 to +33
username = config.username,
password = config.password,
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

Password stored in plain text.

Database credentials including passwords are stored without encryption. This is a security concern, especially if the SQLite database file is accessible. Consider encrypting sensitive fields before storage or using a platform-specific secure storage mechanism.

Comment on lines +21 to +35
@Serializable
private data class StoredDataSource(
val id: String,
val name: String,
val dialect: String,
val host: String,
val port: Int,
val database: String,
val username: String,
val password: String,
val description: String,
val isDefault: Boolean,
val createdAt: Long,
val updatedAt: Long
)
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

Security: Passwords stored in plain text in browser localStorage.

StoredDataSource stores password as plain text, which gets serialized to localStorage. This is a security risk as localStorage is accessible to any JavaScript on the same origin and persists across sessions.

Consider:

  1. Not storing passwords at all (prompt on connect)
  2. Using session storage instead
  3. Encrypting sensitive fields before storage
🤖 Prompt for AI Agents
In mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/db/DataSourceRepository.kt
around lines 21 to 35, the StoredDataSource data class currently includes
password which gets serialized to localStorage — remove sensitive plaintext
storage: delete the password field from StoredDataSource and any serialization
that persists it, and instead (a) keep passwords out of persistent localStorage
by prompting the user on connect or storing ephemeral credentials only in
sessionStorage, or (b) if persistence is required, encrypt the password
client-side using Web Crypto (SubtleCrypto) with a user-derived key (e.g.,
PBKDF2) before serializing and decrypt on demand; update all code paths that
read/write StoredDataSource to use the new approach and migrate or purge any
existing plain-text entries in localStorage.

Change toTableString to generate Markdown table format for query results, replacing the previous ASCII table style. Adds escaping for special Markdown characters and updates tests accordingly.
Refactored MultiDatabaseChatDBAgent and related renderer logic to improve database handling and UI integration. Updated SQL validation and connection modules for better cross-platform support.
Refactored renderer and database connection modules for improved structure and maintainability. Updated related UI components and server-side renderer.
Render a success step when revised SQL passes dry run validation and update approval to use the latest dry run result.
@phodal phodal merged commit 779e03a into master Dec 10, 2025
2 checks passed
@phodal phodal deleted the feature/chatdb-agent branch December 10, 2025 12:46
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.

[feature] Text2SQL Agent

2 participants