mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
fix(vscode-ide-companion): Interactive unification of first login and login
This commit is contained in:
@@ -686,7 +686,38 @@ export class QwenAgentManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to create a new ACP session. If the backend asks for auth despite our
|
||||||
|
// cached flag (e.g. fresh process or expired tokens), re-authenticate and retry.
|
||||||
|
try {
|
||||||
await this.connection.newSession(workingDir);
|
await this.connection.newSession(workingDir);
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
const requiresAuth =
|
||||||
|
msg.includes('Authentication required') ||
|
||||||
|
msg.includes('(code: -32000)');
|
||||||
|
|
||||||
|
if (requiresAuth) {
|
||||||
|
console.warn(
|
||||||
|
'[QwenAgentManager] session/new requires authentication. Retrying with authenticate...',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await this.connection.authenticate(authMethod);
|
||||||
|
// Persist auth cache so subsequent calls can skip the web flow.
|
||||||
|
if (effectiveAuth) {
|
||||||
|
await effectiveAuth.saveAuthState(workingDir, authMethod);
|
||||||
|
}
|
||||||
|
await this.connection.newSession(workingDir);
|
||||||
|
} catch (reauthErr) {
|
||||||
|
// Clear potentially stale cache on failure and rethrow
|
||||||
|
if (effectiveAuth) {
|
||||||
|
await effectiveAuth.clearAuthState();
|
||||||
|
}
|
||||||
|
throw reauthErr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
const newSessionId = this.connection.currentSessionId;
|
const newSessionId = this.connection.currentSessionId;
|
||||||
console.log(
|
console.log(
|
||||||
'[QwenAgentManager] New session created with ID:',
|
'[QwenAgentManager] New session created with ID:',
|
||||||
|
|||||||
@@ -39,10 +39,7 @@ export class QwenConnectionHandler {
|
|||||||
cliPath?: string,
|
cliPath?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const connectId = Date.now();
|
const connectId = Date.now();
|
||||||
console.log(`\n========================================`);
|
|
||||||
console.log(`[QwenAgentManager] 🚀 CONNECT() CALLED - ID: ${connectId}`);
|
console.log(`[QwenAgentManager] 🚀 CONNECT() CALLED - ID: ${connectId}`);
|
||||||
console.log(`[QwenAgentManager] Call stack:\n${new Error().stack}`);
|
|
||||||
console.log(`========================================\n`);
|
|
||||||
|
|
||||||
// Check CLI version and features
|
// Check CLI version and features
|
||||||
const cliVersionManager = CliVersionManager.getInstance();
|
const cliVersionManager = CliVersionManager.getInstance();
|
||||||
@@ -166,7 +163,9 @@ export class QwenConnectionHandler {
|
|||||||
|
|
||||||
// Create new session if unable to restore
|
// Create new session if unable to restore
|
||||||
if (!sessionRestored) {
|
if (!sessionRestored) {
|
||||||
console.log('[QwenAgentManager] Creating new session...');
|
console.log(
|
||||||
|
'[QwenAgentManager] no sessionRestored, Creating new session...',
|
||||||
|
);
|
||||||
|
|
||||||
// Check if we have valid cached authentication
|
// Check if we have valid cached authentication
|
||||||
let hasValidAuth = false;
|
let hasValidAuth = false;
|
||||||
@@ -217,7 +216,13 @@ export class QwenConnectionHandler {
|
|||||||
console.log(
|
console.log(
|
||||||
'[QwenAgentManager] Creating new session after authentication...',
|
'[QwenAgentManager] Creating new session after authentication...',
|
||||||
);
|
);
|
||||||
await this.newSessionWithRetry(connection, workingDir, 3);
|
await this.newSessionWithRetry(
|
||||||
|
connection,
|
||||||
|
workingDir,
|
||||||
|
3,
|
||||||
|
authMethod,
|
||||||
|
authStateManager,
|
||||||
|
);
|
||||||
console.log('[QwenAgentManager] New session created successfully');
|
console.log('[QwenAgentManager] New session created successfully');
|
||||||
|
|
||||||
// Ensure auth state is saved (prevent repeated authentication)
|
// Ensure auth state is saved (prevent repeated authentication)
|
||||||
@@ -257,6 +262,8 @@ export class QwenConnectionHandler {
|
|||||||
connection: AcpConnection,
|
connection: AcpConnection,
|
||||||
workingDir: string,
|
workingDir: string,
|
||||||
maxRetries: number,
|
maxRetries: number,
|
||||||
|
authMethod: string,
|
||||||
|
authStateManager?: AuthStateManager,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
try {
|
try {
|
||||||
@@ -274,6 +281,38 @@ export class QwenConnectionHandler {
|
|||||||
errorMessage,
|
errorMessage,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If the backend reports that authentication is required, try to
|
||||||
|
// authenticate on-the-fly once and retry without waiting.
|
||||||
|
const requiresAuth =
|
||||||
|
errorMessage.includes('Authentication required') ||
|
||||||
|
errorMessage.includes('(code: -32000)');
|
||||||
|
if (requiresAuth) {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Backend requires authentication. Authenticating and retrying session/new...',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await connection.authenticate(authMethod);
|
||||||
|
if (authStateManager) {
|
||||||
|
await authStateManager.saveAuthState(workingDir, authMethod);
|
||||||
|
}
|
||||||
|
// Retry immediately after successful auth
|
||||||
|
await connection.newSession(workingDir);
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Session created successfully after auth',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} catch (authErr) {
|
||||||
|
console.error(
|
||||||
|
'[QwenAgentManager] Re-authentication failed:',
|
||||||
|
authErr,
|
||||||
|
);
|
||||||
|
if (authStateManager) {
|
||||||
|
await authStateManager.clearAuthState();
|
||||||
|
}
|
||||||
|
// Fall through to retry logic below
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (attempt === maxRetries) {
|
if (attempt === maxRetries) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Session creation failed after ${maxRetries} attempts: ${errorMessage}`,
|
`Session creation failed after ${maxRetries} attempts: ${errorMessage}`,
|
||||||
|
|||||||
@@ -60,10 +60,16 @@ export const App: React.FC = () => {
|
|||||||
toolCall: PermissionToolCall;
|
toolCall: PermissionToolCall;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [planEntries, setPlanEntries] = useState<PlanEntry[]>([]);
|
const [planEntries, setPlanEntries] = useState<PlanEntry[]>([]);
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(
|
||||||
|
null,
|
||||||
|
) as React.RefObject<HTMLDivElement>;
|
||||||
// Scroll container for message list; used to keep the view anchored to the latest content
|
// Scroll container for message list; used to keep the view anchored to the latest content
|
||||||
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
const messagesContainerRef = useRef<HTMLDivElement>(
|
||||||
const inputFieldRef = useRef<HTMLDivElement>(null);
|
null,
|
||||||
|
) as React.RefObject<HTMLDivElement>;
|
||||||
|
const inputFieldRef = useRef<HTMLDivElement>(
|
||||||
|
null,
|
||||||
|
) as React.RefObject<HTMLDivElement>;
|
||||||
const [showBanner, setShowBanner] = useState(true);
|
const [showBanner, setShowBanner] = useState(true);
|
||||||
const [editMode, setEditMode] = useState<EditMode>('ask');
|
const [editMode, setEditMode] = useState<EditMode>('ask');
|
||||||
const [thinkingEnabled, setThinkingEnabled] = useState(false);
|
const [thinkingEnabled, setThinkingEnabled] = useState(false);
|
||||||
|
|||||||
@@ -309,58 +309,14 @@ export class WebViewProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Initialize empty conversation immediately for fast UI rendering
|
// Lazy initialization: Do not attempt to connect/auth on WebView show.
|
||||||
// await this.initializeEmptyConversation();
|
// Render the chat UI immediately; we will connect/login on-demand when the
|
||||||
|
// user sends a message or requests a session action.
|
||||||
// // Perform background CLI detection and connection without blocking UI
|
|
||||||
// this.performBackgroundInitialization();
|
|
||||||
|
|
||||||
// Smart login restore: Check if we have valid cached auth and restore connection if available
|
|
||||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
|
||||||
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
|
||||||
const config = vscode.workspace.getConfiguration('qwenCode');
|
|
||||||
const openaiApiKey = config.get<string>('qwen.openaiApiKey', '');
|
|
||||||
const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth';
|
|
||||||
|
|
||||||
// Check if we have valid cached authentication
|
|
||||||
let hasValidAuth = false;
|
|
||||||
if (this.authStateManager) {
|
|
||||||
hasValidAuth = await this.authStateManager.hasValidAuth(
|
|
||||||
workingDir,
|
|
||||||
authMethod,
|
|
||||||
);
|
|
||||||
console.log(
|
console.log(
|
||||||
'[WebViewProvider] Has valid cached auth on show:',
|
'[WebViewProvider] Lazy init: rendering empty conversation only',
|
||||||
hasValidAuth,
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (hasValidAuth && !this.agentInitialized) {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Found valid cached auth, attempting to restore connection...',
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
await this.initializeAgentConnection();
|
|
||||||
console.log('[WebViewProvider] Connection restored successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[WebViewProvider] Failed to restore connection:', error);
|
|
||||||
// Fall back to empty conversation if restore fails
|
|
||||||
await this.initializeEmptyConversation();
|
await this.initializeEmptyConversation();
|
||||||
}
|
}
|
||||||
} else if (this.agentInitialized) {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Agent already initialized, reusing existing connection',
|
|
||||||
);
|
|
||||||
// Reload current session messages
|
|
||||||
await this.loadCurrentSessionMessages();
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] No valid cached auth or agent already initialized, showing empty conversation',
|
|
||||||
);
|
|
||||||
// Just initialize empty conversation for the UI
|
|
||||||
await this.initializeEmptyConversation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize agent connection and session
|
* Initialize agent connection and session
|
||||||
@@ -459,124 +415,6 @@ export class WebViewProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform background initialization without blocking UI
|
|
||||||
* This method runs CLI detection and connection in the background
|
|
||||||
*/
|
|
||||||
private async performBackgroundInitialization(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
|
||||||
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
|
||||||
const config = vscode.workspace.getConfiguration('qwenCode');
|
|
||||||
const qwenEnabled = config.get<boolean>('qwen.enabled', true);
|
|
||||||
|
|
||||||
if (qwenEnabled) {
|
|
||||||
// Check if we have valid cached authentication
|
|
||||||
const openaiApiKey = config.get<string>('qwen.openaiApiKey', '');
|
|
||||||
const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth';
|
|
||||||
|
|
||||||
let hasValidAuth = false;
|
|
||||||
if (this.authStateManager) {
|
|
||||||
hasValidAuth = await this.authStateManager.hasValidAuth(
|
|
||||||
workingDir,
|
|
||||||
authMethod,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Has valid cached auth in background init:',
|
|
||||||
hasValidAuth,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform CLI detection in background
|
|
||||||
const cliDetection = await CliDetector.detectQwenCli();
|
|
||||||
|
|
||||||
if (!cliDetection.isInstalled) {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Qwen CLI not detected in background check',
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] CLI detection error:',
|
|
||||||
cliDetection.error,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Notify webview that CLI is not installed
|
|
||||||
this.sendMessageToWebView({
|
|
||||||
type: 'cliNotInstalled',
|
|
||||||
data: {
|
|
||||||
error: cliDetection.error,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Qwen CLI detected in background check, attempting connection...',
|
|
||||||
);
|
|
||||||
console.log('[WebViewProvider] CLI path:', cliDetection.cliPath);
|
|
||||||
console.log('[WebViewProvider] CLI version:', cliDetection.version);
|
|
||||||
|
|
||||||
if (hasValidAuth && !this.agentInitialized) {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Found valid cached auth, attempting to restore connection in background...',
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
// Pass the detected CLI path to ensure we use the correct installation
|
|
||||||
await this.agentManager.connect(
|
|
||||||
workingDir,
|
|
||||||
this.authStateManager,
|
|
||||||
cliDetection.cliPath,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Connection restored successfully in background',
|
|
||||||
);
|
|
||||||
this.agentInitialized = true;
|
|
||||||
|
|
||||||
// Load messages from the current Qwen session
|
|
||||||
await this.loadCurrentSessionMessages();
|
|
||||||
|
|
||||||
// Notify webview that agent is connected
|
|
||||||
this.sendMessageToWebView({
|
|
||||||
type: 'agentConnected',
|
|
||||||
data: {},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
'[WebViewProvider] Failed to restore connection in background:',
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
// Clear auth cache on error
|
|
||||||
await this.authStateManager.clearAuthState();
|
|
||||||
|
|
||||||
// Notify webview that agent connection failed
|
|
||||||
this.sendMessageToWebView({
|
|
||||||
type: 'agentConnectionError',
|
|
||||||
data: {
|
|
||||||
message:
|
|
||||||
error instanceof Error ? error.message : String(error),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (this.agentInitialized) {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Agent already initialized, no need to reconnect in background',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] No valid cached auth, skipping background connection',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Qwen agent is disabled in settings (background)',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
'[WebViewProvider] Background initialization failed:',
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force re-login by clearing auth cache and reconnecting
|
* Force re-login by clearing auth cache and reconnecting
|
||||||
* Called when user explicitly uses /login command
|
* Called when user explicitly uses /login command
|
||||||
@@ -588,6 +426,16 @@ export class WebViewProvider {
|
|||||||
!!this.authStateManager,
|
!!this.authStateManager,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await vscode.window.withProgress(
|
||||||
|
{
|
||||||
|
location: vscode.ProgressLocation.Notification,
|
||||||
|
title: 'Logging in to Qwen Code... ',
|
||||||
|
cancellable: false,
|
||||||
|
},
|
||||||
|
async (progress) => {
|
||||||
|
try {
|
||||||
|
progress.report({ message: 'Preparing sign-in...' });
|
||||||
|
|
||||||
// Clear existing auth cache
|
// Clear existing auth cache
|
||||||
if (this.authStateManager) {
|
if (this.authStateManager) {
|
||||||
await this.authStateManager.clearAuthState();
|
await this.authStateManager.clearAuthState();
|
||||||
@@ -608,12 +456,17 @@ export class WebViewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait a moment for cleanup to complete
|
// Wait a moment for cleanup to complete
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
|
|
||||||
|
progress.report({
|
||||||
|
message: 'Connecting to CLI and starting sign-in...',
|
||||||
|
});
|
||||||
|
|
||||||
// Reinitialize connection (will trigger fresh authentication)
|
// Reinitialize connection (will trigger fresh authentication)
|
||||||
try {
|
|
||||||
await this.initializeAgentConnection();
|
await this.initializeAgentConnection();
|
||||||
console.log('[WebViewProvider] Force re-login completed successfully');
|
console.log(
|
||||||
|
'[WebViewProvider] Force re-login completed successfully',
|
||||||
|
);
|
||||||
|
|
||||||
// Send success notification to WebView
|
// Send success notification to WebView
|
||||||
this.sendMessageToWebView({
|
this.sendMessageToWebView({
|
||||||
@@ -637,6 +490,8 @@ export class WebViewProvider {
|
|||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -873,64 +728,12 @@ export class WebViewProvider {
|
|||||||
|
|
||||||
console.log('[WebViewProvider] Panel restored successfully');
|
console.log('[WebViewProvider] Panel restored successfully');
|
||||||
|
|
||||||
// TODO:
|
// Lazy init on restore as well: do not auto-connect; just render UI.
|
||||||
// await this.initializeEmptyConversation();
|
|
||||||
// // Perform background initialization without blocking UI
|
|
||||||
// this.performBackgroundInitialization();
|
|
||||||
|
|
||||||
// Smart login restore: Check if we have valid cached auth and restore connection if available
|
|
||||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
|
||||||
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
|
||||||
const config = vscode.workspace.getConfiguration('qwenCode');
|
|
||||||
const openaiApiKey = config.get<string>('qwen.openaiApiKey', '');
|
|
||||||
const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth';
|
|
||||||
|
|
||||||
// Check if we have valid cached authentication
|
|
||||||
let hasValidAuth = false;
|
|
||||||
if (this.authStateManager) {
|
|
||||||
hasValidAuth = await this.authStateManager.hasValidAuth(
|
|
||||||
workingDir,
|
|
||||||
authMethod,
|
|
||||||
);
|
|
||||||
console.log(
|
console.log(
|
||||||
'[WebViewProvider] Has valid cached auth on restore:',
|
'[WebViewProvider] Lazy restore: rendering empty conversation only',
|
||||||
hasValidAuth,
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (hasValidAuth && !this.agentInitialized) {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Found valid cached auth, attempting to restore connection...',
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
await this.initializeAgentConnection();
|
|
||||||
console.log('[WebViewProvider] Connection restored successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[WebViewProvider] Failed to restore connection:', error);
|
|
||||||
// Fall back to empty conversation if restore fails
|
|
||||||
await this.initializeEmptyConversation();
|
await this.initializeEmptyConversation();
|
||||||
}
|
}
|
||||||
} else if (this.agentInitialized) {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] Agent already initialized, refreshing connection...',
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
await this.refreshConnection();
|
|
||||||
console.log('[WebViewProvider] Connection refreshed successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[WebViewProvider] Failed to refresh connection:', error);
|
|
||||||
// Fall back to empty conversation if refresh fails
|
|
||||||
this.agentInitialized = false;
|
|
||||||
await this.initializeEmptyConversation();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'[WebViewProvider] No valid cached auth or agent already initialized, showing empty conversation',
|
|
||||||
);
|
|
||||||
// Just initialize empty conversation for the UI
|
|
||||||
await this.initializeEmptyConversation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current state for serialization
|
* Get the current state for serialization
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2025 Qwen Team
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Use explicit Vitest imports instead of relying on globals.
|
|
||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import type { ToolCallData } from '../toolcalls/shared/types.js';
|
|
||||||
import { hasToolCallOutput } from '../toolcalls/shared/utils.js';
|
|
||||||
|
|
||||||
describe('Message Ordering', () => {
|
|
||||||
it('should correctly identify tool calls with output', () => {
|
|
||||||
// Test failed tool call (should show)
|
|
||||||
const failedToolCall: ToolCallData = {
|
|
||||||
toolCallId: 'test-1',
|
|
||||||
kind: 'read',
|
|
||||||
title: 'Read file',
|
|
||||||
status: 'failed',
|
|
||||||
timestamp: 1000,
|
|
||||||
};
|
|
||||||
expect(hasToolCallOutput(failedToolCall)).toBe(true);
|
|
||||||
|
|
||||||
// Test execute tool call with title (should show)
|
|
||||||
const executeToolCall: ToolCallData = {
|
|
||||||
toolCallId: 'test-2',
|
|
||||||
kind: 'execute',
|
|
||||||
title: 'ls -la',
|
|
||||||
status: 'completed',
|
|
||||||
timestamp: 2000,
|
|
||||||
};
|
|
||||||
expect(hasToolCallOutput(executeToolCall)).toBe(true);
|
|
||||||
|
|
||||||
// Test tool call with content (should show)
|
|
||||||
const contentToolCall: ToolCallData = {
|
|
||||||
toolCallId: 'test-3',
|
|
||||||
kind: 'read',
|
|
||||||
title: 'Read file',
|
|
||||||
status: 'completed',
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'content',
|
|
||||||
content: {
|
|
||||||
type: 'text',
|
|
||||||
text: 'File content',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timestamp: 3000,
|
|
||||||
};
|
|
||||||
expect(hasToolCallOutput(contentToolCall)).toBe(true);
|
|
||||||
|
|
||||||
// Test tool call with locations (should show)
|
|
||||||
const locationToolCall: ToolCallData = {
|
|
||||||
toolCallId: 'test-4',
|
|
||||||
kind: 'read',
|
|
||||||
title: 'Read file',
|
|
||||||
status: 'completed',
|
|
||||||
locations: [
|
|
||||||
{
|
|
||||||
path: '/path/to/file.txt',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timestamp: 4000,
|
|
||||||
};
|
|
||||||
expect(hasToolCallOutput(locationToolCall)).toBe(true);
|
|
||||||
|
|
||||||
// Test tool call with title (should show)
|
|
||||||
const titleToolCall: ToolCallData = {
|
|
||||||
toolCallId: 'test-5',
|
|
||||||
kind: 'generic',
|
|
||||||
title: 'Generic tool call',
|
|
||||||
status: 'completed',
|
|
||||||
timestamp: 5000,
|
|
||||||
};
|
|
||||||
expect(hasToolCallOutput(titleToolCall)).toBe(true);
|
|
||||||
|
|
||||||
// Test tool call without output (should not show)
|
|
||||||
const noOutputToolCall: ToolCallData = {
|
|
||||||
toolCallId: 'test-6',
|
|
||||||
kind: 'generic',
|
|
||||||
title: '',
|
|
||||||
status: 'completed',
|
|
||||||
timestamp: 6000,
|
|
||||||
};
|
|
||||||
expect(hasToolCallOutput(noOutputToolCall)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -376,17 +376,23 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
// Check for session not found error and handle it appropriately
|
// Check for session not found error and handle it appropriately
|
||||||
if (
|
if (
|
||||||
errorMsg.includes('Session not found') ||
|
errorMsg.includes('Session not found') ||
|
||||||
errorMsg.includes('No active ACP session')
|
errorMsg.includes('No active ACP session') ||
|
||||||
|
errorMsg.includes('Authentication required') ||
|
||||||
|
errorMsg.includes('(code: -32000)')
|
||||||
) {
|
) {
|
||||||
// Clear auth cache since session is invalid
|
// Clear auth cache since session is invalid
|
||||||
// Note: We would need access to authStateManager for this, but for now we'll just show login prompt
|
// Note: We would need access to authStateManager for this, but for now we'll just show login prompt
|
||||||
const result = await vscode.window.showWarningMessage(
|
const result = await vscode.window.showWarningMessage(
|
||||||
'Your session has expired. Please login again to continue using Qwen Code.',
|
'Your login has expired. Please login again to continue using Qwen Code.',
|
||||||
'Login Now',
|
'Login Now',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result === 'Login Now') {
|
if (result === 'Login Now') {
|
||||||
vscode.commands.executeCommand('qwenCode.login');
|
if (this.loginHandler) {
|
||||||
|
await this.loginHandler();
|
||||||
|
} else {
|
||||||
|
await vscode.commands.executeCommand('qwenCode.login');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vscode.window.showErrorMessage(`Error sending message: ${error}`);
|
vscode.window.showErrorMessage(`Error sending message: ${error}`);
|
||||||
@@ -405,6 +411,23 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
try {
|
try {
|
||||||
console.log('[SessionMessageHandler] Creating new Qwen session...');
|
console.log('[SessionMessageHandler] Creating new Qwen session...');
|
||||||
|
|
||||||
|
// Ensure connection (login) before creating a new session
|
||||||
|
if (!this.agentManager.isConnected) {
|
||||||
|
const result = await vscode.window.showWarningMessage(
|
||||||
|
'You need to login before creating a new session.',
|
||||||
|
'Login Now',
|
||||||
|
);
|
||||||
|
if (result === 'Login Now') {
|
||||||
|
if (this.loginHandler) {
|
||||||
|
await this.loginHandler();
|
||||||
|
} else {
|
||||||
|
await vscode.commands.executeCommand('qwenCode.login');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save current session before creating new one
|
// Save current session before creating new one
|
||||||
if (this.currentConversationId && this.agentManager.isConnected) {
|
if (this.currentConversationId && this.agentManager.isConnected) {
|
||||||
try {
|
try {
|
||||||
@@ -450,6 +473,39 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
try {
|
try {
|
||||||
console.log('[SessionMessageHandler] Switching to session:', sessionId);
|
console.log('[SessionMessageHandler] Switching to session:', sessionId);
|
||||||
|
|
||||||
|
// If not connected yet, offer to login or view offline
|
||||||
|
if (!this.agentManager.isConnected) {
|
||||||
|
const selection = await vscode.window.showWarningMessage(
|
||||||
|
'You are not logged in. Login now to fully restore this session, or view it offline.',
|
||||||
|
'Login Now',
|
||||||
|
'View Offline',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selection === 'Login Now') {
|
||||||
|
if (this.loginHandler) {
|
||||||
|
await this.loginHandler();
|
||||||
|
} else {
|
||||||
|
await vscode.commands.executeCommand('qwenCode.login');
|
||||||
|
}
|
||||||
|
} else if (selection === 'View Offline') {
|
||||||
|
// Show messages from local cache only
|
||||||
|
const messages =
|
||||||
|
await this.agentManager.getSessionMessages(sessionId);
|
||||||
|
this.currentConversationId = sessionId;
|
||||||
|
this.sendToWebView({
|
||||||
|
type: 'qwenSessionSwitched',
|
||||||
|
data: { sessionId, messages },
|
||||||
|
});
|
||||||
|
vscode.window.showInformationMessage(
|
||||||
|
'Showing cached session content. Login to interact with the AI.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// User dismissed; do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save current session before switching
|
// Save current session before switching
|
||||||
if (
|
if (
|
||||||
this.currentConversationId &&
|
this.currentConversationId &&
|
||||||
@@ -489,7 +545,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||||
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
||||||
|
|
||||||
// Try to load session via ACP
|
// Try to load session via ACP (now we should be connected)
|
||||||
try {
|
try {
|
||||||
const loadResponse =
|
const loadResponse =
|
||||||
await this.agentManager.loadSessionViaAcp(sessionId);
|
await this.agentManager.loadSessionViaAcp(sessionId);
|
||||||
@@ -514,6 +570,8 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
// Fallback: create new session
|
// Fallback: create new session
|
||||||
const messages = await this.agentManager.getSessionMessages(sessionId);
|
const messages = await this.agentManager.getSessionMessages(sessionId);
|
||||||
|
|
||||||
|
// If we are connected, try to create a fresh ACP session so user can interact
|
||||||
|
if (this.agentManager.isConnected) {
|
||||||
try {
|
try {
|
||||||
const newAcpSessionId =
|
const newAcpSessionId =
|
||||||
await this.agentManager.createNewSession(workingDir);
|
await this.agentManager.createNewSession(workingDir);
|
||||||
@@ -535,6 +593,17 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
);
|
);
|
||||||
throw createError;
|
throw createError;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Offline view only
|
||||||
|
this.currentConversationId = sessionId;
|
||||||
|
this.sendToWebView({
|
||||||
|
type: 'qwenSessionSwitched',
|
||||||
|
data: { sessionId, messages, session: sessionDetails },
|
||||||
|
});
|
||||||
|
vscode.window.showWarningMessage(
|
||||||
|
'Showing cached session content. Login to interact with the AI.',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[SessionMessageHandler] Failed to switch session:', error);
|
console.error('[SessionMessageHandler] Failed to switch session:', error);
|
||||||
@@ -620,6 +689,37 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
*/
|
*/
|
||||||
private async handleResumeSession(sessionId: string): Promise<void> {
|
private async handleResumeSession(sessionId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
// If not connected, offer to login or view offline
|
||||||
|
if (!this.agentManager.isConnected) {
|
||||||
|
const selection = await vscode.window.showWarningMessage(
|
||||||
|
'You are not logged in. Login now to fully restore this session, or view it offline.',
|
||||||
|
'Login Now',
|
||||||
|
'View Offline',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selection === 'Login Now') {
|
||||||
|
if (this.loginHandler) {
|
||||||
|
await this.loginHandler();
|
||||||
|
} else {
|
||||||
|
await vscode.commands.executeCommand('qwenCode.login');
|
||||||
|
}
|
||||||
|
} else if (selection === 'View Offline') {
|
||||||
|
const messages =
|
||||||
|
await this.agentManager.getSessionMessages(sessionId);
|
||||||
|
this.currentConversationId = sessionId;
|
||||||
|
this.sendToWebView({
|
||||||
|
type: 'qwenSessionSwitched',
|
||||||
|
data: { sessionId, messages },
|
||||||
|
});
|
||||||
|
vscode.window.showInformationMessage(
|
||||||
|
'Showing cached session content. Login to interact with the AI.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try ACP load first
|
// Try ACP load first
|
||||||
try {
|
try {
|
||||||
await this.agentManager.loadSessionViaAcp(sessionId);
|
await this.agentManager.loadSessionViaAcp(sessionId);
|
||||||
|
|||||||
Reference in New Issue
Block a user