-
Notifications
You must be signed in to change notification settings - Fork 1
feat(vscode): Enhanced Chat Input with DevIn Language Support #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Create project directory structure - Add package.json with VSCode extension configuration - Add tsconfig.json for TypeScript - Add README.md with TODO list and architecture overview Closes #31
Phase 2 - Core Services: - Add mpp-core bridge layer with LLMService, CodingAgent, ToolRegistry, CompletionManager, and DevInsCompiler wrappers - Implement extension.ts entry point with activation/deactivation Phase 3 - IDE Integration: - Implement IDE Server with Express HTTP endpoints - Add DiffManager for file diff operations - Support MCP protocol communication Phase 4 - Chat Interface: - Implement ChatViewProvider with webview - Add streaming LLM response support - Create embedded HTML chat UI Phase 5 - Commands: - Register autodev.chat, autodev.acceptDiff, autodev.cancelDiff, autodev.runAgent - Add keyboard shortcut (Cmd+Shift+A) Testing: - Add vitest configuration - Add unit tests for mpp-core bridge - Add unit tests for DiffManager - Add unit tests for IDEServer Refs #31
Phase 5 - Status Bar: - Add StatusBarManager with idle/thinking/streaming/error states - Animated status icons during LLM operations - Click to open chat command Phase 6 - DevIns Language Support: - Add TextMate grammar for syntax highlighting - Support commands (/), agents (@), variables ($) - Add language configuration (brackets, folding, etc.) - Implement DevInsCompletionProvider with built-in completions - Register completion triggers for /, @, $ characters Testing: - Add unit tests for StatusBarManager - Add unit tests for DevInsCompletionProvider - All 56 tests passing Refs #31
Phase 6 - React Webview UI: - Create webview React project with Vite build - Add MessageList component with Markdown rendering - Add ChatInput component with auto-resize textarea - Add useVSCode hook for extension communication - Integrate VSCode theme variables for consistent styling - Support streaming response with animated indicators - Add fallback inline HTML when React bundle not available - Update ChatViewProvider to load React bundle with CSP Components: - App.tsx - Main chat application - MessageList.tsx - Message display with Markdown support - ChatInput.tsx - Input with keyboard shortcuts - useVSCode.ts - VSCode API communication hook Testing: - Add ChatViewProvider tests - All 63 tests passing Refs #31
Extract MarkdownTextParser to a shared mpp-core module for reuse and improved testability across platforms. Update imports accordingly.
Phase 7 - Sketch Renderer System: - Add Timeline component mirroring mpp-ui's ComposeRenderer - Add SketchRenderer with specialized sub-renderers: - CodeBlockRenderer: syntax highlighting with Copy/Insert/Apply actions - DiffRenderer: diff display with Accept/Reject/View actions - ThinkingRenderer: collapsible thinking blocks - TerminalRenderer: terminal command and output display - MarkdownRenderer: GFM markdown with react-markdown - ToolCallRenderer: tool call information display - Add codeFence parser mirroring mpp-core's CodeFence.parseAll() - Add timeline types (TimelineItem, AgentState, ToolCallInfo, etc.) ChatViewProvider Refactoring: - Replace LLMService with CodingAgent for agent-based interactions - Add VSCodeRenderer implementing JsCodingAgentRenderer interface - Forward agent events (toolCall, toolResult, terminalOutput) to webview - Add action handlers (insert, apply, run-command, accept-diff, etc.) - Remove API key requirement - use helpful guidance for non-DevIns input All 63 tests passing. Refs #31
Phase 8 - Configuration Management Integration Changes: - Add ConfigManager service to load config from ~/.autodev/config.yaml - Refactor ChatViewProvider to use ConfigManager instead of VSCode settings - Mirror IdeaAgentViewModel's loadConfiguration() pattern - Add lazy initialization of CodingAgent with proper config validation - Create renderer that forwards events to webview (like TuiRenderer) - Add 'Open Config' button in header and config prompt - Support all LLM providers: openai, anthropic, deepseek, ollama, etc. - Auto-create config template when file doesn't exist Architecture: - ConfigManager.load() → AutoDevConfigWrapper → getActiveConfig() - isValid() check before agent initialization - JsKoogLLMService + JsCodingAgent from mpp-core - Renderer implements all JsCodingAgentRenderer methods All 63 tests passing. Refs #31
- Add ModelSelector component similar to IdeaModelSelector.kt - Update ChatInput with toolbar: ModelSelector on left, Stop/Send on right - Add config state management in App.tsx - Add sendConfigUpdate, stopExecution, selectConfig in chat-view.ts - Add new message types: configUpdate, stopExecution, selectConfig All 63 tests passing. Refs #31
- Acquire VSCode API in HTML before React loads to avoid duplicate acquisition error - Store API on window.vscodeApi for React to use - Remove debug logging from production code All 63 tests passing. Refs #31
User message was being added twice - once in handleSend for immediate feedback and again when receiving userMessage from extension. Refs #31
- Fix repository URL from phodal/auto-dev to phodal/auto-dev-sketch - Remove unused colors variable in status-bar.ts - Add platform detection for welcome message keybinding (Cmd vs Ctrl)
- Use mpp-core's JsCompletionManager for DevIn command completions - Add getCompletions and applyCompletion message handlers - Update CompletionPopup to support mpp-core CompletionItem format - Fix popup positioning by removing overflow:hidden from container - Add completion state management in App.tsx
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughThis PR implements DevIn language input support, file context management, and code completion features for the VSCode extension. It adds new UI components (DevInInput with syntax highlighting, CompletionPopup, TopToolbar, FileSearchPopup, FileChip), extends the ChatViewProvider with completion, file search, and MCP configuration handlers, and updates the message protocol between extension and webview to support these features. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Webview as Webview<br/>(ChatInput)
participant Extension as Extension<br/>(ChatViewProvider)
participant CompMgr as Completion<br/>Manager
User->>Webview: Types "/" character
activate Webview
Webview->>Webview: Detect trigger, get position
Webview->>Extension: postMessage({ type: 'getCompletions', text, cursorPosition })
deactivate Webview
activate Extension
Extension->>CompMgr: getCompletions(text, position)
activate CompMgr
CompMgr-->>Extension: [CompletionItem, ...]
deactivate CompMgr
Extension->>Webview: postMessage({ type: 'completionsResult', items })
deactivate Extension
activate Webview
Webview->>Webview: Update completionItems state
Webview->>Webview: Render CompletionPopup
Webview->>Webview: Show popup with items
deactivate Webview
User->>Webview: Navigate with ArrowUp/Down + Enter
activate Webview
Webview->>Extension: postMessage({ type: 'applyCompletion', text, cursorPosition, completionIndex })
deactivate Webview
activate Extension
Extension->>CompMgr: applyCompletion(text, index)
activate CompMgr
CompMgr-->>Extension: { newText, newCursorPosition }
deactivate CompMgr
Extension->>Webview: postMessage({ type: 'completionApplied', newText, newCursorPosition })
deactivate Extension
activate Webview
Webview->>Webview: Update input with newText
Webview->>Webview: Set cursor position
Webview->>Webview: Close CompletionPopup
deactivate Webview
sequenceDiagram
participant User
participant Webview as Webview<br/>(TopToolbar)
participant Extension as Extension<br/>(ChatViewProvider)
User->>Webview: Click "Add context" button
activate Webview
Webview->>Webview: Open FileSearchPopup
Webview->>Extension: postMessage({ type: 'getRecentFiles' })
deactivate Webview
activate Extension
Extension->>Extension: Get recent files list
Extension->>Webview: postMessage({ type: 'recentFilesResult', files })
deactivate Extension
activate Webview
Webview->>Webview: Display recent files in popup
User->>Webview: Type search query (e.g., "component")
deactivate Webview
activate Webview
Webview->>Webview: Debounce 150ms
Webview->>Extension: postMessage({ type: 'searchFiles', query: 'component' })
deactivate Webview
activate Extension
Extension->>Extension: Search workspace, filter binaries
Extension->>Webview: postMessage({ type: 'searchFilesResult', files, folders })
deactivate Extension
activate Webview
Webview->>Webview: Update popup results
User->>Webview: Select file (click or Enter)
Webview->>Webview: Add FileChip to TopToolbar
Webview->>Webview: Close FileSearchPopup
deactivate Webview
sequenceDiagram
participant User
participant Webview as Webview<br/>(ChatInput)
participant Extension as Extension<br/>(ChatViewProvider)
participant LLM as LLM Service
User->>Webview: Click "Enhance" button with prompt
activate Webview
Webview->>Webview: Set isEnhancing = true
Webview->>Extension: postMessage({ type: 'promptOptimize', prompt })
deactivate Webview
activate Extension
Extension->>Extension: Create/get LLM service
Extension->>LLM: Optimize prompt via AI
activate LLM
LLM-->>Extension: optimized_prompt
deactivate LLM
Extension->>Webview: postMessage({ type: 'promptOptimized', result })
deactivate Extension
activate Webview
Webview->>Webview: Update input with optimized text
Webview->>Webview: Set isEnhancing = false
deactivate Webview
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes
Areas requiring extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements Issue #33, adding an enhanced chat input interface for the VSCode extension with DevIn language support. The implementation introduces a React-based webview UI with syntax highlighting, auto-completion, file context management, and integration with mpp-core's completion system. The PR also refactors the MarkdownTextParser from mpp-idea to mpp-core for better code reuse across platforms.
Key Changes:
- React webview UI with DevIn syntax highlighting and auto-completion
- File context management with search and selection capabilities
- Integration with mpp-core's JsCompletionManager for intelligent completions
- MCP server configuration support and prompt optimization features
Reviewed changes
Copilot reviewed 71 out of 72 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| mpp-vscode/webview/* | New React-based webview UI with TypeScript components for chat interface, syntax highlighting, and file management |
| mpp-vscode/src/extension.ts | Extension entry point with service initialization and command registration |
| mpp-vscode/src/bridge/mpp-core.ts | TypeScript bridge to Kotlin/JS compiled mpp-core module |
| mpp-vscode/src/services/* | Service implementations for IDE server, diff management, and status bar |
| mpp-vscode/src/providers/* | Chat view provider and DevIns completion provider |
| mpp-vscode/syntaxes/* | TextMate grammar for DevIns language syntax highlighting |
| mpp-vscode/test/* | Unit tests for services, providers, and mpp-core bridge |
| mpp-core/src/commonMain/kotlin/cc/unitmesh/markdown/MarkdownTextParser.kt | Refactored from mpp-idea to mpp-core with proper package namespace |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| JsToolRegistry, | ||
| JsCompressionConfig |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable JsCompressionConfig.
| JsToolRegistry, | |
| JsCompressionConfig | |
| JsToolRegistry |
| return; | ||
| } | ||
|
|
||
| const basePath = workspaceFolders[0].uri.fsPath; |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable basePath.
| const basePath = workspaceFolders[0].uri.fsPath; |
| const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'assets', 'index.css')); | ||
|
|
||
| // Use nonce for security | ||
| const nonce = this.getNonce(); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable nonce.
| const nonce = this.getNonce(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review completed. 2 suggestions posted.
Comment augment review to trigger a new review at any time.
| User's original prompt:`; | ||
|
|
||
| const response = await this.llmService.chat([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.llmService.chat([...]) is likely not a function on KotlinCC.llm.JsKoogLLMService; the exposed API provides methods like sendPrompt/streamPrompt, so this will throw at runtime when optimizing prompts. Consider using the available LLM service method (or the TS bridge wrapper) here.
🤖 Was this useful? React with 👍 or 👎
| }); | ||
|
|
||
| // Write file endpoint | ||
| app.post('/file/write', async (req: Request, res: Response) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The /file/write endpoint writes to any absolute path without validating it’s within the current workspace, enabling arbitrary local file writes if the token is exposed (also applies to /file/read). Consider enforcing a workspace-root restriction using safe path resolution to prevent traversal/prefix-bypass cases.
🤖 Was this useful? React with 👍 or 👎
There was a problem hiding this 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
♻️ Duplicate comments (2)
mpp-vscode/src/providers/chat-view.ts (2)
648-719: Remove unused basePath variable; optional: share binary-extension listInside
handleSearchFiles:const workspaceFolders = vscode.workspace.workspaceFolders; … const basePath = workspaceFolders[0].uri.fsPath;
basePathis never used, so it should be removed to keep the function clean.You might also consider centralizing the binary‑extension list (here vs.
sendActiveFileUpdate) into a shared helper to avoid them drifting apart over time, but that’s optional.- const workspaceFolders = vscode.workspace.workspaceFolders; - if (!workspaceFolders) { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders) { this.postMessage({ type: 'searchFilesResult', data: { files: [], folders: [] } }); return; } - - const basePath = workspaceFolders[0].uri.fsPath;
609-642: llmService.chat is likely not a valid API on JsKoogLLMService (runtime bug)This block assumes
this.llmServiceexposes achat(messages)method and returns a plain string:const response = await this.llmService.chat([ { role: 'system', content: systemPrompt }, { role: 'user', content: prompt } ]); … this.postMessage({ type: 'promptOptimized', data: { optimizedPrompt: response.trim() } });Per prior feedback and the Kotlin/JS export,
KotlinCC.llm.JsKoogLLMServicetypically exposes methods likesendPrompt/streamPrompt, notchat, so this is very likely to throw at runtime whenoptimizePromptis used. The response shape (string vs. object withmessage/content) may also differ.Align this call with however you already use
JsKoogLLMServiceelsewhere in the project (e.g., the AgentMode.ts analogue), and adapt the extraction of the optimized text accordingly.What methods does `JsKoogLLMService` from `@autodev/mpp-core` (namespace `cc.unitmesh.llm`) expose in its JS API, and does it provide a `chat(messages)` method returning a string, or only methods like `sendPrompt` / `streamPrompt`?
🧹 Nitpick comments (12)
mpp-vscode/webview/src/components/TopToolbar.css (2)
126-127: Remove redundantflex-wrapstyle.Line 126 sets
flex-wrap: nowrapwhich is already the default behavior for flex containers and therefore unnecessary.-/* Collapsed state - horizontal scroll */ -.top-toolbar.collapsed .files-row { - flex-wrap: nowrap; -}
30-30: Inconsistent CSS variable fallbacks; add fallbacks consistently across all color variables.Some color variables have VSCode-specific fallbacks (lines 111, 161, 175) while others rely solely on the CSS variable (lines 30, 68, 98). For robustness and consistency, provide fallbacks for all color variables.
Apply fallbacks to ensure all color definitions have a sensible default:
.add-file-button { display: flex; align-items: center; gap: 4px; padding: 4px 8px; background: transparent; border: 1px dashed var(--panel-border); border-radius: 4px; - color: var(--foreground); + color: var(--foreground, #e0e0e0); font-size: 12px; cursor: pointer; transition: background 0.15s, border-color 0.15s; flex-shrink: 0; }.more-files { font-size: 11px; - color: var(--foreground); + color: var(--foreground, #e0e0e0); opacity: 0.6; cursor: pointer; white-space: nowrap; padding: 2px 6px; border-radius: 4px; transition: background 0.15s; }.expand-button, .clear-button { display: flex; align-items: center; justify-content: center; padding: 4px; background: transparent; border: none; border-radius: 4px; - color: var(--foreground); + color: var(--foreground, #e0e0e0); opacity: 0.6; cursor: pointer; transition: opacity 0.15s, background 0.15s; }.context-indicator { display: flex; align-items: center; justify-content: center; position: relative; padding: 4px; background: transparent; border: none; border-radius: 4px; - color: var(--foreground); + color: var(--foreground, #e0e0e0); opacity: 0.5; cursor: pointer; transition: opacity 0.15s, background 0.15s; }Also applies to: 68-68, 98-98, 111-111, 161-161, 175-175
mpp-vscode/webview/src/hooks/useVSCode.ts (1)
60-60: Consider formatting the long union type for readability.The WebviewMessage type union now contains 13 different message types in a single line, which reduces readability.
Apply this diff to improve readability:
- type: 'sendMessage' | 'clearHistory' | 'action' | 'openConfig' | 'stopExecution' | 'selectConfig' | 'searchFiles' | 'getRecentFiles' | 'readFileContent' | 'requestConfig' | 'getActiveFile' | 'getCompletions' | 'applyCompletion'; + type: + | 'sendMessage' + | 'clearHistory' + | 'action' + | 'openConfig' + | 'stopExecution' + | 'selectConfig' + | 'searchFiles' + | 'getRecentFiles' + | 'readFileContent' + | 'requestConfig' + | 'getActiveFile' + | 'getCompletions' + | 'applyCompletion';mpp-vscode/webview/src/components/FileChip.tsx (1)
27-37: Consider adding ARIA labels to decorative SVG icons.For better accessibility, decorative icons should have
aria-hidden="true"or role="img" with aria-label.Apply this diff to improve accessibility:
<span className="file-chip-icon"> {file.isDirectory ? ( - <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"> + <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"> <path d="M1.5 2A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5V5a1.5 1.5 0 0 0-1.5-1.5H7.707l-.853-.854A.5.5 0 0 0 6.5 2.5H1.5z"/> </svg> ) : ( - <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"> + <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"> <path d="M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0H4zm5.5 0v3A1.5 1.5 0 0 0 11 4.5h3V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/> </svg> )}Apply similar changes to the remove button SVGs and FileChipExpanded icons.
Also applies to: 73-83
mpp-vscode/webview/src/components/DevInInput.tsx (1)
56-66: Consider broader whitespace detection for trigger characters.The trigger detection only checks for space (
' ') and newline ('\n'), but tabs and other whitespace characters are ignored. If users might type triggers after tabs, consider using a regex test like/\s/.test(charBeforeThat)instead.// Trigger completion if typing /, @, or $ after whitespace or at start if ((charBefore === '/' || charBefore === '@' || charBefore === '$') && - (charBeforeThat === ' ' || charBeforeThat === '\n' || cursorPos === 1)) { + (/\s/.test(charBeforeThat) || cursorPos === 1)) { onTriggerCompletion(charBefore as '/' | '@' | '$', cursorPos); }mpp-vscode/webview/src/components/FileSearchPopup.tsx (1)
20-25:FileItemduplicatesSelectedFileinterface.
FileItemhas the same shape asSelectedFileimported fromFileChip. Consider reusingSelectedFiledirectly to avoid maintaining duplicate type definitions.-interface FileItem { - name: string; - path: string; - relativePath: string; - isDirectory: boolean; -} +// Reuse SelectedFile as the item type +type FileItem = SelectedFile;mpp-vscode/webview/src/components/TopToolbar.tsx (1)
29-41: ExtractContextIndicatoroutsideTopToolbarto avoid re-creation on each render.Defining
ContextIndicatorinsideTopToolbarcauses a new function component instance on every render, which can lead to unnecessary DOM updates. Extract it as a separate component or useuseMemo/useCallbackpatterns.+interface ContextIndicatorProps { + isActive: boolean; + onToggle?: () => void; +} + +const ContextIndicator: React.FC<ContextIndicatorProps> = ({ isActive, onToggle }) => ( + <button + className={`context-indicator ${isActive ? 'active' : ''}`} + onClick={onToggle} + title={isActive ? 'Auto-add current file: ON' : 'Auto-add current file: OFF'} + > + <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"> + <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/> + </svg> + <span className="indicator-dot" /> + </button> +); + export const TopToolbar: React.FC<TopToolbarProps> = ({ // ... - // Context indicator component - const ContextIndicator = () => ( ... );Then use
<ContextIndicator isActive={autoAddCurrentFile} onToggle={onToggleAutoAdd} />.mpp-vscode/webview/src/components/ChatInput.tsx (1)
141-155: Consider providing user feedback on prompt optimization failure.Currently, errors are only logged to
console.error. Users won't know if optimization failed. Consider showing a brief notification or status indicator.mpp-vscode/src/providers/chat-view.ts (3)
29-54: Hook editorChangeDisposable into context.subscriptions
editorChangeDisposableis created but never disposed; in an extension this should be registered withcontext.subscriptionsso VS Code can clean it up.You can keep the field if you want to reference it later, but also push it into subscriptions:
this.editorChangeDisposable = vscode.window.onDidChangeActiveTextEditor((editor) => { if (editor && this.webviewView) { this.sendActiveFileUpdate(editor.document); } }); + this.context.subscriptions.push(this.editorChangeDisposable);This prevents leaks if the view/provider lifecycle changes in the future.
574-603: Harden MCP config path handling and home directory detection
openMcpConfigrelies onprocess.env.HOME || process.env.USERPROFILEand then string‑concatenates paths. Two minor robustness issues:
- If both env vars are unset (unlikely but possible),
homeDirbecomes'', so you end up creating/.autodev/mcp.json(or a relative path), which is surprising.- Manual string concatenation (
${homeDir}/.autodev/...) can produce mixed‑separator paths on Windows.Consider:
- Failing fast with a user‑friendly error if
homeDiris falsy.- Using
path.joinfor~/.autodevandmcp.jsonto keep paths OS‑correct.Example:
+import * as path from 'path'; … - const homeDir = process.env.HOME || process.env.USERPROFILE || ''; - const mcpConfigPath = `${homeDir}/.autodev/mcp.json`; + const homeDir = process.env.HOME || process.env.USERPROFILE; + if (!homeDir) { + throw new Error('HOME/USERPROFILE is not set; cannot locate user home directory.'); + } + const configDir = path.join(homeDir, '.autodev'); + const mcpConfigPath = path.join(configDir, 'mcp.json'); … - await fs.mkdir(`${homeDir}/.autodev`, { recursive: true }); + await fs.mkdir(configDir, { recursive: true });
725-765: Recent files implementation is fine; minor deduplication nit
handleGetRecentFilessensibly prefersvisibleTextEditorsand then adds othertextDocuments, skipping duplicates in the second pass.One small nit: you can also guard against duplicates within
visibleTextEditorsthemselves (e.g., split editors for the same file) by checkingrecentFiles.somebefore each push, but this is purely cosmetic given the low cardinality here.mpp-vscode/webview/src/App.tsx (1)
270-293: Consider extracting file command formatting logic.The DevIns file command formatting (lines 274-277) works correctly but could be extracted into a helper function for reusability and testability.
Example:
const formatFileCommands = (files: SelectedFile[]): string => { return files.map(f => f.isDirectory ? `/dir:${f.relativePath}` : `/file:${f.relativePath}` ).join('\n'); }; // In handleSend: if (files && files.length > 0) { fullContent = `${formatFileCommands(files)}\n\n${content}`; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
mpp-vscode/src/providers/chat-view.ts(3 hunks)mpp-vscode/webview/src/App.tsx(7 hunks)mpp-vscode/webview/src/components/ChatInput.css(3 hunks)mpp-vscode/webview/src/components/ChatInput.tsx(3 hunks)mpp-vscode/webview/src/components/CompletionPopup.css(1 hunks)mpp-vscode/webview/src/components/CompletionPopup.tsx(1 hunks)mpp-vscode/webview/src/components/DevInInput.css(1 hunks)mpp-vscode/webview/src/components/DevInInput.tsx(1 hunks)mpp-vscode/webview/src/components/FileChip.css(1 hunks)mpp-vscode/webview/src/components/FileChip.tsx(1 hunks)mpp-vscode/webview/src/components/FileSearchPopup.css(1 hunks)mpp-vscode/webview/src/components/FileSearchPopup.tsx(1 hunks)mpp-vscode/webview/src/components/TopToolbar.css(1 hunks)mpp-vscode/webview/src/components/TopToolbar.tsx(1 hunks)mpp-vscode/webview/src/hooks/useVSCode.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to mpp-ui/src/jsMain/typescript/**/*.{ts,tsx} : In CLI/TUI (TypeScript), use `mpp-ui/src/jsMain/typescript/design-system/` and import `semanticInk` / `semanticChalk`
Applied to files:
mpp-vscode/webview/src/components/FileChip.tsxmpp-vscode/webview/src/App.tsx
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/*.kt : In Kotlin/JS JsExport: Avoid `Flow`, use `Promise` instead
Applied to files:
mpp-vscode/src/providers/chat-view.ts
🧬 Code graph analysis (5)
mpp-vscode/webview/src/components/DevInInput.tsx (1)
core/src/main/kotlin/cc/unitmesh/devti/settings/locale/LanguageChangedCallback.kt (1)
placeholder(50-55)
mpp-vscode/webview/src/components/FileSearchPopup.tsx (3)
mpp-vscode/webview/src/components/FileChip.tsx (1)
SelectedFile(11-16)mpp-vscode/webview/src/hooks/useVSCode.ts (2)
useVSCode(95-114)ExtensionMessage(20-56)mpp-vscode/src/providers/chat-view.ts (1)
postMessage(222-224)
mpp-vscode/webview/src/components/ChatInput.tsx (5)
mpp-vscode/webview/src/components/FileChip.tsx (1)
SelectedFile(11-16)mpp-vscode/webview/src/components/ModelSelector.tsx (2)
ModelConfig(11-15)ModelSelector(24-112)mpp-vscode/webview/src/components/CompletionPopup.tsx (2)
CompletionItem(12-24)CompletionPopup(58-117)mpp-vscode/webview/src/components/TopToolbar.tsx (1)
TopToolbar(22-116)mpp-vscode/webview/src/components/DevInInput.tsx (1)
DevInInput(27-111)
mpp-vscode/src/providers/chat-view.ts (3)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/tool/impl/WebFetchTool.kt (1)
prompt(16-23)mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/model/AgentDefinition.kt (1)
systemPrompt(27-32)mpp-vscode/test/mocks/vscode.ts (1)
Uri(5-18)
mpp-vscode/webview/src/App.tsx (3)
mpp-vscode/webview/src/components/FileChip.tsx (1)
SelectedFile(11-16)mpp-vscode/webview/src/components/CompletionPopup.tsx (1)
CompletionItem(12-24)mpp-vscode/src/bridge/mpp-core.ts (1)
CompletionItem(177-184)
🪛 ast-grep (0.40.0)
mpp-vscode/webview/src/components/DevInInput.tsx
[warning] 93-93: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
mpp-vscode/webview/src/components/DevInInput.tsx
[error] 94-94: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
⏰ 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 (223)
- GitHub Check: Build (241)
🔇 Additional comments (26)
mpp-vscode/webview/src/components/ChatInput.css (1)
2-6: LGTM! UI styling updates align with new features.The CSS changes support the new token usage display, enhance button, and completion workflows. The styling is consistent with the VSCode design system and uses appropriate CSS variables.
Also applies to: 14-16, 62-69, 162-205
mpp-vscode/webview/src/components/CompletionPopup.css (1)
1-77: LGTM! Well-structured completion popup styling.The styling is clean and consistent, with appropriate hover/selected states and kind-specific icon colors. The z-index (line 15) should coordinate with other popups in the application to prevent stacking conflicts.
mpp-vscode/webview/src/components/FileChip.css (1)
1-122: LGTM! Clean file chip styling with good UX patterns.The progressive disclosure pattern for the remove button (opacity: 0 by default, visible on hover) provides a clean interface. Both inline and expanded variants are well-implemented.
mpp-vscode/webview/src/components/FileSearchPopup.css (1)
1-134: LGTM! Solid modal overlay implementation.The overlay pattern with fixed positioning and backdrop is well-implemented. The scrollable list area and search input styling follow VSCode conventions.
mpp-vscode/webview/src/hooks/useVSCode.ts (1)
37-50: LGTM! Message protocol extensions align with new features.The new event types (tokenUpdate, promptOptimized, searchFilesResult, activeFileChanged, completionsResult, etc.) properly support the file context, completion, and token tracking features.
mpp-vscode/webview/src/components/FileChip.tsx (1)
11-22: LGTM! Clean type definitions.The SelectedFile interface and FileChipProps are well-structured. Note that FileChipProps is not exported, which is appropriate as it's an internal implementation detail.
mpp-vscode/webview/src/components/DevInInput.css (1)
1-78: LGTM! Clean overlay-based syntax highlighting implementation.The layered approach (highlight overlay behind transparent textarea) is a solid pattern for syntax highlighting. The styling is well-organized with appropriate use of CSS variables for theming. The actual text displayed in the textarea uses
var(--foreground)which maintains proper contrast ratio (~8.6:1 against the dark background), meeting WCAG AA accessibility standards. The colored token backgrounds in the overlay provide visual enhancement without compromising readability.Likely an incorrect or invalid review comment.
mpp-vscode/webview/src/components/DevInInput.tsx (1)
69-87: XSS mitigation appears adequate, but add a clarifying comment.The static analysis tool flagged
dangerouslySetInnerHTML, but the implementation correctly escapes user input viaescapeHtml()before applying regex-based highlighting. Since$&only inserts already-escaped matched substrings, the current approach is safe.Consider adding a brief comment above
getHighlightedHtmlto document thatescapeHtmlmust always be called first, preventing future maintainers from accidentally reordering or bypassing the escape step.mpp-vscode/webview/src/components/CompletionPopup.tsx (2)
62-90: Global keydown handler may intercept events from other components.The
window.addEventListener('keydown', ...)approach captures all keyboard events when the popup is open. This could interfere with other input handlers (e.g., the parent textarea) if not carefully coordinated. Currently,handleKeyDowncallse.preventDefault()on navigation keys, which might block expected behavior in the textarea.Verify that parent components (like
ChatInput) guard against submission when the popup is open—this appears to be handled at line 159 inChatInput.tsx.
103-114: LGTM!The component correctly handles keyboard navigation with proper bounds checking, and the event listener cleanup in the
useEffectis properly implemented.mpp-vscode/webview/src/components/FileSearchPopup.tsx (1)
106-122: LGTM!The
useMemoforallItems, the filtering logic, and the keyboard navigation are implemented correctly. The scroll-into-view effect properly synchronizes selection visibility.mpp-vscode/webview/src/components/TopToolbar.tsx (1)
59-115: LGTM!The toolbar correctly manages expanded/collapsed states, handles file chip rendering with unique keys, and integrates the
FileSearchPopupfor adding files. The conditional rendering logic is clean.mpp-vscode/webview/src/components/ChatInput.tsx (2)
157-165: LGTM!The early return when
completionOpenis true correctly prevents message submission while the user is navigating completions, coordinating well withCompletionPopup's global keyboard handler.
167-200: LGTM!The component structure cleanly integrates
TopToolbar,DevInInput, andCompletionPopup. The file context management and completion UI are well-organized within the layout.mpp-vscode/src/providers/chat-view.ts (5)
56-91: Active file updates look correct and safely filtered
sendActiveFileUpdate/sendCurrentActiveFilecorrectly:
- Require a workspace,
- Skip non‑
fileURIs,- Filter out common binary extensions, and
- Use
asRelativePath+ file name for stable IDs.This should give the webview consistent, workspace‑relative paths without spamming it for non‑text files.
93-170: New webview message types and config init wiring look soundThe added cases for
searchFiles,getRecentFiles,readFileContent,requestConfig,getActiveFile,getCompletions, andapplyCompletionare routed to dedicated handlers and wrapped inawaitwhere needed.Also, awaiting
initializeFromConfig()at the end ofresolveWebviewViewwhile keepingrequestConfigas a cheap re‑sync entry point makes the startup/config flow robust even if the webview sends messages early.
562-571: Action wiring for prompt optimization and MCP config is straightforwardThe new
optimizePromptandopenMcpConfigbranches delegate cleanly tohandlePromptOptimizeandopenMcpConfig, and they reuse the existing action dispatch pattern without side effects on other cases.
813-835: Completion retrieval: defensive fallbacks look good
handleGetCompletions:
- Cleanly short‑circuits when
completionManageris missing.- Wraps the call in
try/catchand degrades to an empty list on failure.- Normalizes completion items into a JSON‑friendly array for the webview.
This is a good, defensive boundary for the completion manager.
841-868: Completion apply handler mirrors retrieval nicely
handleApplyCompletionmirrors the get‑completions flow:
- Safely handles missing
completionManager.- Wraps
applyCompletionintry/catch.- Normalizes the result into
{ newText, newCursorPosition, shouldTriggerNextCompletion }ornull.That shape should play well with the webview and keeps the extension side simple.
mpp-vscode/webview/src/App.tsx (7)
23-27: LGTM!The
CompletionResultinterface is well-defined with clear, required fields for managing completion application results.
46-54: LGTM!The new state hooks are appropriately typed with correct initial values.
264-267: LGTM!The effect correctly requests configuration on mount with appropriate dependencies.
347-360: LGTM!The MCP configuration and completion handlers are well-structured, properly memoized, and follow the established messaging pattern.
424-436: LGTM!The new props passed to
ChatInputcorrectly wire up the token usage, active file, completion, MCP config, and prompt optimization features.
228-237: Confirm whetherrelativePathshould source frommsg.data.relativePathormsg.data.path.Line 233 sets
relativePathto the same value aspath. If the extension sends both properties, verify thatrelativePathuses the correct source. Check the extension code that constructsactiveFileChangedmessages to understand the intended semantics of these two fields.
12-13: No action needed—CompletionItem interfaces are appropriately designed for their respective layers.The two
CompletionIteminterfaces serve different purposes and are not interchangeable by design.mpp-core.tsdefines a strict interface for the completion manager (lines 177–184 with requiredtriggerTypeandindex), whileCompletionPopup.tsxexports a more permissive interface (lines 12–24) with optional fields and legacy fallbacks for backward compatibility. The data flows safely frommpp-core(which provides required fields) to the webview (which accepts them as optional).CompletionPopupusestriggerTypefor icon display (line 39–41) and relies on the array index parameter rather than theindexfield, making the optional designation intentional and correct. Consolidating these definitions would conflate architectural layers unnecessarily; the current separation is appropriate.
| private async handleReadFileContent(filePath: string): Promise<void> { | ||
| if (!filePath) { | ||
| this.postMessage({ type: 'fileContentResult', data: { content: null, error: 'No path provided' } }); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| // Convert relative path to absolute if needed | ||
| let uri: vscode.Uri; | ||
| if (filePath.startsWith('/') || filePath.match(/^[a-zA-Z]:\\/)) { | ||
| // Already absolute path | ||
| uri = vscode.Uri.file(filePath); | ||
| } else { | ||
| // Relative path - resolve against workspace | ||
| const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; | ||
| if (!workspaceFolder) { | ||
| this.postMessage({ type: 'fileContentResult', data: { content: null, error: 'No workspace folder' } }); | ||
| return; | ||
| } | ||
| uri = vscode.Uri.joinPath(workspaceFolder.uri, filePath); | ||
| } | ||
|
|
||
| const content = await vscode.workspace.fs.readFile(uri); | ||
| const text = new TextDecoder().decode(content); | ||
|
|
||
| this.postMessage({ | ||
| type: 'fileContentResult', | ||
| data: { path: filePath, content: text } | ||
| }); | ||
| } catch (error) { | ||
| this.log(`Error reading file: ${error}`); | ||
| this.postMessage({ | ||
| type: 'fileContentResult', | ||
| data: { path: filePath, content: null, error: String(error) } | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider restricting readFileContent to workspace files
handleReadFileContent accepts either relative or absolute paths and will happily read any local file if the webview asks for it:
- Relative paths are resolved against the first workspace folder.
- Absolute paths (
/...orC:\...) bypass the workspace restriction entirely.
Given this is driven by your own webview UI, it’s not an immediate security hole, but it does expand what the webview can cause the extension to read (e.g. system or home‑directory files) if any XSS or compromised code ever appears in the webview.
Consider tightening this so that:
- Only files under the current workspace root(s) are allowed, or
- Absolute paths are rejected (or gated behind an explicit user confirmation), and
- You validate that the resolved
uriis within some allowed root.
That will better align this helper with the “project context” use case.
| // Token usage update | ||
| case 'tokenUpdate': | ||
| if (msg.data?.totalTokens != null) { | ||
| setTotalTokens(msg.data.totalTokens as number); | ||
| } | ||
| break; | ||
|
|
||
| // Active file changed (for auto-add current file) | ||
| case 'activeFileChanged': | ||
| if (msg.data) { | ||
| setActiveFile({ | ||
| path: msg.data.path as string, | ||
| name: msg.data.name as string, | ||
| relativePath: msg.data.path as string, | ||
| isDirectory: msg.data.isDirectory as boolean || false | ||
| }); | ||
| } | ||
| break; | ||
|
|
||
| // Completion results from mpp-core | ||
| case 'completionsResult': | ||
| if (msg.data?.items) { | ||
| setCompletionItems(msg.data.items as CompletionItem[]); | ||
| } | ||
| break; | ||
|
|
||
| // Completion applied result | ||
| case 'completionApplied': | ||
| if (msg.data) { | ||
| setCompletionResult({ | ||
| newText: msg.data.newText as string, | ||
| newCursorPosition: msg.data.newCursorPosition as number, | ||
| shouldTriggerNextCompletion: msg.data.shouldTriggerNextCompletion as boolean | ||
| }); | ||
| } | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strengthen type safety in message handling.
The message handlers use unchecked type assertions (as string, as number, as CompletionItem[]) without validating the data structure. If msg.data doesn't match the expected shape, runtime errors could occur.
Consider adding runtime validation or using type guards to verify the data before casting, especially for complex types like CompletionItem[].
Example validation pattern:
case 'completionsResult':
if (msg.data?.items) {
- setCompletionItems(msg.data.items as CompletionItem[]);
+ const items = msg.data.items;
+ if (Array.isArray(items)) {
+ setCompletionItems(items as CompletionItem[]);
+ }
}
break;📝 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.
| // Token usage update | |
| case 'tokenUpdate': | |
| if (msg.data?.totalTokens != null) { | |
| setTotalTokens(msg.data.totalTokens as number); | |
| } | |
| break; | |
| // Active file changed (for auto-add current file) | |
| case 'activeFileChanged': | |
| if (msg.data) { | |
| setActiveFile({ | |
| path: msg.data.path as string, | |
| name: msg.data.name as string, | |
| relativePath: msg.data.path as string, | |
| isDirectory: msg.data.isDirectory as boolean || false | |
| }); | |
| } | |
| break; | |
| // Completion results from mpp-core | |
| case 'completionsResult': | |
| if (msg.data?.items) { | |
| setCompletionItems(msg.data.items as CompletionItem[]); | |
| } | |
| break; | |
| // Completion applied result | |
| case 'completionApplied': | |
| if (msg.data) { | |
| setCompletionResult({ | |
| newText: msg.data.newText as string, | |
| newCursorPosition: msg.data.newCursorPosition as number, | |
| shouldTriggerNextCompletion: msg.data.shouldTriggerNextCompletion as boolean | |
| }); | |
| } | |
| break; | |
| // Token usage update | |
| case 'tokenUpdate': | |
| if (msg.data?.totalTokens != null) { | |
| setTotalTokens(msg.data.totalTokens as number); | |
| } | |
| break; | |
| // Active file changed (for auto-add current file) | |
| case 'activeFileChanged': | |
| if (msg.data) { | |
| setActiveFile({ | |
| path: msg.data.path as string, | |
| name: msg.data.name as string, | |
| relativePath: msg.data.path as string, | |
| isDirectory: msg.data.isDirectory as boolean || false | |
| }); | |
| } | |
| break; | |
| // Completion results from mpp-core | |
| case 'completionsResult': | |
| if (msg.data?.items) { | |
| const items = msg.data.items; | |
| if (Array.isArray(items)) { | |
| setCompletionItems(items as CompletionItem[]); | |
| } | |
| } | |
| break; | |
| // Completion applied result | |
| case 'completionApplied': | |
| if (msg.data) { | |
| setCompletionResult({ | |
| newText: msg.data.newText as string, | |
| newCursorPosition: msg.data.newCursorPosition as number, | |
| shouldTriggerNextCompletion: msg.data.shouldTriggerNextCompletion as boolean | |
| }); | |
| } | |
| break; |
🤖 Prompt for AI Agents
In mpp-vscode/webview/src/App.tsx around lines 220 to 255, the message handlers
perform unchecked casts (e.g., "as string", "as number", "as CompletionItem[]")
which can cause runtime errors if msg.data is malformed; replace these casts
with runtime validation/type-guards: check msg.data exists, validate primitive
fields with typeof (e.g., typeof msg.data.totalTokens === 'number'), validate
objects have required keys, validate arrays with Array.isArray and per-item
type-guards for CompletionItem, and only call state setters when validation
passes; on validation failure, log an error and avoid updating state (or use
safe defaults), and factor repeated checks into small helper validators to keep
the handler concise.
| // Handle prompt optimization | ||
| const handlePromptOptimize = useCallback(async (prompt: string): Promise<string> => { | ||
| return new Promise((resolve) => { | ||
| // Send optimization request to extension | ||
| postMessage({ type: 'action', action: 'optimizePrompt', data: { prompt } }); | ||
|
|
||
| // Listen for response | ||
| const handler = (event: MessageEvent) => { | ||
| const msg = event.data; | ||
| if (msg.type === 'promptOptimized' && msg.data?.optimizedPrompt) { | ||
| window.removeEventListener('message', handler); | ||
| resolve(msg.data.optimizedPrompt as string); | ||
| } else if (msg.type === 'promptOptimizeFailed') { | ||
| window.removeEventListener('message', handler); | ||
| resolve(prompt); // Return original on failure | ||
| } | ||
| }; | ||
| window.addEventListener('message', handler); | ||
|
|
||
| // Timeout after 30 seconds | ||
| setTimeout(() => { | ||
| window.removeEventListener('message', handler); | ||
| resolve(prompt); | ||
| }, 30000); | ||
| }); | ||
| }, [postMessage]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix memory leak and use established message handling pattern.
The handlePromptOptimize function adds a window event listener that isn't cleaned up if the component unmounts, creating a memory leak. Additionally, it bypasses the useVSCode hook's onMessage mechanism, which is inconsistent with the rest of the codebase.
Refactor to use the established pattern or manage cleanup properly:
const handlePromptOptimize = useCallback(async (prompt: string): Promise<string> => {
return new Promise((resolve) => {
+ let resolved = false;
+
postMessage({ type: 'action', action: 'optimizePrompt', data: { prompt } });
const handler = (event: MessageEvent) => {
const msg = event.data;
if (msg.type === 'promptOptimized' && msg.data?.optimizedPrompt) {
+ if (resolved) return;
+ resolved = true;
window.removeEventListener('message', handler);
resolve(msg.data.optimizedPrompt as string);
} else if (msg.type === 'promptOptimizeFailed') {
+ if (resolved) return;
+ resolved = true;
window.removeEventListener('message', handler);
resolve(prompt);
}
};
window.addEventListener('message', handler);
setTimeout(() => {
+ if (resolved) return;
+ resolved = true;
window.removeEventListener('message', handler);
resolve(prompt);
}, 30000);
});
}, [postMessage]);Better yet, consider using a request ID system with the onMessage hook to avoid manual event listener management entirely.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
mpp-vscode/webview/src/App.tsx around lines 320 to 345: the current
handlePromptOptimize creates a window message listener that can leak if the
component unmounts and it also bypasses the project's useVSCode onMessage
pattern; refactor to use the established useVSCode/onMessage mechanism (or pass
postMessage into a request/response helper) and implement a request ID
correlation so responses are matched to the Promise, and ensure any temporary
handlers are registered via the hook and removed on unmount; alternatively, keep
the Promise-based API but register the listener through the hook (not
window.addEventListener), store the timeout ID and request ID, and clear both
the listener and timeout when a response arrives or the component unmounts.
| // Handle completion result from mpp-core | ||
| useEffect(() => { | ||
| textareaRef.current?.focus(); | ||
| if (completionResult) { | ||
| setInput(completionResult.newText); | ||
| cursorPositionRef.current = completionResult.newCursorPosition; | ||
| if (completionResult.shouldTriggerNextCompletion && onGetCompletions) { | ||
| // Trigger next completion | ||
| onGetCompletions(completionResult.newText, completionResult.newCursorPosition); | ||
| } else { | ||
| setCompletionOpen(false); | ||
| } | ||
| } | ||
| }, [completionResult, onGetCompletions]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor position is stored but not applied to the textarea.
cursorPositionRef.current is updated, but the actual textarea cursor is not programmatically set to newCursorPosition. After setInput(completionResult.newText), the cursor will likely jump to the end, causing subsequent completions to use the wrong context.
If cursor positioning matters for chained completions, you'll need to set textarea.selectionStart and selectionEnd after the state update.
Do you want me to generate a solution that properly positions the cursor after applying a completion result?
🤖 Prompt for AI Agents
In mpp-vscode/webview/src/components/ChatInput.tsx around lines 72-84, the code
updates cursorPositionRef.current but never applies it to the textarea DOM node,
so after setInput(...) the cursor jumps to the end. After calling
setInput(completionResult.newText) ensure you apply the new cursor position to
the textarea: get the textarea ref (ensure it exists), then after the state/DOM
update (use requestAnimationFrame or setTimeout(...,0) or useLayoutEffect) set
textarea.selectionStart = textarea.selectionEnd =
completionResult.newCursorPosition (or set selectionEnd appropriately if you
need a range). Keep the existing flow for
shouldTriggerNextCompletion/onGetCompletions and closing completionOpen, but
only set the DOM selection when the textarea ref is present and the completion
does not immediately trigger another completion that will replace the text.
| // Handle completion trigger - request completions from mpp-core | ||
| const handleTriggerCompletion = useCallback((trigger: '/' | '@' | '$', position: number) => { | ||
| cursorPositionRef.current = position; | ||
| if (onGetCompletions) { | ||
| // Use mpp-core for completions | ||
| onGetCompletions(input.substring(0, position) + trigger, position + 1); | ||
| } | ||
| }, [input, onGetCompletions]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "getCompletions|onGetCompletions" --type=ts --type=tsx -B 2 -A 5Repository: phodal/auto-dev-sketch
Length of output: 93
🏁 Script executed:
rg -n "handleTriggerCompletion" --type=ts --type=tsx -B 3 -A 3Repository: phodal/auto-dev-sketch
Length of output: 93
🏁 Script executed:
rg -n "onGetCompletions" webview/src/components/ChatInput.tsx -B 5 -A 10Repository: phodal/auto-dev-sketch
Length of output: 142
🏁 Script executed:
git ls-files | head -30Repository: phodal/auto-dev-sketch
Length of output: 1070
🏁 Script executed:
find . -name "ChatInput.tsx" -o -name "*ChatInput*" 2>/dev/nullRepository: phodal/auto-dev-sketch
Length of output: 165
🏁 Script executed:
find . -type f -name "*.ts" -o -name "*.tsx" | grep -i chat | head -20Repository: phodal/auto-dev-sketch
Length of output: 360
🏁 Script executed:
wc -l ./mpp-vscode/webview/src/components/ChatInput.tsxRepository: phodal/auto-dev-sketch
Length of output: 119
🏁 Script executed:
cat -n ./mpp-vscode/webview/src/components/ChatInput.tsx | sed -n '100,130p'Repository: phodal/auto-dev-sketch
Length of output: 1401
🏁 Script executed:
rg -n "getCompletions|onGetCompletions|handleTriggerCompletion" ./mpp-vscode/webview/src/components/ChatInput.tsx -B 2 -A 5Repository: phodal/auto-dev-sketch
Length of output: 2405
🏁 Script executed:
rg -n "getCompletions" --type=ts --type=js -B 3 -A 8Repository: phodal/auto-dev-sketch
Length of output: 7941
🏁 Script executed:
rg -n "DevInInput|onTriggerCompletion" ./mpp-vscode/webview/src/components/ -B 3 -A 5Repository: phodal/auto-dev-sketch
Length of output: 6533
🏁 Script executed:
find ./mpp-vscode/webview/src -name "DevInInput*" -o -name "*DevInInput*"Repository: phodal/auto-dev-sketch
Length of output: 167
🏁 Script executed:
cat -n ./mpp-vscode/webview/src/components/ChatInput.tsx | sed -n '1,50p'Repository: phodal/auto-dev-sketch
Length of output: 2119
🏁 Script executed:
cat -n ./mpp-vscode/webview/src/components/DevInInput.tsx | sed -n '50,70p'Repository: phodal/auto-dev-sketch
Length of output: 1066
Remove duplicate trigger character and incorrect position offset in handleTriggerCompletion.
In handleTriggerCompletion (line 116), the trigger character is already present in input at the cursor position when DevInInput calls this handler. Line 64 in DevInInput.tsx shows that onTriggerCompletion is called with cursorPos after the trigger has been typed, meaning input[position - 1] is already the trigger character.
The current code concatenates the trigger again (+ trigger) and increments the position (position + 1), which duplicates the trigger and provides an incorrect cursor position to getCompletions.
Fix: Use onGetCompletions(input.substring(0, position), position) instead of onGetCompletions(input.substring(0, position) + trigger, position + 1).
🤖 Prompt for AI Agents
In mpp-vscode/webview/src/components/ChatInput.tsx around lines 111 to 118,
handleTriggerCompletion is wrongly re-inserting the trigger and advancing the
cursor: input already contains the trigger at position-1, so replace the call to
onGetCompletions with one that passes the current prefix and position unchanged
by using onGetCompletions(input.substring(0, position), position) instead of
concatenating the trigger and using position + 1.
| <button className="file-chip-remove" onClick={onRemove} title="Remove from context"> | ||
| <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"> | ||
| <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> | ||
| </svg> | ||
| </button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add explicit button type to prevent form submission.
The remove buttons should have type="button" to prevent unintended form submissions if these chips are used within a form context.
Apply this diff:
- <button className="file-chip-remove" onClick={onRemove} title="Remove from context">
+ <button type="button" className="file-chip-remove" onClick={onRemove} title="Remove from context">And similarly for FileChipExpanded (line 88):
- <button className="file-chip-remove" onClick={onRemove} title="Remove from context">
+ <button type="button" className="file-chip-remove" onClick={onRemove} title="Remove from context">Also applies to: 88-92
🤖 Prompt for AI Agents
In mpp-vscode/webview/src/components/FileChip.tsx around lines 42-46 and also
around lines 88-92, the remove <button> elements lack an explicit type causing
them to act as submit buttons when inside a form; update each remove button to
include type="button" (i.e., add the attribute to the existing <button> tags in
both FileChip and FileChipExpanded) to prevent unintended form submissions while
preserving existing className, onClick, title, and SVG content.
| function getTruncatedPath(path: string): string { | ||
| const parentPath = path.substring(0, path.lastIndexOf('/')); | ||
| if (!parentPath) return ''; | ||
|
|
||
| if (parentPath.length <= 30) return parentPath; | ||
|
|
||
| const parts = parentPath.split('/'); | ||
| if (parts.length <= 2) return `...${parentPath}`; | ||
|
|
||
| const keepParts = parts.slice(-3); | ||
| return `.../${keepParts.join('/')}`; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path truncation logic may not handle all edge cases.
The getTruncatedPath function has a few potential issues:
- Line 55: Uses
/as path separator, which won't work correctly on Windows (should use platform-specific separator or normalize paths) - Line 58: The 30-character threshold is arbitrary and may not work well for all contexts
- Line 61: If there are ≤2 parts, it prepends
...but might still exceed the length limit
Consider this more robust implementation:
function getTruncatedPath(path: string): string {
- const parentPath = path.substring(0, path.lastIndexOf('/'));
+ // Normalize path separators
+ const normalizedPath = path.replace(/\\/g, '/');
+ const parentPath = normalizedPath.substring(0, normalizedPath.lastIndexOf('/'));
if (!parentPath) return '';
if (parentPath.length <= 30) return parentPath;
const parts = parentPath.split('/');
- if (parts.length <= 2) return `...${parentPath}`;
+ if (parts.length <= 2) {
+ // For short paths, truncate from the start
+ return `...${parentPath.slice(-27)}`;
+ }
const keepParts = parts.slice(-3);
return `.../${keepParts.join('/')}`;
}📝 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.
| function getTruncatedPath(path: string): string { | |
| const parentPath = path.substring(0, path.lastIndexOf('/')); | |
| if (!parentPath) return ''; | |
| if (parentPath.length <= 30) return parentPath; | |
| const parts = parentPath.split('/'); | |
| if (parts.length <= 2) return `...${parentPath}`; | |
| const keepParts = parts.slice(-3); | |
| return `.../${keepParts.join('/')}`; | |
| } | |
| function getTruncatedPath(path: string): string { | |
| // Normalize path separators | |
| const normalizedPath = path.replace(/\\/g, '/'); | |
| const parentPath = normalizedPath.substring(0, normalizedPath.lastIndexOf('/')); | |
| if (!parentPath) return ''; | |
| if (parentPath.length <= 30) return parentPath; | |
| const parts = parentPath.split('/'); | |
| if (parts.length <= 2) { | |
| // For short paths, truncate from the start | |
| return `...${parentPath.slice(-27)}`; | |
| } | |
| const keepParts = parts.slice(-3); | |
| return `.../${keepParts.join('/')}`; | |
| } |
| useEffect(() => { | ||
| if (!isOpen) return; | ||
| if (searchQuery.length >= 2) { | ||
| setIsLoading(true); | ||
| const timer = setTimeout(() => { | ||
| postMessage({ type: 'searchFiles', data: { query: searchQuery } }); | ||
| }, 150); | ||
| return () => clearTimeout(timer); | ||
| } else { | ||
| setFiles([]); | ||
| setFolders([]); | ||
| } | ||
| }, [searchQuery, isOpen, postMessage]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isLoading may remain true if query drops below 2 characters.
When searchQuery.length >= 2, isLoading is set to true. If the user then deletes characters so the query becomes shorter than 2, the else branch clears the results but never resets isLoading to false, leaving the UI in a stale loading state.
} else {
setFiles([]);
setFolders([]);
+ setIsLoading(false);
}🤖 Prompt for AI Agents
In mpp-vscode/webview/src/components/FileSearchPopup.tsx around lines 92 to 104,
the effect sets isLoading(true) when searchQuery.length >= 2 but never resets
isLoading when the query drops below 2 (else branch only clears files/folders),
causing a stale loading state; update the effect so the else branch also calls
setIsLoading(false) and ensure when isOpen becomes false you likewise reset
isLoading(false) (or add cleanup) so the loading flag is always cleared when
searches are cancelled or the popup closes.
| .expand-button, | ||
| .clear-button { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| padding: 4px; | ||
| background: transparent; | ||
| border: none; | ||
| border-radius: 4px; | ||
| color: var(--foreground); | ||
| opacity: 0.6; | ||
| cursor: pointer; | ||
| transition: opacity 0.15s, background 0.15s; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add focus states for keyboard navigation accessibility.
The buttons and context indicator lack :focus-visible states, which breaks keyboard navigation in the IDE and violates WCAG accessibility requirements. Users navigating via Tab key will not see a focus indicator.
Apply this diff to add focus states:
.expand-button,
.clear-button {
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
background: transparent;
border: none;
border-radius: 4px;
color: var(--foreground);
opacity: 0.6;
cursor: pointer;
transition: opacity 0.15s, background 0.15s;
}
.expand-button:hover,
.clear-button:hover {
opacity: 1;
background: var(--list-hover-background);
}
+.expand-button:focus-visible,
+.clear-button:focus-visible {
+ opacity: 1;
+ outline: 1px solid var(--focus-border);
+ outline-offset: 2px;
+}Also add focus state to context indicator:
.context-indicator:hover {
opacity: 0.8;
background: var(--list-hover-background);
}
.context-indicator.active {
opacity: 1;
color: var(--vscode-textLink-foreground, #3794ff);
}
+.context-indicator:focus-visible {
+ opacity: 1;
+ outline: 1px solid var(--focus-border);
+ outline-offset: 2px;
+}And for the add-file button:
.add-file-button:hover {
background: var(--list-hover-background);
border-color: var(--focus-border);
}
+.add-file-button:focus-visible {
+ border-color: var(--focus-border);
+ outline: 1px solid var(--focus-border);
+ outline-offset: 2px;
+}Also applies to: 139-152
🤖 Prompt for AI Agents
In mpp-vscode/webview/src/components/TopToolbar.css around lines 89-102 (also
apply same changes to 139-152), the interactive controls lack :focus-visible
styles which breaks keyboard navigation; add :focus-visible rules for
.expand-button and .clear-button, the context indicator element, and the
add-file button that provide a visible focus ring (for example a 2px outline or
a subtle box-shadow using a theme variable like --focus), preserve
border-radius, and optionally set a slightly different background/opacity on
focus; ensure these selectors use :focus-visible (not :focus) so mouse
interactions aren’t affected and keep the existing transition so the focus
indicator appears smoothly.
Summary
Implements Issue #33 - Enhanced Chat Input for VSCode Extension
Changes
Phase 1: Enhanced Input Area with File Context
FileChip.tsx/css- File tag component with remove buttonFileSearchPopup.tsx/css- File search popup with fuzzy searchTopToolbar.tsx/css- Top toolbar for file context managementhandleSearchFiles,handleGetRecentFiles,handleReadFileContentPhase 2: DevIn Language Support
DevInInput.tsx/css- Input with syntax highlighting/command:,@agent,$variablesyntaxCompletionPopup.tsx/css- Auto-completion popupJsCompletionManagerfor completionsPhase 3: Bottom Toolbar Enhancement
Phase 4: Context Manager
Phase 5: MCP Configuration
~/.autodev/mcp.jsonTechnical Details
JsCompletionManagerfor DevIn command completionsoverflow: hiddenfrom containerAcceptance Criteria
Closes #33
Pull Request opened by Augment Code with guidance from the PR author
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.