mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
chore(vscode-ide-companion): wip
This commit is contained in:
@@ -34,6 +34,9 @@ export type { ChatMessage, PlanEntry, ToolCallUpdateData };
|
|||||||
*
|
*
|
||||||
* Coordinates various modules and provides unified interface
|
* Coordinates various modules and provides unified interface
|
||||||
*/
|
*/
|
||||||
|
interface AgentConnectOptions {
|
||||||
|
autoAuthenticate?: boolean;
|
||||||
|
}
|
||||||
interface AgentSessionOptions {
|
interface AgentSessionOptions {
|
||||||
autoAuthenticate?: boolean;
|
autoAuthenticate?: boolean;
|
||||||
}
|
}
|
||||||
@@ -189,12 +192,14 @@ export class QwenAgentManager {
|
|||||||
async connect(
|
async connect(
|
||||||
workingDir: string,
|
workingDir: string,
|
||||||
cliEntryPath: string,
|
cliEntryPath: string,
|
||||||
|
options?: AgentConnectOptions,
|
||||||
): Promise<QwenConnectionResult> {
|
): Promise<QwenConnectionResult> {
|
||||||
this.currentWorkingDir = workingDir;
|
this.currentWorkingDir = workingDir;
|
||||||
return this.connectionHandler.connect(
|
return this.connectionHandler.connect(
|
||||||
this.connection,
|
this.connection,
|
||||||
workingDir,
|
workingDir,
|
||||||
cliEntryPath,
|
cliEntryPath,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,9 +281,10 @@ export class QwenAgentManager {
|
|||||||
'[QwenAgentManager] Getting session list with version-aware strategy',
|
'[QwenAgentManager] Getting session list with version-aware strategy',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prefer ACP method first; fall back to file system if it fails for any reason.
|
|
||||||
try {
|
try {
|
||||||
console.log('[QwenAgentManager] Attempting to get session list via ACP');
|
console.log(
|
||||||
|
'[QwenAgentManager] Attempting to get session list via ACP method',
|
||||||
|
);
|
||||||
const response = await this.connection.listSessions();
|
const response = await this.connection.listSessions();
|
||||||
console.log('[QwenAgentManager] ACP session list response:', response);
|
console.log('[QwenAgentManager] ACP session list response:', response);
|
||||||
|
|
||||||
@@ -288,19 +294,21 @@ export class QwenAgentManager {
|
|||||||
const res: unknown = response;
|
const res: unknown = response;
|
||||||
let items: Array<Record<string, unknown>> = [];
|
let items: Array<Record<string, unknown>> = [];
|
||||||
|
|
||||||
if (Array.isArray(res)) {
|
// Note: AcpSessionManager resolves `sendRequest` with the JSON-RPC
|
||||||
items = res as Array<Record<string, unknown>>;
|
// "result" directly (not the full AcpResponse). Treat it as unknown
|
||||||
} else if (res && typeof res === 'object' && 'items' in res) {
|
// and carefully narrow before accessing `items` to satisfy strict TS.
|
||||||
|
if (res && typeof res === 'object' && 'items' in res) {
|
||||||
const itemsValue = (res as { items?: unknown }).items;
|
const itemsValue = (res as { items?: unknown }).items;
|
||||||
items = Array.isArray(itemsValue)
|
items = Array.isArray(itemsValue)
|
||||||
? (itemsValue as Array<Record<string, unknown>>)
|
? (itemsValue as Array<Record<string, unknown>>)
|
||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[QwenAgentManager] Sessions retrieved via ACP:', {
|
console.log(
|
||||||
count: items.length,
|
'[QwenAgentManager] Sessions retrieved via ACP:',
|
||||||
});
|
res,
|
||||||
|
items.length,
|
||||||
|
);
|
||||||
if (items.length > 0) {
|
if (items.length > 0) {
|
||||||
const sessions = items.map((item) => ({
|
const sessions = items.map((item) => ({
|
||||||
id: item.sessionId || item.id,
|
id: item.sessionId || item.id,
|
||||||
@@ -314,6 +322,11 @@ export class QwenAgentManager {
|
|||||||
filePath: item.filePath,
|
filePath: item.filePath,
|
||||||
cwd: item.cwd,
|
cwd: item.cwd,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Sessions retrieved via ACP:',
|
||||||
|
sessions.length,
|
||||||
|
);
|
||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -376,6 +389,7 @@ export class QwenAgentManager {
|
|||||||
}> {
|
}> {
|
||||||
const size = params?.size ?? 20;
|
const size = params?.size ?? 20;
|
||||||
const cursor = params?.cursor;
|
const cursor = params?.cursor;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.connection.listSessions({
|
const response = await this.connection.listSessions({
|
||||||
size,
|
size,
|
||||||
@@ -470,7 +484,6 @@ export class QwenAgentManager {
|
|||||||
*/
|
*/
|
||||||
async getSessionMessages(sessionId: string): Promise<ChatMessage[]> {
|
async getSessionMessages(sessionId: string): Promise<ChatMessage[]> {
|
||||||
try {
|
try {
|
||||||
// Prefer reading CLI's JSONL if we can find filePath from session/list
|
|
||||||
try {
|
try {
|
||||||
const list = await this.getSessionList();
|
const list = await this.getSessionList();
|
||||||
const item = list.find(
|
const item = list.find(
|
||||||
@@ -690,7 +703,9 @@ export class QwenAgentManager {
|
|||||||
const planText = planEntries
|
const planText = planEntries
|
||||||
.map(
|
.map(
|
||||||
(entry: Record<string, unknown>, index: number) =>
|
(entry: Record<string, unknown>, index: number) =>
|
||||||
`${index + 1}. ${entry.description || entry.title || 'Unnamed step'}`,
|
`${index + 1}. ${
|
||||||
|
entry.description || entry.title || 'Unnamed step'
|
||||||
|
}`,
|
||||||
)
|
)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
msgs.push({
|
msgs.push({
|
||||||
@@ -969,13 +984,15 @@ export class QwenAgentManager {
|
|||||||
sessionId,
|
sessionId,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prefer ACP session/load first; fall back to file system on failure.
|
|
||||||
try {
|
try {
|
||||||
console.log('[QwenAgentManager] Attempting to load session via ACP');
|
console.log(
|
||||||
|
'[QwenAgentManager] Attempting to load session via ACP method',
|
||||||
|
);
|
||||||
await this.loadSessionViaAcp(sessionId);
|
await this.loadSessionViaAcp(sessionId);
|
||||||
console.log('[QwenAgentManager] Session loaded successfully via ACP');
|
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.
|
// 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) {
|
} catch (error) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'[QwenAgentManager] ACP session load failed, falling back to file system method:',
|
'[QwenAgentManager] ACP session load failed, falling back to file system method:',
|
||||||
@@ -1094,7 +1111,7 @@ export class QwenAgentManager {
|
|||||||
// Let CLI handle authentication - it's the single source of truth
|
// Let CLI handle authentication - it's the single source of truth
|
||||||
await this.connection.authenticate(authMethod);
|
await this.connection.authenticate(authMethod);
|
||||||
// Add a slight delay to ensure auth state is settled
|
// Add a slight delay to ensure auth state is settled
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
await this.connection.newSession(workingDir);
|
await this.connection.newSession(workingDir);
|
||||||
} catch (reauthErr) {
|
} catch (reauthErr) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AcpConnection } from './acpConnection.js';
|
import type { AcpConnection } from './acpConnection.js';
|
||||||
|
import { isAuthenticationRequiredError } from '../utils/authErrors.js';
|
||||||
|
import { authMethod } from '../types/acpTypes.js';
|
||||||
|
|
||||||
export interface QwenConnectionResult {
|
export interface QwenConnectionResult {
|
||||||
sessionCreated: boolean;
|
sessionCreated: boolean;
|
||||||
@@ -27,30 +29,153 @@ export class QwenConnectionHandler {
|
|||||||
*
|
*
|
||||||
* @param connection - ACP connection instance
|
* @param connection - ACP connection instance
|
||||||
* @param workingDir - Working directory
|
* @param workingDir - Working directory
|
||||||
* @param cliEntryPath - Path to bundled CLI entrypoint (cli.js)
|
* @param cliPath - CLI path (optional, if provided will override the path in configuration)
|
||||||
*/
|
*/
|
||||||
async connect(
|
async connect(
|
||||||
connection: AcpConnection,
|
connection: AcpConnection,
|
||||||
workingDir: string,
|
workingDir: string,
|
||||||
cliEntryPath: string,
|
cliEntryPath: string,
|
||||||
|
options?: {
|
||||||
|
autoAuthenticate?: boolean;
|
||||||
|
},
|
||||||
): Promise<QwenConnectionResult> {
|
): Promise<QwenConnectionResult> {
|
||||||
const connectId = Date.now();
|
const connectId = Date.now();
|
||||||
console.log(`[QwenAgentManager] 🚀 CONNECT() CALLED - ID: ${connectId}`);
|
console.log(`[QwenAgentManager] 🚀 CONNECT() CALLED - ID: ${connectId}`);
|
||||||
const sessionCreated = false;
|
const autoAuthenticate = options?.autoAuthenticate ?? true;
|
||||||
const requiresAuth = false;
|
let sessionCreated = false;
|
||||||
|
let requiresAuth = false;
|
||||||
|
|
||||||
// Build extra CLI arguments (only essential parameters)
|
// Build extra CLI arguments (only essential parameters)
|
||||||
const extraArgs: string[] = [];
|
const extraArgs: string[] = [];
|
||||||
|
|
||||||
await connection.connect(cliEntryPath, workingDir, extraArgs);
|
await connection.connect(cliEntryPath!, workingDir, extraArgs);
|
||||||
|
|
||||||
// Note: Session creation is now handled by the caller (QwenAgentManager)
|
// Try to restore existing session or create new session
|
||||||
// This prevents automatic session creation on every connection which was
|
// Note: Auto-restore on connect is disabled to avoid surprising loads
|
||||||
// causing unwanted authentication prompts
|
// when user opens a "New Chat" tab. Restoration is now an explicit action
|
||||||
|
// (session selector → session/load) or handled by higher-level flows.
|
||||||
|
const sessionRestored = false;
|
||||||
|
|
||||||
|
// Create new session if unable to restore
|
||||||
|
if (!sessionRestored) {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] no sessionRestored, Creating new session...',
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Creating new session (letting CLI handle authentication)...',
|
||||||
|
);
|
||||||
|
await this.newSessionWithRetry(
|
||||||
|
connection,
|
||||||
|
workingDir,
|
||||||
|
3,
|
||||||
|
authMethod,
|
||||||
|
autoAuthenticate,
|
||||||
|
);
|
||||||
|
console.log('[QwenAgentManager] New session created successfully');
|
||||||
|
sessionCreated = true;
|
||||||
|
} catch (sessionError) {
|
||||||
|
const needsAuth =
|
||||||
|
autoAuthenticate === false &&
|
||||||
|
isAuthenticationRequiredError(sessionError);
|
||||||
|
if (needsAuth) {
|
||||||
|
requiresAuth = true;
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Session creation requires authentication; waiting for user-triggered login.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`\n⚠️ [SESSION FAILED] newSessionWithRetry threw error\n`,
|
||||||
|
);
|
||||||
|
console.log(`[QwenAgentManager] Error details:`, sessionError);
|
||||||
|
throw sessionError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sessionCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`\n========================================`);
|
console.log(`\n========================================`);
|
||||||
console.log(`[QwenAgentManager] ✅ CONNECT() COMPLETED SUCCESSFULLY`);
|
console.log(`[QwenAgentManager] ✅ CONNECT() COMPLETED SUCCESSFULLY`);
|
||||||
console.log(`========================================\n`);
|
console.log(`========================================\n`);
|
||||||
return { sessionCreated, requiresAuth };
|
return { sessionCreated, requiresAuth };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new session (with retry)
|
||||||
|
*
|
||||||
|
* @param connection - ACP connection instance
|
||||||
|
* @param workingDir - Working directory
|
||||||
|
* @param maxRetries - Maximum number of retries
|
||||||
|
*/
|
||||||
|
private async newSessionWithRetry(
|
||||||
|
connection: AcpConnection,
|
||||||
|
workingDir: string,
|
||||||
|
maxRetries: number,
|
||||||
|
authMethod: string,
|
||||||
|
autoAuthenticate: boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
`[QwenAgentManager] Creating session (attempt ${attempt}/${maxRetries})...`,
|
||||||
|
);
|
||||||
|
await connection.newSession(workingDir);
|
||||||
|
console.log('[QwenAgentManager] Session created successfully');
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : String(error);
|
||||||
|
console.error(
|
||||||
|
`[QwenAgentManager] Session creation attempt ${attempt} failed:`,
|
||||||
|
errorMessage,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If Qwen reports that authentication is required, try to
|
||||||
|
// authenticate on-the-fly once and retry without waiting.
|
||||||
|
const requiresAuth = isAuthenticationRequiredError(error);
|
||||||
|
if (requiresAuth) {
|
||||||
|
if (!autoAuthenticate) {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Authentication required but auto-authentication is disabled. Propagating error.',
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Qwen requires authentication. Authenticating and retrying session/new...',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await connection.authenticate(authMethod);
|
||||||
|
// FIXME: @yiliang114 If there is no delay for a while, immediately executing
|
||||||
|
// newSession may cause the cli authorization jump to be triggered again
|
||||||
|
// Add a slight delay to ensure auth state is settled
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
// Fall through to retry logic below
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt === maxRetries) {
|
||||||
|
throw new Error(
|
||||||
|
`Session creation failed after ${maxRetries} attempts: ${errorMessage}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
||||||
|
console.log(`[QwenAgentManager] Retrying in ${delay}ms...`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -576,6 +576,7 @@ export class WebViewProvider {
|
|||||||
const connectResult = await this.agentManager.connect(
|
const connectResult = await this.agentManager.connect(
|
||||||
workingDir,
|
workingDir,
|
||||||
bundledCliEntry,
|
bundledCliEntry,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
console.log('[WebViewProvider] Agent connected successfully');
|
console.log('[WebViewProvider] Agent connected successfully');
|
||||||
this.agentInitialized = true;
|
this.agentInitialized = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user