mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
chore(vscode-ide-companion): code style & command register bugfix
This commit is contained in:
@@ -56,17 +56,17 @@
|
|||||||
"title": "Qwen Code: View Third-Party Notices"
|
"title": "Qwen Code: View Third-Party Notices"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "qwenCode.openChat",
|
"command": "qwen-code.openChat",
|
||||||
"title": "Qwen Code: Open Chat",
|
"title": "Qwen Code: Open Chat",
|
||||||
"icon": "./assets/icon.png"
|
"icon": "./assets/icon.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "qwenCode.clearAuthCache",
|
"command": "qwen-code.login",
|
||||||
"title": "Qwen Code: Clear Authentication Cache"
|
"title": "Qwen Code: Login"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "qwenCode.login",
|
"command": "qwen-code.clearAuthCache",
|
||||||
"title": "Qwen Code: Login"
|
"title": "Qwen Code: Clear Authentication Cache"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configuration": {
|
"configuration": {
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"when": "qwen.diff.isVisible"
|
"when": "qwen.diff.isVisible"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "qwenCode.login",
|
"command": "qwen-code.login",
|
||||||
"when": "false"
|
"when": "false"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
"group": "navigation"
|
"group": "navigation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "qwenCode.openChat",
|
"command": "qwen-code.openChat",
|
||||||
"group": "navigation"
|
"group": "navigation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
"when": "qwen.diff.isVisible"
|
"when": "qwen.diff.isVisible"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "qwenCode.openChat",
|
"command": "qwen-code.openChat",
|
||||||
"key": "ctrl+shift+a",
|
"key": "ctrl+shift+a",
|
||||||
"mac": "cmd+shift+a"
|
"mac": "cmd+shift+a"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,23 +26,6 @@ import { determineNodePathForCli } from '../cli/cliPathDetector.js';
|
|||||||
* ACP Connection Handler for VSCode Extension
|
* ACP Connection Handler for VSCode Extension
|
||||||
*
|
*
|
||||||
* This class implements the client side of the ACP (Agent Communication Protocol).
|
* 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 {
|
export class AcpConnection {
|
||||||
private child: ChildProcess | null = null;
|
private child: ChildProcess | null = null;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export interface PlanEntry {
|
|||||||
/** Entry content */
|
/** Entry content */
|
||||||
content: string;
|
content: string;
|
||||||
/** Priority */
|
/** Priority */
|
||||||
priority: 'high' | 'medium' | 'low';
|
priority?: 'high' | 'medium' | 'low';
|
||||||
/** Status */
|
/** Status */
|
||||||
status: 'pending' | 'in_progress' | 'completed';
|
status: 'pending' | 'in_progress' | 'completed';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
|
|
||||||
import { CliDetector, type CliDetectionResult } from './cliDetector.js';
|
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';
|
export const MIN_CLI_VERSION_FOR_SESSION_METHODS = '0.4.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,8 +4,12 @@ import type { WebViewProvider } from '../webview/WebViewProvider.js';
|
|||||||
|
|
||||||
type Logger = (message: string) => void;
|
type Logger = (message: string) => void;
|
||||||
|
|
||||||
|
export const runQwenCodeCommand = 'qwen-code.runQwenCode';
|
||||||
export const showDiffCommand = 'qwenCode.showDiff';
|
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(
|
export function registerNewCommands(
|
||||||
context: vscode.ExtensionContext,
|
context: vscode.ExtensionContext,
|
||||||
@@ -20,15 +24,15 @@ export function registerNewCommands(
|
|||||||
vscode.commands.registerCommand(openChatCommand, async () => {
|
vscode.commands.registerCommand(openChatCommand, async () => {
|
||||||
const config = vscode.workspace.getConfiguration('qwenCode');
|
const config = vscode.workspace.getConfiguration('qwenCode');
|
||||||
const useTerminal = config.get<boolean>('useTerminal', false);
|
const useTerminal = config.get<boolean>('useTerminal', false);
|
||||||
console.log('[Command] Using terminal mode:', useTerminal);
|
|
||||||
|
// Use terminal mode
|
||||||
if (useTerminal) {
|
if (useTerminal) {
|
||||||
// 使用终端模式
|
|
||||||
await vscode.commands.executeCommand(
|
await vscode.commands.executeCommand(
|
||||||
'qwen-code.runQwenCode',
|
runQwenCodeCommand,
|
||||||
vscode.TerminalLocation.Editor, // 在编辑器区域创建终端,
|
vscode.TerminalLocation.Editor, // create a terminal in the editor area,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// 使用 WebView 模式
|
// Use WebView mode
|
||||||
const providers = getWebViewProviders();
|
const providers = getWebViewProviders();
|
||||||
if (providers.length > 0) {
|
if (providers.length > 0) {
|
||||||
await providers[providers.length - 1].show();
|
await providers[providers.length - 1].show();
|
||||||
@@ -44,7 +48,6 @@ export function registerNewCommands(
|
|||||||
vscode.commands.registerCommand(
|
vscode.commands.registerCommand(
|
||||||
showDiffCommand,
|
showDiffCommand,
|
||||||
async (args: { path: string; oldText: string; newText: string }) => {
|
async (args: { path: string; oldText: string; newText: string }) => {
|
||||||
log(`[Command] showDiff called for: ${args.path}`);
|
|
||||||
try {
|
try {
|
||||||
let absolutePath = args.path;
|
let absolutePath = args.path;
|
||||||
if (!args.path.startsWith('/') && !args.path.match(/^[a-zA-Z]:/)) {
|
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)
|
// TODO: qwenCode.openNewChatTab (not contributed in package.json; used programmatically)
|
||||||
disposables.push(
|
disposables.push(
|
||||||
vscode.commands.registerCommand('qwenCode.openNewChatTab', async () => {
|
vscode.commands.registerCommand(openNewChatTabCommand, async () => {
|
||||||
const provider = createWebViewProvider();
|
const provider = createWebViewProvider();
|
||||||
await provider.show();
|
await provider.show();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
disposables.push(
|
disposables.push(
|
||||||
vscode.commands.registerCommand('qwenCode.clearAuthCache', async () => {
|
vscode.commands.registerCommand(loginCommand, 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 () => {
|
|
||||||
const providers = getWebViewProviders();
|
const providers = getWebViewProviders();
|
||||||
if (providers.length > 0) {
|
if (providers.length > 0) {
|
||||||
await providers[providers.length - 1].forceReLogin();
|
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);
|
context.subscriptions.push(...disposables);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,6 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* 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 = {
|
export const AGENT_METHODS = {
|
||||||
authenticate: 'authenticate',
|
authenticate: 'authenticate',
|
||||||
initialize: 'initialize',
|
initialize: 'initialize',
|
||||||
@@ -35,15 +15,6 @@ export const AGENT_METHODS = {
|
|||||||
session_save: 'session/save',
|
session_save: 'session/save',
|
||||||
} as const;
|
} 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 = {
|
export const CLIENT_METHODS = {
|
||||||
fs_read_text_file: 'fs/read_text_file',
|
fs_read_text_file: 'fs/read_text_file',
|
||||||
fs_write_text_file: 'fs/write_text_file',
|
fs_write_text_file: 'fs/write_text_file',
|
||||||
|
|||||||
@@ -21,15 +21,14 @@ import { useMessageSubmit } from './hooks/useMessageSubmit.js';
|
|||||||
import type {
|
import type {
|
||||||
PermissionOption,
|
PermissionOption,
|
||||||
ToolCall as PermissionToolCall,
|
ToolCall as PermissionToolCall,
|
||||||
} from './components/PermissionRequest.js';
|
} from './components/PermissionDrawer/PermissionRequest.js';
|
||||||
import type { TextMessage } from './hooks/message/useMessageHandling.js';
|
import type { TextMessage } from './hooks/message/useMessageHandling.js';
|
||||||
import type { ToolCallData } from './components/ToolCall.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 { ToolCall } from './components/ToolCall.js';
|
||||||
import { hasToolCallOutput } from './components/toolcalls/shared/utils.js';
|
import { hasToolCallOutput } from './components/toolcalls/shared/utils.js';
|
||||||
// import { InProgressToolCall } from './components/InProgressToolCall.js';
|
// import { InProgressToolCall } from './components/InProgressToolCall.js';
|
||||||
import { EmptyState } from './components/ui/EmptyState.js';
|
import { EmptyState } from './components/ui/EmptyState.js';
|
||||||
import type { PlanEntry } from './components/PlanDisplay.js';
|
|
||||||
import { type CompletionItem } from './types/CompletionTypes.js';
|
import { type CompletionItem } from './types/CompletionTypes.js';
|
||||||
import { useCompletionTrigger } from './hooks/useCompletionTrigger.js';
|
import { useCompletionTrigger } from './hooks/useCompletionTrigger.js';
|
||||||
import { InfoBanner } from './components/ui/InfoBanner.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 { SessionSelector } from './components/session/SessionSelector.js';
|
||||||
import { FileIcon, UserIcon } from './components/icons/index.js';
|
import { FileIcon, UserIcon } from './components/icons/index.js';
|
||||||
import type { EditMode } from './types/toolCall.js';
|
import type { EditMode } from './types/toolCall.js';
|
||||||
|
import type { PlanEntry } from '../agents/qwenTypes.js';
|
||||||
|
|
||||||
export const App: React.FC = () => {
|
export const App: React.FC = () => {
|
||||||
const vscode = useVSCode();
|
const vscode = useVSCode();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { WebViewContent } from '../webview/WebViewContent.js';
|
|||||||
import { CliInstaller } from '../cli/cliInstaller.js';
|
import { CliInstaller } from '../cli/cliInstaller.js';
|
||||||
import { getFileName } from './utils/webviewUtils.js';
|
import { getFileName } from './utils/webviewUtils.js';
|
||||||
import { authMethod } from '../auth/index.js';
|
import { authMethod } from '../auth/index.js';
|
||||||
|
import { runQwenCodeCommand } from '../commands/index.js';
|
||||||
|
|
||||||
export class WebViewProvider {
|
export class WebViewProvider {
|
||||||
private panelManager: PanelManager;
|
private panelManager: PanelManager;
|
||||||
@@ -1067,7 +1068,7 @@ export class WebViewProvider {
|
|||||||
if (useTerminal) {
|
if (useTerminal) {
|
||||||
// In terminal mode, execute the runQwenCode command to open a new terminal
|
// In terminal mode, execute the runQwenCode command to open a new terminal
|
||||||
try {
|
try {
|
||||||
await vscode.commands.executeCommand('qwen-code.runQwenCode');
|
await vscode.commands.executeCommand(runQwenCodeCommand);
|
||||||
console.log('[WebViewProvider] Opened new terminal session');
|
console.log('[WebViewProvider] Opened new terminal session');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
@@ -135,7 +135,8 @@
|
|||||||
border: 1px solid var(--app-primary-border-color);
|
border: 1px solid var(--app-primary-border-color);
|
||||||
border-radius: var(--corner-radius-small, 4px);
|
border-radius: var(--corner-radius-small, 4px);
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
white-space: nowrap;
|
white-space: pre-wrap; /* 支持自动换行 */
|
||||||
|
word-break: break-word; /* 在必要时断词 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-content pre {
|
.markdown-content pre {
|
||||||
@@ -207,7 +208,8 @@
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
white-space: pre;
|
white-space: pre-wrap; /* 支持自动换行 */
|
||||||
|
word-break: break-word; /* 在必要时断词 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-content .file-path-link {
|
.markdown-content .file-path-link {
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ interface MarkdownRendererProps {
|
|||||||
/**
|
/**
|
||||||
* Regular expressions for parsing content
|
* Regular expressions for parsing content
|
||||||
*/
|
*/
|
||||||
|
// Match absolute file paths like: /path/to/file.ts or C:\path\to\file.ts
|
||||||
const FILE_PATH_REGEX =
|
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;
|
/(?:[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/file.ts#7-14 or path/file.ts#7
|
// 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 =
|
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
|
* MarkdownRenderer component - renders markdown content with enhanced features
|
||||||
@@ -166,9 +167,22 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
|||||||
const href = a.getAttribute('href') || '';
|
const href = a.getAttribute('href') || '';
|
||||||
const text = (a.textContent || '').trim();
|
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://<filename>, convert it back
|
// If linkify turned a bare filename into http://<filename>, convert it back
|
||||||
const httpMatch = href.match(/^https?:\/\/(.+)$/i);
|
const httpMatch = href.match(/^https?:\/\/(.+)$/i);
|
||||||
if (httpMatch && BARE_FILE_REGEX.test(text) && httpMatch[1] === text) {
|
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
|
// Treat as a file link instead of external URL
|
||||||
const filePath = text; // no leading slash
|
const filePath = text; // no leading slash
|
||||||
a.classList.add('file-path-link');
|
a.classList.add('file-path-link');
|
||||||
@@ -182,6 +196,12 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
|||||||
if (/^(https?|mailto|ftp|data):/i.test(href)) return;
|
if (/^(https?|mailto|ftp|data):/i.test(href)) return;
|
||||||
|
|
||||||
const candidate = href || text;
|
const candidate = href || text;
|
||||||
|
|
||||||
|
// Skip if it looks like a code reference
|
||||||
|
if (isCodeReference(candidate)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
FILE_PATH_WITH_LINES_NO_G.test(candidate) ||
|
FILE_PATH_WITH_LINES_NO_G.test(candidate) ||
|
||||||
FILE_PATH_NO_G.test(candidate)
|
FILE_PATH_NO_G.test(candidate)
|
||||||
@@ -194,6 +214,14 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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) => {
|
const walk = (node: Node) => {
|
||||||
// Do not transform inside existing anchors
|
// Do not transform inside existing anchors
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
@@ -218,6 +246,20 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
|||||||
while ((m = union.exec(text))) {
|
while ((m = union.exec(text))) {
|
||||||
const matchText = m[0];
|
const matchText = m[0];
|
||||||
const idx = m.index;
|
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) {
|
if (idx > lastIndex) {
|
||||||
frag.appendChild(
|
frag.appendChild(
|
||||||
document.createTextNode(text.slice(lastIndex, idx)),
|
document.createTextNode(text.slice(lastIndex, idx)),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<PermissionRequestProps> = ({
|
|
||||||
// options,
|
|
||||||
// toolCall,
|
|
||||||
// onResponse,
|
|
||||||
// }) => {
|
|
||||||
// const [selected, setSelected] = useState<string | null>(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<string, string> = {
|
|
||||||
// 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 (
|
|
||||||
// <div className="permission-request-card">
|
|
||||||
// <div className="permission-card-body">
|
|
||||||
// {/* Header with icon and title */}
|
|
||||||
// <div className="permission-header">
|
|
||||||
// <div className="permission-icon-wrapper">
|
|
||||||
// <span className="permission-icon">{icon}</span>
|
|
||||||
// </div>
|
|
||||||
// <div className="permission-info">
|
|
||||||
// <div className="permission-title">{title}</div>
|
|
||||||
// <div className="permission-subtitle">Waiting for your approval</div>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// {/* Show command if available */}
|
|
||||||
// {(toolCall.rawInput?.command || toolCall.title) && (
|
|
||||||
// <div className="permission-command-section">
|
|
||||||
// <div className="permission-command-header">
|
|
||||||
// <div className="permission-command-status">
|
|
||||||
// <span className="permission-command-dot">●</span>
|
|
||||||
// <span className="permission-command-label">COMMAND</span>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// <div className="permission-command-content">
|
|
||||||
// <div className="permission-command-input-section">
|
|
||||||
// <span className="permission-command-io-label">IN</span>
|
|
||||||
// <code className="permission-command-code">
|
|
||||||
// {toolCall.rawInput?.command || toolCall.title}
|
|
||||||
// </code>
|
|
||||||
// </div>
|
|
||||||
// {toolCall.rawInput?.description && (
|
|
||||||
// <div className="permission-command-description">
|
|
||||||
// {toolCall.rawInput.description}
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
|
|
||||||
// {/* Show file locations if available */}
|
|
||||||
// {toolCall.locations && toolCall.locations.length > 0 && (
|
|
||||||
// <div className="permission-locations-section">
|
|
||||||
// <div className="permission-locations-label">Affected Files</div>
|
|
||||||
// {toolCall.locations.map((location, index) => (
|
|
||||||
// <div key={index} className="permission-location-item">
|
|
||||||
// <span className="permission-location-icon">📄</span>
|
|
||||||
// <span className="permission-location-path">
|
|
||||||
// {location.path}
|
|
||||||
// </span>
|
|
||||||
// {location.line !== null && location.line !== undefined && (
|
|
||||||
// <span className="permission-location-line">
|
|
||||||
// ::{location.line}
|
|
||||||
// </span>
|
|
||||||
// )}
|
|
||||||
// </div>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
|
|
||||||
// {/* Options */}
|
|
||||||
// {!hasResponded && (
|
|
||||||
// <div className="permission-options-section">
|
|
||||||
// <div className="permission-options-label">Choose an action:</div>
|
|
||||||
// <div className="permission-options-list">
|
|
||||||
// {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 (
|
|
||||||
// <label
|
|
||||||
// key={option.optionId}
|
|
||||||
// className={`permission-option ${isSelected ? 'selected' : ''} ${
|
|
||||||
// isAllow ? 'allow' : 'reject'
|
|
||||||
// } ${isAlways ? 'always' : ''}`}
|
|
||||||
// >
|
|
||||||
// <input
|
|
||||||
// type="radio"
|
|
||||||
// name="permission"
|
|
||||||
// value={option.optionId}
|
|
||||||
// checked={isSelected}
|
|
||||||
// onChange={() => setSelected(option.optionId)}
|
|
||||||
// className="permission-radio"
|
|
||||||
// />
|
|
||||||
// <span className="permission-option-content">
|
|
||||||
// <span className="permission-option-number">
|
|
||||||
// {index + 1}
|
|
||||||
// </span>
|
|
||||||
// {isAlways && (
|
|
||||||
// <span className="permission-always-badge">⚡</span>
|
|
||||||
// )}
|
|
||||||
// {option.name}
|
|
||||||
// </span>
|
|
||||||
// </label>
|
|
||||||
// );
|
|
||||||
// })
|
|
||||||
// ) : (
|
|
||||||
// <div className="permission-no-options">
|
|
||||||
// No options available
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
// </div>
|
|
||||||
// <div className="permission-actions">
|
|
||||||
// <button
|
|
||||||
// className="permission-confirm-button"
|
|
||||||
// disabled={!selected || isResponding}
|
|
||||||
// onClick={handleConfirm}
|
|
||||||
// >
|
|
||||||
// {isResponding ? 'Processing...' : 'Confirm'}
|
|
||||||
// </button>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
|
|
||||||
// {/* Success message */}
|
|
||||||
// {hasResponded && (
|
|
||||||
// <div className="permission-success">
|
|
||||||
// <span className="permission-success-icon">✓</span>
|
|
||||||
// <span className="permission-success-text">
|
|
||||||
// Response sent successfully
|
|
||||||
// </span>
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
@@ -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<PlanDisplayProps> = ({ 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 (
|
|
||||||
<div
|
|
||||||
className={[
|
|
||||||
// Container: Similar to example .A/.e
|
|
||||||
'relative flex flex-col items-start py-2 pl-[30px] select-text text-[var(--app-primary-foreground)]',
|
|
||||||
// Left status dot, similar to example .e:before
|
|
||||||
'before:content-["\\25cf"] before:absolute before:left-[10px] before:top-[12px] before:text-[10px] before:z-[1]',
|
|
||||||
statusDotClass,
|
|
||||||
// Original plan-display styles: bg-transparent border-0 py-2 px-4 my-2
|
|
||||||
'bg-transparent border-0 my-2',
|
|
||||||
].join(' ')}
|
|
||||||
>
|
|
||||||
{/* Title area, similar to example summary/_e/or */}
|
|
||||||
<div className="w-full flex items-center gap-1.5 mb-2">
|
|
||||||
<div className="relative">
|
|
||||||
<div className="list-none line-clamp-2 max-w-full overflow-hidden _e">
|
|
||||||
<span>
|
|
||||||
<div>
|
|
||||||
<span className="or font-bold mr-1">Update Todos</span>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* List area, similar to example .qr/.Fr/.Hr */}
|
|
||||||
<div className="qr grid-cols-1 flex flex-col py-2">
|
|
||||||
<ul className="Fr list-none p-0 m-0 flex flex-col gap-1">
|
|
||||||
{entries.map((entry, index) => {
|
|
||||||
const isDone = entry.status === 'completed';
|
|
||||||
const isIndeterminate = entry.status === 'in_progress';
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
key={index}
|
|
||||||
className={[
|
|
||||||
'Hr flex items-start gap-2 p-0 rounded text-[var(--app-primary-foreground)]',
|
|
||||||
isDone ? 'fo opacity-70' : '',
|
|
||||||
].join(' ')}
|
|
||||||
>
|
|
||||||
{/* Display checkbox (reusable component) */}
|
|
||||||
<label className="flex items-start gap-2">
|
|
||||||
<CheckboxDisplay
|
|
||||||
checked={isDone}
|
|
||||||
indeterminate={isIndeterminate}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={[
|
|
||||||
'vo plan-entry-text flex-1 text-xs leading-[1.5] text-[var(--app-primary-foreground)]',
|
|
||||||
isDone
|
|
||||||
? 'line-through text-[var(--app-secondary-foreground)] opacity-70'
|
|
||||||
: 'opacity-85',
|
|
||||||
].join(' ')}
|
|
||||||
>
|
|
||||||
{entry.content}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -67,7 +67,7 @@ export const UserMessage: React.FC<UserMessageProps> = ({
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="mr inline-flex items-center py-0 pl-1 pr-2 ml-1 gap-1 rounded-sm cursor-pointer relative opacity-50 hover:opacity-100"
|
className="mr inline-flex items-center py-0 pl-1 pr-2 ml-1 gap-1 rounded-sm cursor-pointer relative opacity-50"
|
||||||
onClick={() => fileContext && onFileClick?.(fileContext.filePath)}
|
onClick={() => fileContext && onFileClick?.(fileContext.filePath)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
|||||||
@@ -10,4 +10,3 @@ export { ThinkingMessage } from './ThinkingMessage.js';
|
|||||||
export { StreamingMessage } from './StreamingMessage.js';
|
export { StreamingMessage } from './StreamingMessage.js';
|
||||||
export { WaitingMessage } from './Waiting/WaitingMessage.js';
|
export { WaitingMessage } from './Waiting/WaitingMessage.js';
|
||||||
export { InterruptedMessage } from './Waiting/InterruptedMessage.js';
|
export { InterruptedMessage } from './Waiting/InterruptedMessage.js';
|
||||||
export { PlanDisplay } from '../PlanDisplay.js';
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import { useVSCode } from '../../../hooks/useVSCode.js';
|
import { useVSCode } from '../../../hooks/useVSCode.js';
|
||||||
import { FileLink } from '../../ui/FileLink.js';
|
import { FileLink } from '../../ui/FileLink.js';
|
||||||
import { handleOpenDiff } from '../../../utils/diffUtils.js';
|
import { handleOpenDiff } from '../../../utils/diffUtils.js';
|
||||||
|
import { DiffDisplay } from '../shared/DiffDisplay.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate diff summary (added/removed lines)
|
* Calculate diff summary (added/removed lines)
|
||||||
@@ -85,6 +86,64 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [toolCallId]);
|
}, [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 (
|
||||||
|
<div
|
||||||
|
className={`qwen-message message-item relative py-2 select-text toolcall-container toolcall-status-${containerStatus}`}
|
||||||
|
>
|
||||||
|
<div className="toolcall-edit-content flex flex-col gap-1 min-w-0 max-w-full">
|
||||||
|
<div className="flex items-center justify-between min-w-0">
|
||||||
|
<div className="flex items-baseline gap-2 min-w-0">
|
||||||
|
<span className="text-[13px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
||||||
|
Edit
|
||||||
|
</span>
|
||||||
|
{path && (
|
||||||
|
<FileLink
|
||||||
|
path={path}
|
||||||
|
showFullPath={false}
|
||||||
|
className="font-mono text-[var(--app-secondary-foreground)] hover:underline"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Failed state text (replace summary) */}
|
||||||
|
<div className="inline-flex text-[var(--app-secondary-foreground)] text-[0.85em] opacity-70 flex-row items-start w-full gap-1 flex items-center">
|
||||||
|
<span className="flex-shrink-0 w-full">edit failed</span>
|
||||||
|
</div>
|
||||||
|
{/* Inline diff preview(s) */}
|
||||||
|
{diffs.length > 0 && (
|
||||||
|
<div className="flex flex-col gap-2 mt-1">
|
||||||
|
{diffs.map(
|
||||||
|
(
|
||||||
|
item: import('../shared/types.js').ToolCallContent,
|
||||||
|
idx: number,
|
||||||
|
) => (
|
||||||
|
<DiffDisplay
|
||||||
|
key={`diff-${idx}`}
|
||||||
|
path={item.path}
|
||||||
|
oldText={item.oldText}
|
||||||
|
newText={item.newText}
|
||||||
|
onOpenDiff={() =>
|
||||||
|
handleOpenDiffInternal(
|
||||||
|
item.path || path,
|
||||||
|
item.oldText,
|
||||||
|
item.newText,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Error case: show error
|
// Error case: show error
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
const path = diffs[0]?.path || locations?.[0]?.path || '';
|
const path = diffs[0]?.path || locations?.[0]?.path || '';
|
||||||
@@ -99,7 +158,7 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
<FileLink
|
<FileLink
|
||||||
path={path}
|
path={path}
|
||||||
showFullPath={false}
|
showFullPath={false}
|
||||||
className="text-xs font-mono text-[var(--app-secondary-foreground)] hover:underline"
|
className="text-xs font-mono hover:underline"
|
||||||
/>
|
/>
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
@@ -118,21 +177,19 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`qwen-message message-item relative py-2 select-text toolcall-container toolcall-status-${containerStatus}`}
|
className={`qwen-message message-item relative py-2 select-text toolcall-container toolcall-status-${containerStatus}`}
|
||||||
title="Open diff in VS Code"
|
|
||||||
>
|
>
|
||||||
{/* IMPORTANT: Always include min-w-0/max-w-full on inner wrappers to prevent overflow. */}
|
|
||||||
<div className="toolcall-edit-content flex flex-col gap-1 min-w-0 max-w-full">
|
<div className="toolcall-edit-content flex flex-col gap-1 min-w-0 max-w-full">
|
||||||
<div className="flex items-center justify-between min-w-0">
|
<div className="flex items-center justify-between min-w-0">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-baseline gap-2 min-w-0">
|
||||||
{/* Align the inline Edit label styling with shared toolcall label: larger + bold */}
|
{/* Align the inline Edit label styling with shared toolcall label: larger + bold */}
|
||||||
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
<span className="text-[13px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
||||||
Edit
|
Edit
|
||||||
</span>
|
</span>
|
||||||
{path && (
|
{path && (
|
||||||
<FileLink
|
<FileLink
|
||||||
path={path}
|
path={path}
|
||||||
showFullPath={false}
|
showFullPath={false}
|
||||||
className="text-xs font-mono text-[var(--app-secondary-foreground)] hover:underline"
|
className="font-mono text-[var(--app-secondary-foreground)] hover:underline"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export const ReadToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
return (
|
return (
|
||||||
<ToolCallContainer
|
<ToolCallContainer
|
||||||
label={'Read'}
|
label={'Read'}
|
||||||
className="read-tool-call-success"
|
className={`read-tool-call-${containerStatus}`}
|
||||||
status={containerStatus}
|
status={containerStatus}
|
||||||
toolCallId={toolCallId}
|
toolCallId={toolCallId}
|
||||||
labelSuffix={
|
labelSuffix={
|
||||||
@@ -125,7 +125,7 @@ export const ReadToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
return (
|
return (
|
||||||
<ToolCallContainer
|
<ToolCallContainer
|
||||||
label={'Read'}
|
label={'Read'}
|
||||||
className="read-tool-call-success"
|
className={`read-tool-call-${containerStatus}`}
|
||||||
status={containerStatus}
|
status={containerStatus}
|
||||||
toolCallId={toolCallId}
|
toolCallId={toolCallId}
|
||||||
labelSuffix={
|
labelSuffix={
|
||||||
|
|||||||
@@ -11,14 +11,10 @@ import type { BaseToolCallProps } from '../shared/types.js';
|
|||||||
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
||||||
import { groupContent, safeTitle } from '../shared/utils.js';
|
import { groupContent, safeTitle } from '../shared/utils.js';
|
||||||
import { CheckboxDisplay } from '../../ui/CheckboxDisplay.js';
|
import { CheckboxDisplay } from '../../ui/CheckboxDisplay.js';
|
||||||
|
import type { PlanEntry } from '../../../../agents/qwenTypes.js';
|
||||||
|
|
||||||
type EntryStatus = 'pending' | 'in_progress' | 'completed';
|
type EntryStatus = 'pending' | 'in_progress' | 'completed';
|
||||||
|
|
||||||
interface PlanEntry {
|
|
||||||
content: string;
|
|
||||||
status: EntryStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapToolStatusToBullet = (
|
const mapToolStatusToBullet = (
|
||||||
status: import('../shared/types.js').ToolCallStatus,
|
status: import('../shared/types.js').ToolCallStatus,
|
||||||
): 'success' | 'error' | 'warning' | 'loading' | 'default' => {
|
): 'success' | 'error' | 'warning' | 'loading' | 'default' => {
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ export const CheckboxDisplay: React.FC<CheckboxDisplayProps> = ({
|
|||||||
aria-hidden
|
aria-hidden
|
||||||
className={[
|
className={[
|
||||||
'absolute inline-block',
|
'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
|
// 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(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class AuthMessageHandler extends BaseMessageHandler {
|
|||||||
vscode.window.showInformationMessage(
|
vscode.window.showInformationMessage(
|
||||||
'Please wait while we connect to Qwen Code...',
|
'Please wait while we connect to Qwen Code...',
|
||||||
);
|
);
|
||||||
await vscode.commands.executeCommand('qwenCode.login');
|
await vscode.commands.executeCommand('qwen-code.login');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AuthMessageHandler] Login failed:', error);
|
console.error('[AuthMessageHandler] Login failed:', error);
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
vscode.window.showInformationMessage(
|
vscode.window.showInformationMessage(
|
||||||
'Please wait while we connect to Qwen Code...',
|
'Please wait while we connect to Qwen Code...',
|
||||||
);
|
);
|
||||||
await vscode.commands.executeCommand('qwenCode.login');
|
await vscode.commands.executeCommand('qwen-code.login');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -306,7 +306,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
vscode.window.showInformationMessage(
|
vscode.window.showInformationMessage(
|
||||||
'Please wait while we connect to Qwen Code...',
|
'Please wait while we connect to Qwen Code...',
|
||||||
);
|
);
|
||||||
await vscode.commands.executeCommand('qwenCode.login');
|
await vscode.commands.executeCommand('qwen-code.login');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -420,7 +420,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
if (this.loginHandler) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} 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) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} else {
|
||||||
await vscode.commands.executeCommand('qwenCode.login');
|
await vscode.commands.executeCommand('qwen-code.login');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
@@ -514,7 +514,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
if (this.loginHandler) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} 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) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} else {
|
||||||
await vscode.commands.executeCommand('qwenCode.login');
|
await vscode.commands.executeCommand('qwen-code.login');
|
||||||
}
|
}
|
||||||
} else if (selection === 'View Offline') {
|
} else if (selection === 'View Offline') {
|
||||||
// Show messages from local cache only
|
// Show messages from local cache only
|
||||||
@@ -653,7 +653,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
if (this.loginHandler) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} 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) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} 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) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} 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) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} 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) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} 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) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} 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) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} else {
|
||||||
await vscode.commands.executeCommand('qwenCode.login');
|
await vscode.commands.executeCommand('qwen-code.login');
|
||||||
}
|
}
|
||||||
} else if (selection === 'View Offline') {
|
} else if (selection === 'View Offline') {
|
||||||
const messages =
|
const messages =
|
||||||
@@ -1034,7 +1034,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
if (this.loginHandler) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} 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) {
|
if (this.loginHandler) {
|
||||||
await this.loginHandler();
|
await this.loginHandler();
|
||||||
} else {
|
} else {
|
||||||
await vscode.commands.executeCommand('qwenCode.login');
|
await vscode.commands.executeCommand('qwen-code.login');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import type { Conversation } from '../../storage/conversationStore.js';
|
|||||||
import type {
|
import type {
|
||||||
PermissionOption,
|
PermissionOption,
|
||||||
ToolCall as PermissionToolCall,
|
ToolCall as PermissionToolCall,
|
||||||
} from '../components/PermissionRequest.js';
|
} from '../components/PermissionDrawer/PermissionRequest.js';
|
||||||
import type { PlanEntry } from '../components/PlanDisplay.js';
|
|
||||||
import type { ToolCallUpdate } from '../types/toolCall.js';
|
import type { ToolCallUpdate } from '../types/toolCall.js';
|
||||||
|
import type { PlanEntry } from '../../agents/qwenTypes.js';
|
||||||
|
|
||||||
interface UseWebViewMessagesProps {
|
interface UseWebViewMessagesProps {
|
||||||
// Session management
|
// Session management
|
||||||
|
|||||||
@@ -9,17 +9,17 @@
|
|||||||
export default {
|
export default {
|
||||||
content: [
|
content: [
|
||||||
// Progressive adoption strategy: Only scan newly created Tailwind components
|
// 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/**/*.{js,jsx,ts,tsx}',
|
||||||
// './src/webview/components/messages/**/*.{js,jsx,ts,tsx}',
|
'./src/webview/components/messages/**/*.{js,jsx,ts,tsx}',
|
||||||
// './src/webview/components/toolcalls/**/*.{js,jsx,ts,tsx}',
|
'./src/webview/components/toolcalls/**/*.{js,jsx,ts,tsx}',
|
||||||
// './src/webview/components/InProgressToolCall.tsx',
|
'./src/webview/components/InProgressToolCall.tsx',
|
||||||
// './src/webview/components/MessageContent.tsx',
|
'./src/webview/components/MessageContent.tsx',
|
||||||
// './src/webview/components/InputForm.tsx',
|
'./src/webview/components/InputForm.tsx',
|
||||||
// './src/webview/components/PermissionDrawer.tsx',
|
'./src/webview/components/PermissionDrawer.tsx',
|
||||||
// './src/webview/components/PlanDisplay.tsx',
|
'./src/webview/components/PlanDisplay.tsx',
|
||||||
// './src/webview/components/session/SessionSelector.tsx',
|
'./src/webview/components/session/SessionSelector.tsx',
|
||||||
// './src/webview/components/messages/UserMessage.tsx',
|
'./src/webview/components/messages/UserMessage.tsx',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|||||||
Reference in New Issue
Block a user