diff --git a/packages/vscode-ide-companion/package.json b/packages/vscode-ide-companion/package.json index fc9d7836..6b132258 100644 --- a/packages/vscode-ide-companion/package.json +++ b/packages/vscode-ide-companion/package.json @@ -56,17 +56,17 @@ "title": "Qwen Code: View Third-Party Notices" }, { - "command": "qwenCode.openChat", + "command": "qwen-code.openChat", "title": "Qwen Code: Open Chat", "icon": "./assets/icon.png" }, { - "command": "qwenCode.clearAuthCache", - "title": "Qwen Code: Clear Authentication Cache" + "command": "qwen-code.login", + "title": "Qwen Code: Login" }, { - "command": "qwenCode.login", - "title": "Qwen Code: Login" + "command": "qwen-code.clearAuthCache", + "title": "Qwen Code: Clear Authentication Cache" } ], "configuration": { @@ -90,7 +90,7 @@ "when": "qwen.diff.isVisible" }, { - "command": "qwenCode.login", + "command": "qwen-code.login", "when": "false" } ], @@ -106,7 +106,7 @@ "group": "navigation" }, { - "command": "qwenCode.openChat", + "command": "qwen-code.openChat", "group": "navigation" } ] @@ -123,7 +123,7 @@ "when": "qwen.diff.isVisible" }, { - "command": "qwenCode.openChat", + "command": "qwen-code.openChat", "key": "ctrl+shift+a", "mac": "cmd+shift+a" } diff --git a/packages/vscode-ide-companion/src/acp/acpConnection.ts b/packages/vscode-ide-companion/src/acp/acpConnection.ts index 38b48fc4..27a111c6 100644 --- a/packages/vscode-ide-companion/src/acp/acpConnection.ts +++ b/packages/vscode-ide-companion/src/acp/acpConnection.ts @@ -26,23 +26,6 @@ import { determineNodePathForCli } from '../cli/cliPathDetector.js'; * ACP Connection Handler for VSCode Extension * * This class implements the client side of the ACP (Agent Communication Protocol). - * - * Implementation Status: - * - * Client Methods (Methods this class implements, called by CLI): - * ✅ session/update - Handle session updates via onSessionUpdate callback - * ✅ session/request_permission - Request user permission for tool execution - * ✅ fs/read_text_file - Read file from workspace - * ✅ fs/write_text_file - Write file to workspace - * - * Agent Methods (Methods CLI implements, called by this class): - * ✅ initialize - Initialize ACP protocol connection - * ✅ authenticate - Authenticate with selected auth method - * ✅ session/new - Create new chat session - * ✅ session/prompt - Send user message to agent - * ✅ session/cancel - Cancel current generation - * ✅ session/load - Load previous session - * ✅ session/save - Save current session */ export class AcpConnection { private child: ChildProcess | null = null; diff --git a/packages/vscode-ide-companion/src/agents/qwenTypes.ts b/packages/vscode-ide-companion/src/agents/qwenTypes.ts index 6cb5c625..2ac22c04 100644 --- a/packages/vscode-ide-companion/src/agents/qwenTypes.ts +++ b/packages/vscode-ide-companion/src/agents/qwenTypes.ts @@ -18,7 +18,7 @@ export interface PlanEntry { /** Entry content */ content: string; /** Priority */ - priority: 'high' | 'medium' | 'low'; + priority?: 'high' | 'medium' | 'low'; /** Status */ status: 'pending' | 'in_progress' | 'completed'; } diff --git a/packages/vscode-ide-companion/src/cli/cliVersionManager.ts b/packages/vscode-ide-companion/src/cli/cliVersionManager.ts index acf76bf6..383ae638 100644 --- a/packages/vscode-ide-companion/src/cli/cliVersionManager.ts +++ b/packages/vscode-ide-companion/src/cli/cliVersionManager.ts @@ -6,9 +6,6 @@ import { CliDetector, type CliDetectionResult } from './cliDetector.js'; -/** - * Minimum CLI version that supports session/list and session/load ACP methods - */ export const MIN_CLI_VERSION_FOR_SESSION_METHODS = '0.4.0'; /** diff --git a/packages/vscode-ide-companion/src/commands/index.ts b/packages/vscode-ide-companion/src/commands/index.ts index ef432825..7ca510a7 100644 --- a/packages/vscode-ide-companion/src/commands/index.ts +++ b/packages/vscode-ide-companion/src/commands/index.ts @@ -4,8 +4,12 @@ import type { WebViewProvider } from '../webview/WebViewProvider.js'; type Logger = (message: string) => void; +export const runQwenCodeCommand = 'qwen-code.runQwenCode'; export const showDiffCommand = 'qwenCode.showDiff'; -export const openChatCommand = 'qwenCode.openChat'; +export const openChatCommand = 'qwen-code.openChat'; +export const openNewChatTabCommand = 'qwenCode.openNewChatTab'; +export const loginCommand = 'qwen-code.login'; +export const clearAuthCacheCommand = 'qwen-code.clearAuthCache'; export function registerNewCommands( context: vscode.ExtensionContext, @@ -20,15 +24,15 @@ export function registerNewCommands( vscode.commands.registerCommand(openChatCommand, async () => { const config = vscode.workspace.getConfiguration('qwenCode'); const useTerminal = config.get('useTerminal', false); - console.log('[Command] Using terminal mode:', useTerminal); + + // Use terminal mode if (useTerminal) { - // 使用终端模式 await vscode.commands.executeCommand( - 'qwen-code.runQwenCode', - vscode.TerminalLocation.Editor, // 在编辑器区域创建终端, + runQwenCodeCommand, + vscode.TerminalLocation.Editor, // create a terminal in the editor area, ); } else { - // 使用 WebView 模式 + // Use WebView mode const providers = getWebViewProviders(); if (providers.length > 0) { await providers[providers.length - 1].show(); @@ -44,7 +48,6 @@ export function registerNewCommands( vscode.commands.registerCommand( showDiffCommand, async (args: { path: string; oldText: string; newText: string }) => { - log(`[Command] showDiff called for: ${args.path}`); try { let absolutePath = args.path; if (!args.path.startsWith('/') && !args.path.match(/^[a-zA-Z]:/)) { @@ -68,27 +71,14 @@ export function registerNewCommands( // TODO: qwenCode.openNewChatTab (not contributed in package.json; used programmatically) disposables.push( - vscode.commands.registerCommand('qwenCode.openNewChatTab', async () => { + vscode.commands.registerCommand(openNewChatTabCommand, async () => { const provider = createWebViewProvider(); await provider.show(); }), ); disposables.push( - vscode.commands.registerCommand('qwenCode.clearAuthCache', async () => { - const providers = getWebViewProviders(); - for (const provider of providers) { - await provider.clearAuthCache(); - } - vscode.window.showInformationMessage( - 'Qwen Code authentication cache cleared. You will need to login again on next connection.', - ); - log('Auth cache cleared by user'); - }), - ); - - disposables.push( - vscode.commands.registerCommand('qwenCode.login', async () => { + vscode.commands.registerCommand(loginCommand, async () => { const providers = getWebViewProviders(); if (providers.length > 0) { await providers[providers.length - 1].forceReLogin(); @@ -100,5 +90,18 @@ export function registerNewCommands( }), ); + disposables.push( + vscode.commands.registerCommand(clearAuthCacheCommand, async () => { + const providers = getWebViewProviders(); + for (const provider of providers) { + await provider.clearAuthCache(); + } + vscode.window.showInformationMessage( + 'Qwen Code authentication cache cleared. You will need to login again on next connection.', + ); + log('Auth cache cleared by user'); + }), + ); + context.subscriptions.push(...disposables); } diff --git a/packages/vscode-ide-companion/src/constants/acpSchema.ts b/packages/vscode-ide-companion/src/constants/acpSchema.ts index 5f826f97..65125b63 100644 --- a/packages/vscode-ide-companion/src/constants/acpSchema.ts +++ b/packages/vscode-ide-companion/src/constants/acpSchema.ts @@ -4,26 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * ACP (Agent Communication Protocol) Method Definitions - * - * This file defines the protocol methods for communication between - * the VSCode extension (Client) and the qwen CLI (Agent/Server). - */ - -/** - * Methods that the Agent (CLI) implements and receives from Client (VSCode) - * - * Status in qwen CLI: - * ✅ initialize - Protocol initialization - * ✅ authenticate - User authentication - * ✅ session/new - Create new session - * ✅ session/load - Load existing session (v0.2.4+) - * ✅ session/list - List available sessions (v0.2.4+) - * ✅ session/prompt - Send user message to agent - * ✅ session/cancel - Cancel current generation - * ✅ session/save - Save current session - */ export const AGENT_METHODS = { authenticate: 'authenticate', initialize: 'initialize', @@ -35,15 +15,6 @@ export const AGENT_METHODS = { session_save: 'session/save', } as const; -/** - * Methods that the Client (VSCode) implements and receives from Agent (CLI) - * - * Status in VSCode extension: - * ✅ fs/read_text_file - Read file content - * ✅ fs/write_text_file - Write file content - * ✅ session/request_permission - Request user permission for tool execution - * ✅ session/update - Stream session updates (notification) - */ export const CLIENT_METHODS = { fs_read_text_file: 'fs/read_text_file', fs_write_text_file: 'fs/write_text_file', diff --git a/packages/vscode-ide-companion/src/webview/App.tsx b/packages/vscode-ide-companion/src/webview/App.tsx index 0a15af83..f4b853be 100644 --- a/packages/vscode-ide-companion/src/webview/App.tsx +++ b/packages/vscode-ide-companion/src/webview/App.tsx @@ -21,15 +21,14 @@ import { useMessageSubmit } from './hooks/useMessageSubmit.js'; import type { PermissionOption, ToolCall as PermissionToolCall, -} from './components/PermissionRequest.js'; +} from './components/PermissionDrawer/PermissionRequest.js'; import type { TextMessage } from './hooks/message/useMessageHandling.js'; import type { ToolCallData } from './components/ToolCall.js'; -import { PermissionDrawer } from './components/PermissionDrawer.js'; +import { PermissionDrawer } from './components/PermissionDrawer/PermissionDrawer.js'; import { ToolCall } from './components/ToolCall.js'; import { hasToolCallOutput } from './components/toolcalls/shared/utils.js'; // import { InProgressToolCall } from './components/InProgressToolCall.js'; import { EmptyState } from './components/ui/EmptyState.js'; -import type { PlanEntry } from './components/PlanDisplay.js'; import { type CompletionItem } from './types/CompletionTypes.js'; import { useCompletionTrigger } from './hooks/useCompletionTrigger.js'; import { InfoBanner } from './components/ui/InfoBanner.js'; @@ -45,6 +44,7 @@ import { InputForm } from './components/InputForm.js'; import { SessionSelector } from './components/session/SessionSelector.js'; import { FileIcon, UserIcon } from './components/icons/index.js'; import type { EditMode } from './types/toolCall.js'; +import type { PlanEntry } from '../agents/qwenTypes.js'; export const App: React.FC = () => { const vscode = useVSCode(); diff --git a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts b/packages/vscode-ide-companion/src/webview/WebViewProvider.ts index 5d97ac31..f7b52d5c 100644 --- a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts +++ b/packages/vscode-ide-companion/src/webview/WebViewProvider.ts @@ -16,6 +16,7 @@ import { WebViewContent } from '../webview/WebViewContent.js'; import { CliInstaller } from '../cli/cliInstaller.js'; import { getFileName } from './utils/webviewUtils.js'; import { authMethod } from '../auth/index.js'; +import { runQwenCodeCommand } from '../commands/index.js'; export class WebViewProvider { private panelManager: PanelManager; @@ -1067,7 +1068,7 @@ export class WebViewProvider { if (useTerminal) { // In terminal mode, execute the runQwenCode command to open a new terminal try { - await vscode.commands.executeCommand('qwen-code.runQwenCode'); + await vscode.commands.executeCommand(runQwenCodeCommand); console.log('[WebViewProvider] Opened new terminal session'); } catch (error) { console.error( diff --git a/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.css b/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.css index 8734887b..1382483d 100644 --- a/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.css +++ b/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.css @@ -135,7 +135,8 @@ border: 1px solid var(--app-primary-border-color); border-radius: var(--corner-radius-small, 4px); padding: 0.2em 0.4em; - white-space: nowrap; + white-space: pre-wrap; /* 支持自动换行 */ + word-break: break-word; /* 在必要时断词 */ } .markdown-content pre { @@ -207,7 +208,8 @@ background: none; border: none; padding: 0; - white-space: pre; + white-space: pre-wrap; /* 支持自动换行 */ + word-break: break-word; /* 在必要时断词 */ } .markdown-content .file-path-link { diff --git a/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.tsx b/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.tsx index 6e852e52..09c41652 100644 --- a/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.tsx +++ b/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.tsx @@ -19,11 +19,12 @@ interface MarkdownRendererProps { /** * Regular expressions for parsing content */ +// Match absolute file paths like: /path/to/file.ts or C:\path\to\file.ts const FILE_PATH_REGEX = - /([a-zA-Z]:)?([/\\][\w\-. ]+)+\.(tsx?|jsx?|css|scss|json|md|py|java|go|rs|c|cpp|h|hpp|sh|yaml|yml|toml|xml|html|vue|svelte)/gi; -// Match file paths with optional line numbers like: path/file.ts#7-14 or path/file.ts#7 + /(?:[a-zA-Z]:)?[/\\](?:[\w\-. ]+[/\\])+[\w\-. ]+\.(tsx?|jsx?|css|scss|json|md|py|java|go|rs|c|cpp|h|hpp|sh|yaml|yml|toml|xml|html|vue|svelte)/gi; +// Match file paths with optional line numbers like: /path/to/file.ts#7-14 or C:\path\to\file.ts#7 const FILE_PATH_WITH_LINES_REGEX = - /([a-zA-Z]:)?([/\\][\w\-. ]+)+\.(tsx?|jsx?|css|scss|json|md|py|java|go|rs|c|cpp|h|hpp|sh|yaml|yml|toml|xml|html|vue|svelte)#(\d+)(?:-(\d+))?/gi; + /(?:[a-zA-Z]:)?[/\\](?:[\w\-. ]+[/\\])+[\w\-. ]+\.(tsx?|jsx?|css|scss|json|md|py|java|go|rs|c|cpp|h|hpp|sh|yaml|yml|toml|xml|html|vue|svelte)#(\d+)(?:-(\d+))?/gi; /** * MarkdownRenderer component - renders markdown content with enhanced features @@ -166,9 +167,22 @@ export const MarkdownRenderer: React.FC = ({ const href = a.getAttribute('href') || ''; const text = (a.textContent || '').trim(); + // Helper function to check if a string looks like a code reference + const isCodeReference = (str: string): boolean => { + // Check if it looks like a code reference (e.g., module.property) + // Patterns like "vscode.contribution", "module.submodule.function" + const codeRefPattern = /^[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)+$/; + return codeRefPattern.test(str); + }; + // If linkify turned a bare filename into http://, convert it back const httpMatch = href.match(/^https?:\/\/(.+)$/i); if (httpMatch && BARE_FILE_REGEX.test(text) && httpMatch[1] === text) { + // Skip if it looks like a code reference + if (isCodeReference(text)) { + return; + } + // Treat as a file link instead of external URL const filePath = text; // no leading slash a.classList.add('file-path-link'); @@ -182,6 +196,12 @@ export const MarkdownRenderer: React.FC = ({ if (/^(https?|mailto|ftp|data):/i.test(href)) return; const candidate = href || text; + + // Skip if it looks like a code reference + if (isCodeReference(candidate)) { + return; + } + if ( FILE_PATH_WITH_LINES_NO_G.test(candidate) || FILE_PATH_NO_G.test(candidate) @@ -194,6 +214,14 @@ export const MarkdownRenderer: React.FC = ({ } }; + // Helper function to check if a string looks like a code reference + const isCodeReference = (str: string): boolean => { + // Check if it looks like a code reference (e.g., module.property) + // Patterns like "vscode.contribution", "module.submodule.function" + const codeRefPattern = /^[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)+$/; + return codeRefPattern.test(str); + }; + const walk = (node: Node) => { // Do not transform inside existing anchors if (node.nodeType === Node.ELEMENT_NODE) { @@ -218,6 +246,20 @@ export const MarkdownRenderer: React.FC = ({ while ((m = union.exec(text))) { const matchText = m[0]; const idx = m.index; + + // Skip if it looks like a code reference + if (isCodeReference(matchText)) { + // Just add the text as-is without creating a link + if (idx > lastIndex) { + frag.appendChild( + document.createTextNode(text.slice(lastIndex, idx)), + ); + } + frag.appendChild(document.createTextNode(matchText)); + lastIndex = idx + matchText.length; + continue; + } + if (idx > lastIndex) { frag.appendChild( document.createTextNode(text.slice(lastIndex, idx)), diff --git a/packages/vscode-ide-companion/src/webview/components/PermissionDrawer.tsx b/packages/vscode-ide-companion/src/webview/components/PermissionDrawer/PermissionDrawer.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/PermissionDrawer.tsx rename to packages/vscode-ide-companion/src/webview/components/PermissionDrawer/PermissionDrawer.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/PermissionDrawer/PermissionRequest.tsx b/packages/vscode-ide-companion/src/webview/components/PermissionDrawer/PermissionRequest.tsx new file mode 100644 index 00000000..a7b7356c --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/PermissionDrawer/PermissionRequest.tsx @@ -0,0 +1,37 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface PermissionOption { + name: string; + kind: string; + optionId: string; +} + +export interface ToolCall { + title?: string; + kind?: string; + toolCallId?: string; + rawInput?: { + command?: string; + description?: string; + [key: string]: unknown; + }; + content?: Array<{ + type: string; + [key: string]: unknown; + }>; + locations?: Array<{ + path: string; + line?: number | null; + }>; + status?: string; +} + +export interface PermissionRequestProps { + options: PermissionOption[]; + toolCall: ToolCall; + onResponse: (optionId: string) => void; +} diff --git a/packages/vscode-ide-companion/src/webview/components/PermissionRequest.tsx b/packages/vscode-ide-companion/src/webview/components/PermissionRequest.tsx deleted file mode 100644 index 78016e06..00000000 --- a/packages/vscode-ide-companion/src/webview/components/PermissionRequest.tsx +++ /dev/null @@ -1,227 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -export interface PermissionOption { - name: string; - kind: string; - optionId: string; -} - -export interface ToolCall { - title?: string; - kind?: string; - toolCallId?: string; - rawInput?: { - command?: string; - description?: string; - [key: string]: unknown; - }; - content?: Array<{ - type: string; - [key: string]: unknown; - }>; - locations?: Array<{ - path: string; - line?: number | null; - }>; - status?: string; -} - -export interface PermissionRequestProps { - options: PermissionOption[]; - toolCall: ToolCall; - onResponse: (optionId: string) => void; -} - -// export const PermissionRequest: React.FC = ({ -// options, -// toolCall, -// onResponse, -// }) => { -// const [selected, setSelected] = useState(null); -// const [isResponding, setIsResponding] = useState(false); -// const [hasResponded, setHasResponded] = useState(false); - -// const getToolInfo = () => { -// if (!toolCall) { -// return { -// title: 'Permission Request', -// description: 'Agent is requesting permission', -// icon: '🔐', -// }; -// } - -// const displayTitle = -// toolCall.title || toolCall.rawInput?.description || 'Permission Request'; - -// const kindIcons: Record = { -// edit: '✏️', -// read: '📖', -// fetch: '🌐', -// execute: '⚡', -// delete: '🗑️', -// move: '📦', -// search: '🔍', -// think: '💭', -// other: '🔧', -// }; - -// return { -// title: displayTitle, -// icon: kindIcons[toolCall.kind || 'other'] || '🔧', -// }; -// }; - -// const { title, icon } = getToolInfo(); - -// const handleConfirm = async () => { -// if (hasResponded || !selected) { -// return; -// } - -// setIsResponding(true); -// try { -// await onResponse(selected); -// setHasResponded(true); -// } catch (error) { -// console.error('Error confirming permission:', error); -// } finally { -// setIsResponding(false); -// } -// }; - -// if (!toolCall) { -// return null; -// } - -// return ( -//
-//
-// {/* Header with icon and title */} -//
-//
-// {icon} -//
-//
-//
{title}
-//
Waiting for your approval
-//
-//
- -// {/* Show command if available */} -// {(toolCall.rawInput?.command || toolCall.title) && ( -//
-//
-//
-// -// COMMAND -//
-//
-//
-//
-// IN -// -// {toolCall.rawInput?.command || toolCall.title} -// -//
-// {toolCall.rawInput?.description && ( -//
-// {toolCall.rawInput.description} -//
-// )} -//
-//
-// )} - -// {/* Show file locations if available */} -// {toolCall.locations && toolCall.locations.length > 0 && ( -//
-//
Affected Files
-// {toolCall.locations.map((location, index) => ( -//
-// 📄 -// -// {location.path} -// -// {location.line !== null && location.line !== undefined && ( -// -// ::{location.line} -// -// )} -//
-// ))} -//
-// )} - -// {/* Options */} -// {!hasResponded && ( -//
-//
Choose an action:
-//
-// {options && options.length > 0 ? ( -// options.map((option, index) => { -// const isSelected = selected === option.optionId; -// const isAllow = option.kind.includes('allow'); -// const isAlways = option.kind.includes('always'); - -// return ( -// -// ); -// }) -// ) : ( -//
-// No options available -//
-// )} -//
-//
-// -//
-//
-// )} - -// {/* Success message */} -// {hasResponded && ( -//
-// -// -// Response sent successfully -// -//
-// )} -//
-//
-// ); -// }; diff --git a/packages/vscode-ide-companion/src/webview/components/PlanDisplay.tsx b/packages/vscode-ide-companion/src/webview/components/PlanDisplay.tsx deleted file mode 100644 index 046e4d8a..00000000 --- a/packages/vscode-ide-companion/src/webview/components/PlanDisplay.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -import type React from 'react'; -import { CheckboxDisplay } from './ui/CheckboxDisplay.js'; - -export interface PlanEntry { - content: string; - priority: 'high' | 'medium' | 'low'; - status: 'pending' | 'in_progress' | 'completed'; -} - -interface PlanDisplayProps { - entries: PlanEntry[]; -} - -/** - * PlanDisplay component - displays AI's task plan/todo list - */ -export const PlanDisplay: React.FC = ({ entries }) => { - // Calculate overall status for left dot color - const allCompleted = - entries.length > 0 && entries.every((e) => e.status === 'completed'); - const anyInProgress = entries.some((e) => e.status === 'in_progress'); - const statusDotClass = allCompleted - ? 'before:text-[#74c991]' - : anyInProgress - ? 'before:text-[#e1c08d]' - : 'before:text-[var(--app-secondary-foreground)]'; - - return ( -
- {/* Title area, similar to example summary/_e/or */} -
-
-
- -
- Update Todos -
-
-
-
-
- - {/* List area, similar to example .qr/.Fr/.Hr */} -
-
    - {entries.map((entry, index) => { - const isDone = entry.status === 'completed'; - const isIndeterminate = entry.status === 'in_progress'; - return ( -
  • - {/* Display checkbox (reusable component) */} - - -
    - {entry.content} -
    -
  • - ); - })} -
-
-
- ); -}; diff --git a/packages/vscode-ide-companion/src/webview/components/messages/UserMessage.tsx b/packages/vscode-ide-companion/src/webview/components/messages/UserMessage.tsx index 69086197..102b2756 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/UserMessage.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/UserMessage.tsx @@ -67,7 +67,7 @@ export const UserMessage: React.FC = ({
fileContext && onFileClick?.(fileContext.filePath)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { diff --git a/packages/vscode-ide-companion/src/webview/components/messages/index.tsx b/packages/vscode-ide-companion/src/webview/components/messages/index.tsx index 33bfe6c7..a8c7224a 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/index.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/index.tsx @@ -10,4 +10,3 @@ export { ThinkingMessage } from './ThinkingMessage.js'; export { StreamingMessage } from './StreamingMessage.js'; export { WaitingMessage } from './Waiting/WaitingMessage.js'; export { InterruptedMessage } from './Waiting/InterruptedMessage.js'; -export { PlanDisplay } from '../PlanDisplay.js'; diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/Edit/EditToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/Edit/EditToolCall.tsx index 543bc745..29d6b762 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/Edit/EditToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/Edit/EditToolCall.tsx @@ -16,6 +16,7 @@ import { import { useVSCode } from '../../../hooks/useVSCode.js'; import { FileLink } from '../../ui/FileLink.js'; import { handleOpenDiff } from '../../../utils/diffUtils.js'; +import { DiffDisplay } from '../shared/DiffDisplay.js'; /** * Calculate diff summary (added/removed lines) @@ -85,6 +86,64 @@ export const EditToolCall: React.FC = ({ toolCall }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [toolCallId]); + // Failed case: show explicit failed message and render inline diffs + if (toolCall.status === 'failed') { + const firstDiff = diffs[0]; + const path = firstDiff?.path || locations?.[0]?.path || ''; + const containerStatus = mapToolStatusToContainerStatus(toolCall.status); + return ( +
+
+
+
+ + Edit + + {path && ( + + )} +
+
+ {/* Failed state text (replace summary) */} +
+ edit failed +
+ {/* Inline diff preview(s) */} + {diffs.length > 0 && ( +
+ {diffs.map( + ( + item: import('../shared/types.js').ToolCallContent, + idx: number, + ) => ( + + handleOpenDiffInternal( + item.path || path, + item.oldText, + item.newText, + ) + } + /> + ), + )} +
+ )} +
+
+ ); + } + // Error case: show error if (errors.length > 0) { const path = diffs[0]?.path || locations?.[0]?.path || ''; @@ -99,7 +158,7 @@ export const EditToolCall: React.FC = ({ toolCall }) => { ) : undefined } @@ -118,21 +177,19 @@ export const EditToolCall: React.FC = ({ toolCall }) => { return (
- {/* IMPORTANT: Always include min-w-0/max-w-full on inner wrappers to prevent overflow. */}
-
+
{/* Align the inline Edit label styling with shared toolcall label: larger + bold */} - + Edit {path && ( )}
diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/Read/ReadToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/Read/ReadToolCall.tsx index 56a6aafd..3892bc00 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/Read/ReadToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/Read/ReadToolCall.tsx @@ -101,7 +101,7 @@ export const ReadToolCall: React.FC = ({ toolCall }) => { return ( = ({ toolCall }) => { return ( { diff --git a/packages/vscode-ide-companion/src/webview/components/ui/CheckboxDisplay.tsx b/packages/vscode-ide-companion/src/webview/components/ui/CheckboxDisplay.tsx index 243abec4..fe413a61 100644 --- a/packages/vscode-ide-companion/src/webview/components/ui/CheckboxDisplay.tsx +++ b/packages/vscode-ide-companion/src/webview/components/ui/CheckboxDisplay.tsx @@ -71,9 +71,9 @@ export const CheckboxDisplay: React.FC = ({ aria-hidden className={[ 'absolute inline-block', - 'left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2', + 'left-1/2 top-10px -translate-x-1/2 -translate-y-1/2', // Use a literal star; no icon font needed - 'text-[11px] leading-none text-[#e1c08d] select-none', + 'text-[16px] leading-none text-[#e1c08d] select-none', ].join(' ')} > * diff --git a/packages/vscode-ide-companion/src/webview/handlers/AuthMessageHandler.ts b/packages/vscode-ide-companion/src/webview/handlers/AuthMessageHandler.ts index dce9aed6..ab4b70b2 100644 --- a/packages/vscode-ide-companion/src/webview/handlers/AuthMessageHandler.ts +++ b/packages/vscode-ide-companion/src/webview/handlers/AuthMessageHandler.ts @@ -64,7 +64,7 @@ export class AuthMessageHandler extends BaseMessageHandler { vscode.window.showInformationMessage( 'Please wait while we connect to Qwen Code...', ); - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } catch (error) { console.error('[AuthMessageHandler] Login failed:', error); diff --git a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts index dfd0fd75..ccde5337 100644 --- a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts +++ b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts @@ -280,7 +280,7 @@ export class SessionMessageHandler extends BaseMessageHandler { vscode.window.showInformationMessage( 'Please wait while we connect to Qwen Code...', ); - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } return; @@ -306,7 +306,7 @@ export class SessionMessageHandler extends BaseMessageHandler { vscode.window.showInformationMessage( 'Please wait while we connect to Qwen Code...', ); - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } return; @@ -420,7 +420,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } @@ -456,7 +456,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } else { return; @@ -514,7 +514,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } @@ -551,7 +551,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } else if (selection === 'View Offline') { // Show messages from local cache only @@ -653,7 +653,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } @@ -709,7 +709,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } @@ -757,7 +757,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } @@ -807,7 +807,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } @@ -870,7 +870,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } @@ -917,7 +917,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } @@ -983,7 +983,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } else if (selection === 'View Offline') { const messages = @@ -1034,7 +1034,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } @@ -1084,7 +1084,7 @@ export class SessionMessageHandler extends BaseMessageHandler { if (this.loginHandler) { await this.loginHandler(); } else { - await vscode.commands.executeCommand('qwenCode.login'); + await vscode.commands.executeCommand('qwen-code.login'); } } diff --git a/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts b/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts index 4cb3eb78..53ed7468 100644 --- a/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts +++ b/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts @@ -10,9 +10,9 @@ import type { Conversation } from '../../storage/conversationStore.js'; import type { PermissionOption, ToolCall as PermissionToolCall, -} from '../components/PermissionRequest.js'; -import type { PlanEntry } from '../components/PlanDisplay.js'; +} from '../components/PermissionDrawer/PermissionRequest.js'; import type { ToolCallUpdate } from '../types/toolCall.js'; +import type { PlanEntry } from '../../agents/qwenTypes.js'; interface UseWebViewMessagesProps { // Session management diff --git a/packages/vscode-ide-companion/tailwind.config.js b/packages/vscode-ide-companion/tailwind.config.js index 72ea91b9..26e28794 100644 --- a/packages/vscode-ide-companion/tailwind.config.js +++ b/packages/vscode-ide-companion/tailwind.config.js @@ -9,17 +9,17 @@ export default { content: [ // Progressive adoption strategy: Only scan newly created Tailwind components - // './src/webview/App.tsx', + './src/webview/App.tsx', './src/webview/**/*.{js,jsx,ts,tsx}', - // './src/webview/components/messages/**/*.{js,jsx,ts,tsx}', - // './src/webview/components/toolcalls/**/*.{js,jsx,ts,tsx}', - // './src/webview/components/InProgressToolCall.tsx', - // './src/webview/components/MessageContent.tsx', - // './src/webview/components/InputForm.tsx', - // './src/webview/components/PermissionDrawer.tsx', - // './src/webview/components/PlanDisplay.tsx', - // './src/webview/components/session/SessionSelector.tsx', - // './src/webview/components/messages/UserMessage.tsx', + './src/webview/components/messages/**/*.{js,jsx,ts,tsx}', + './src/webview/components/toolcalls/**/*.{js,jsx,ts,tsx}', + './src/webview/components/InProgressToolCall.tsx', + './src/webview/components/MessageContent.tsx', + './src/webview/components/InputForm.tsx', + './src/webview/components/PermissionDrawer.tsx', + './src/webview/components/PlanDisplay.tsx', + './src/webview/components/session/SessionSelector.tsx', + './src/webview/components/messages/UserMessage.tsx', ], theme: { extend: {