mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat(vscode-ide-companion): 0.2.4 版本ACP 协议检测和实现
- 新增 session/list 方法支持 - 改进 session/load 方法兼容性 - 优化代理环境变量设置 - 调整 CLI 安装流程 - 移除未使用的随机加载消息功能
This commit is contained in:
@@ -198,7 +198,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||||
"@qwen-code/qwen-code-core": "file:../core",
|
"@qwen-code/qwen-code-core": "file:../core",
|
||||||
"@qwen-code/qwen-code": "file:../cli",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ import { AcpSessionManager } from './acpSessionManager.js';
|
|||||||
* ✅ session/cancel - Cancel current generation
|
* ✅ session/cancel - Cancel current generation
|
||||||
* ✅ session/load - Load previous session
|
* ✅ session/load - Load previous session
|
||||||
* ✅ session/save - Save current session
|
* ✅ session/save - Save current session
|
||||||
*
|
|
||||||
* Custom Methods (Not in standard ACP):
|
|
||||||
* ⚠️ session/list - List available sessions (custom extension)
|
|
||||||
*/
|
*/
|
||||||
export class AcpConnection {
|
export class AcpConnection {
|
||||||
private child: ChildProcess | null = null;
|
private child: ChildProcess | null = null;
|
||||||
@@ -101,10 +98,10 @@ export class AcpConnection {
|
|||||||
const proxyUrl = extraArgs[proxyIndex + 1];
|
const proxyUrl = extraArgs[proxyIndex + 1];
|
||||||
console.log('[ACP] Setting proxy environment variables:', proxyUrl);
|
console.log('[ACP] Setting proxy environment variables:', proxyUrl);
|
||||||
|
|
||||||
env.HTTP_PROXY = proxyUrl;
|
env['HTTP_PROXY'] = proxyUrl;
|
||||||
env.HTTPS_PROXY = proxyUrl;
|
env['HTTPS_PROXY'] = proxyUrl;
|
||||||
env.http_proxy = proxyUrl;
|
env['http_proxy'] = proxyUrl;
|
||||||
env.https_proxy = proxyUrl;
|
env['https_proxy'] = proxyUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
let spawnCommand: string;
|
let spawnCommand: string;
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
* ✅ initialize - Protocol initialization
|
* ✅ initialize - Protocol initialization
|
||||||
* ✅ authenticate - User authentication
|
* ✅ authenticate - User authentication
|
||||||
* ✅ session/new - Create new session
|
* ✅ session/new - Create new session
|
||||||
* ✅ session/load - Load existing 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/prompt - Send user message to agent
|
||||||
* ✅ session/cancel - Cancel current generation
|
* ✅ session/cancel - Cancel current generation
|
||||||
* ✅ session/save - Save current session
|
* ✅ session/save - Save current session
|
||||||
@@ -27,6 +28,7 @@ export const AGENT_METHODS = {
|
|||||||
authenticate: 'authenticate',
|
authenticate: 'authenticate',
|
||||||
initialize: 'initialize',
|
initialize: 'initialize',
|
||||||
session_cancel: 'session/cancel',
|
session_cancel: 'session/cancel',
|
||||||
|
session_list: 'session/list',
|
||||||
session_load: 'session/load',
|
session_load: 'session/load',
|
||||||
session_new: 'session/new',
|
session_new: 'session/new',
|
||||||
session_prompt: 'session/prompt',
|
session_prompt: 'session/prompt',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import type {
|
|||||||
} from './qwenTypes.js';
|
} from './qwenTypes.js';
|
||||||
import { QwenConnectionHandler } from './qwenConnectionHandler.js';
|
import { QwenConnectionHandler } from './qwenConnectionHandler.js';
|
||||||
import { QwenSessionUpdateHandler } from './qwenSessionUpdateHandler.js';
|
import { QwenSessionUpdateHandler } from './qwenSessionUpdateHandler.js';
|
||||||
|
import { CliContextManager } from '../cli/cliContextManager.js';
|
||||||
|
|
||||||
export type { ChatMessage, PlanEntry, ToolCallUpdateData };
|
export type { ChatMessage, PlanEntry, ToolCallUpdateData };
|
||||||
|
|
||||||
@@ -137,14 +138,64 @@ export class QwenAgentManager {
|
|||||||
* @returns Session list
|
* @returns Session list
|
||||||
*/
|
*/
|
||||||
async getSessionList(): Promise<Array<Record<string, unknown>>> {
|
async getSessionList(): Promise<Array<Record<string, unknown>>> {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Getting session list with version-aware strategy',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if CLI supports session/list method
|
||||||
|
const cliContextManager = CliContextManager.getInstance();
|
||||||
|
const supportsSessionList = cliContextManager.supportsSessionList();
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] CLI supports session/list:',
|
||||||
|
supportsSessionList,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try ACP method first if supported
|
||||||
|
if (supportsSessionList) {
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Attempting to get session list via ACP method',
|
||||||
|
);
|
||||||
|
const response = await this.connection.listSessions();
|
||||||
|
console.log('[QwenAgentManager] ACP session list response:', response);
|
||||||
|
|
||||||
|
if (response.result && Array.isArray(response.result)) {
|
||||||
|
const sessions = response.result.map((session) => ({
|
||||||
|
id: session.sessionId || session.id,
|
||||||
|
sessionId: session.sessionId || session.id,
|
||||||
|
title: session.title || session.name || 'Untitled Session',
|
||||||
|
name: session.title || session.name || 'Untitled Session',
|
||||||
|
startTime: session.startTime,
|
||||||
|
lastUpdated: session.lastUpdated,
|
||||||
|
messageCount: session.messageCount || 0,
|
||||||
|
projectHash: session.projectHash,
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Sessions retrieved via ACP:',
|
||||||
|
sessions.length,
|
||||||
|
);
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
'[QwenAgentManager] ACP session list failed, falling back to file system method:',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always fall back to file system method
|
||||||
try {
|
try {
|
||||||
|
console.log('[QwenAgentManager] Getting session list from file system');
|
||||||
const sessions = await this.sessionReader.getAllSessions(undefined, true);
|
const sessions = await this.sessionReader.getAllSessions(undefined, true);
|
||||||
console.log(
|
console.log(
|
||||||
'[QwenAgentManager] Session list from files (all projects):',
|
'[QwenAgentManager] Session list from file system (all projects):',
|
||||||
sessions.length,
|
sessions.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
return sessions.map(
|
const result = sessions.map(
|
||||||
(session: QwenSession): Record<string, unknown> => ({
|
(session: QwenSession): Record<string, unknown> => ({
|
||||||
id: session.sessionId,
|
id: session.sessionId,
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
@@ -156,8 +207,17 @@ export class QwenAgentManager {
|
|||||||
projectHash: session.projectHash,
|
projectHash: session.projectHash,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Sessions retrieved from file system:',
|
||||||
|
result.length,
|
||||||
|
);
|
||||||
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[QwenAgentManager] Failed to get session list:', error);
|
console.error(
|
||||||
|
'[QwenAgentManager] Failed to get session list from file system:',
|
||||||
|
error,
|
||||||
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,12 +430,22 @@ export class QwenAgentManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to load session via ACP session/load method
|
* Try to load session via ACP session/load method
|
||||||
* This is a test method to verify if CLI supports session/load
|
* This method will only be used if CLI version supports it
|
||||||
*
|
*
|
||||||
* @param sessionId - Session ID
|
* @param sessionId - Session ID
|
||||||
* @returns Load response or error
|
* @returns Load response or error
|
||||||
*/
|
*/
|
||||||
async loadSessionViaAcp(sessionId: string): Promise<unknown> {
|
async loadSessionViaAcp(sessionId: string): Promise<unknown> {
|
||||||
|
// Check if CLI supports session/load method
|
||||||
|
const cliContextManager = CliContextManager.getInstance();
|
||||||
|
const supportsSessionLoad = cliContextManager.supportsSessionLoad();
|
||||||
|
|
||||||
|
if (!supportsSessionLoad) {
|
||||||
|
throw new Error(
|
||||||
|
`CLI version does not support session/load method. Please upgrade to version 0.2.4 or later.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(
|
console.log(
|
||||||
'[QwenAgentManager] Attempting session/load via ACP for session:',
|
'[QwenAgentManager] Attempting session/load via ACP for session:',
|
||||||
@@ -419,23 +489,91 @@ export class QwenAgentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load session directly from file system (without relying on ACP)
|
* Load session with version-aware strategy
|
||||||
|
* First tries ACP method if CLI version supports it, falls back to file system method
|
||||||
*
|
*
|
||||||
* @param sessionId - Session ID
|
* @param sessionId - Session ID to load
|
||||||
* @returns Loaded session messages or null
|
* @returns Loaded session messages or null
|
||||||
*/
|
*/
|
||||||
async loadSessionDirect(sessionId: string): Promise<ChatMessage[] | null> {
|
async loadSession(sessionId: string): Promise<ChatMessage[] | null> {
|
||||||
try {
|
console.log(
|
||||||
console.log('[QwenAgentManager] Loading session directly:', sessionId);
|
'[QwenAgentManager] Loading session with version-aware strategy:',
|
||||||
|
sessionId,
|
||||||
|
);
|
||||||
|
|
||||||
// Load session
|
// Check if CLI supports session/load method
|
||||||
|
const cliContextManager = CliContextManager.getInstance();
|
||||||
|
const supportsSessionLoad = cliContextManager.supportsSessionLoad();
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] CLI supports session/load:',
|
||||||
|
supportsSessionLoad,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try ACP method first if supported
|
||||||
|
if (supportsSessionLoad) {
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Attempting to load session via ACP method',
|
||||||
|
);
|
||||||
|
await this.loadSessionViaAcp(sessionId);
|
||||||
|
console.log('[QwenAgentManager] Session loaded successfully via ACP');
|
||||||
|
|
||||||
|
// After loading via ACP, we still need to get messages from file system
|
||||||
|
// In future, we might get them directly from the ACP response
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
'[QwenAgentManager] ACP session load failed, falling back to file system method:',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always fall back to file system method
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Loading session messages from file system',
|
||||||
|
);
|
||||||
|
const messages = await this.loadSessionMessagesFromFile(sessionId);
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Session messages loaded successfully from file system',
|
||||||
|
);
|
||||||
|
return messages;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'[QwenAgentManager] Failed to load session messages from file system:',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load session messages from file system
|
||||||
|
*
|
||||||
|
* @param sessionId - Session ID to load
|
||||||
|
* @returns Loaded session messages
|
||||||
|
*/
|
||||||
|
private async loadSessionMessagesFromFile(
|
||||||
|
sessionId: string,
|
||||||
|
): Promise<ChatMessage[] | null> {
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Loading session from file system:',
|
||||||
|
sessionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load session from file system
|
||||||
const session = await this.sessionManager.loadSession(
|
const session = await this.sessionManager.loadSession(
|
||||||
sessionId,
|
sessionId,
|
||||||
this.currentWorkingDir,
|
this.currentWorkingDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
console.log('[QwenAgentManager] Session not found:', sessionId);
|
console.log(
|
||||||
|
'[QwenAgentManager] Session not found in file system:',
|
||||||
|
sessionId,
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,14 +584,26 @@ export class QwenAgentManager {
|
|||||||
timestamp: new Date(msg.timestamp).getTime(),
|
timestamp: new Date(msg.timestamp).getTime(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('[QwenAgentManager] Session loaded directly:', sessionId);
|
|
||||||
return messages;
|
return messages;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[QwenAgentManager] Session load directly failed:', error);
|
console.error(
|
||||||
return null;
|
'[QwenAgentManager] Session load from file system failed:',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load session, preferring ACP method if CLI version supports it
|
||||||
|
*
|
||||||
|
* @param sessionId - Session ID
|
||||||
|
* @returns Loaded session messages or null
|
||||||
|
*/
|
||||||
|
async loadSessionDirect(sessionId: string): Promise<ChatMessage[] | null> {
|
||||||
|
return this.loadSession(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new session
|
* Create new session
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ export class QwenConnectionHandler {
|
|||||||
console.warn(
|
console.warn(
|
||||||
`[QwenAgentManager] CLI version ${versionInfo.version} is below minimum required version ${'0.2.4'}`,
|
`[QwenAgentManager] CLI version ${versionInfo.version} is below minimum required version ${'0.2.4'}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: 暂时注释
|
||||||
// vscode.window.showWarningMessage(
|
// vscode.window.showWarningMessage(
|
||||||
// `Qwen Code CLI version ${versionInfo.version} is below the minimum required version. Some features may not work properly. Please upgrade to version 0.2.4 or later.`,
|
// `Qwen Code CLI version ${versionInfo.version} is below the minimum required version. Some features may not work properly. Please upgrade to version 0.2.4 or later.`,
|
||||||
// );
|
// );
|
||||||
|
|||||||
@@ -93,6 +93,33 @@ export class CliInstaller {
|
|||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Use NVM environment to ensure we get the same Node.js version
|
||||||
|
// as when they run 'node -v' in terminal
|
||||||
|
// Fallback chain: default alias -> node alias -> current version
|
||||||
|
const installCommand =
|
||||||
|
process.platform === 'win32'
|
||||||
|
? 'npm install -g @qwen-code/qwen-code@latest'
|
||||||
|
: 'source ~/.nvm/nvm.sh 2>/dev/null && (nvm use default 2>/dev/null || nvm use node 2>/dev/null || nvm use 2>/dev/null); npm install -g @qwen-code/qwen-code@latest';
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'[CliInstaller] Installing with command:',
|
||||||
|
installCommand,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
'[CliInstaller] Current process PATH:',
|
||||||
|
process.env['PATH'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Also log Node.js version being used by VS Code
|
||||||
|
console.log(
|
||||||
|
'[CliInstaller] VS Code Node.js version:',
|
||||||
|
process.version,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
'[CliInstaller] VS Code Node.js execPath:',
|
||||||
|
process.execPath,
|
||||||
|
);
|
||||||
|
|
||||||
const { stdout, stderr } = await execAsync(
|
const { stdout, stderr } = await execAsync(
|
||||||
'npm install -g @qwen-code/qwen-code@latest',
|
'npm install -g @qwen-code/qwen-code@latest',
|
||||||
{ timeout: 120000 }, // 2 minutes timeout
|
{ timeout: 120000 }, // 2 minutes timeout
|
||||||
@@ -140,9 +140,6 @@ export const WITTY_LOADING_PHRASES = [
|
|||||||
"New line? That's Ctrl+J.",
|
"New line? That's Ctrl+J.",
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Get random loading message
|
|
||||||
*/
|
|
||||||
export const getRandomLoadingMessage = (): string =>
|
export const getRandomLoadingMessage = (): string =>
|
||||||
WITTY_LOADING_PHRASES[
|
WITTY_LOADING_PHRASES[
|
||||||
Math.floor(Math.random() * WITTY_LOADING_PHRASES.length)
|
Math.floor(Math.random() * WITTY_LOADING_PHRASES.length)
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
console.log('[Extension] Panel restore completed');
|
console.log('[Extension] Panel restore completed');
|
||||||
|
|
||||||
log('WebView panel restored from serialization');
|
log('WebView panel restored from serialization');
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { AuthStateManager } from '../auth/authStateManager.js';
|
|||||||
import { PanelManager } from './PanelManager.js';
|
import { PanelManager } from './PanelManager.js';
|
||||||
import { MessageHandler } from './MessageHandler.js';
|
import { MessageHandler } from './MessageHandler.js';
|
||||||
import { WebViewContent } from './WebViewContent.js';
|
import { WebViewContent } from './WebViewContent.js';
|
||||||
import { CliInstaller } from '../utils/CliInstaller.js';
|
import { CliInstaller } from '../cli/CliInstaller.js';
|
||||||
import { getFileName } from './utils/webviewUtils.js';
|
import { getFileName } from './utils/webviewUtils.js';
|
||||||
|
|
||||||
export class WebViewProvider {
|
export class WebViewProvider {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { QwenAgentManager } from '../agents/qwenAgentManager.js';
|
import type { QwenAgentManager } from '../../agents/qwenAgentManager.js';
|
||||||
import type { ConversationStore } from '../storage/conversationStore.js';
|
import type { ConversationStore } from '../../storage/conversationStore.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base message handler interface
|
* Base message handler interface
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ export class SettingsMessageHandler extends BaseMessageHandler {
|
|||||||
try {
|
try {
|
||||||
// Open settings in a side panel
|
// Open settings in a side panel
|
||||||
await vscode.commands.executeCommand('workbench.action.openSettings', {
|
await vscode.commands.executeCommand('workbench.action.openSettings', {
|
||||||
// TODO:
|
|
||||||
// openToSide: true,
|
|
||||||
query: 'qwenCode',
|
query: 'qwenCode',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useCallback, useRef } from 'react';
|
import { useState, useCallback, useRef } from 'react';
|
||||||
import type { VSCodeAPI } from '../hooks/useVSCode.js';
|
import type { VSCodeAPI } from '../../hooks/useVSCode.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File context management Hook
|
* File context management Hook
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import type { VSCodeAPI } from '../hooks/useVSCode.js';
|
import type { VSCodeAPI } from '../../hooks/useVSCode.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Session management Hook
|
* Session management Hook
|
||||||
|
|||||||
Reference in New Issue
Block a user