chore(vscode-ide-companion): wip

This commit is contained in:
yiliang114
2025-12-13 22:19:54 +08:00
parent 641dd03689
commit 0f2f1faee5
3 changed files with 166 additions and 23 deletions

View File

@@ -34,6 +34,9 @@ export type { ChatMessage, PlanEntry, ToolCallUpdateData };
*
* Coordinates various modules and provides unified interface
*/
interface AgentConnectOptions {
autoAuthenticate?: boolean;
}
interface AgentSessionOptions {
autoAuthenticate?: boolean;
}
@@ -189,12 +192,14 @@ export class QwenAgentManager {
async connect(
workingDir: string,
cliEntryPath: string,
options?: AgentConnectOptions,
): Promise<QwenConnectionResult> {
this.currentWorkingDir = workingDir;
return this.connectionHandler.connect(
this.connection,
workingDir,
cliEntryPath,
options,
);
}
@@ -276,9 +281,10 @@ export class QwenAgentManager {
'[QwenAgentManager] Getting session list with version-aware strategy',
);
// Prefer ACP method first; fall back to file system if it fails for any reason.
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();
console.log('[QwenAgentManager] ACP session list response:', response);
@@ -288,19 +294,21 @@ export class QwenAgentManager {
const res: unknown = response;
let items: Array<Record<string, unknown>> = [];
if (Array.isArray(res)) {
items = res as Array<Record<string, unknown>>;
} else if (res && typeof res === 'object' && 'items' in res) {
// Note: AcpSessionManager resolves `sendRequest` with the JSON-RPC
// "result" directly (not the full AcpResponse). Treat it as unknown
// 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;
items = Array.isArray(itemsValue)
? (itemsValue as Array<Record<string, unknown>>)
: [];
}
console.log('[QwenAgentManager] Sessions retrieved via ACP:', {
count: items.length,
});
console.log(
'[QwenAgentManager] Sessions retrieved via ACP:',
res,
items.length,
);
if (items.length > 0) {
const sessions = items.map((item) => ({
id: item.sessionId || item.id,
@@ -314,6 +322,11 @@ export class QwenAgentManager {
filePath: item.filePath,
cwd: item.cwd,
}));
console.log(
'[QwenAgentManager] Sessions retrieved via ACP:',
sessions.length,
);
return sessions;
}
} catch (error) {
@@ -376,6 +389,7 @@ export class QwenAgentManager {
}> {
const size = params?.size ?? 20;
const cursor = params?.cursor;
try {
const response = await this.connection.listSessions({
size,
@@ -470,7 +484,6 @@ export class QwenAgentManager {
*/
async getSessionMessages(sessionId: string): Promise<ChatMessage[]> {
try {
// Prefer reading CLI's JSONL if we can find filePath from session/list
try {
const list = await this.getSessionList();
const item = list.find(
@@ -690,7 +703,9 @@ export class QwenAgentManager {
const planText = planEntries
.map(
(entry: Record<string, unknown>, index: number) =>
`${index + 1}. ${entry.description || entry.title || 'Unnamed step'}`,
`${index + 1}. ${
entry.description || entry.title || 'Unnamed step'
}`,
)
.join('\n');
msgs.push({
@@ -969,13 +984,15 @@ export class QwenAgentManager {
sessionId,
);
// Prefer ACP session/load first; fall back to file system on failure.
try {
console.log('[QwenAgentManager] Attempting to load session via ACP');
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.
// 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:',
@@ -1094,7 +1111,7 @@ export class QwenAgentManager {
// Let CLI handle authentication - it's the single source of truth
await this.connection.authenticate(authMethod);
// 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);
} catch (reauthErr) {
console.error(

View File

@@ -11,6 +11,8 @@
*/
import type { AcpConnection } from './acpConnection.js';
import { isAuthenticationRequiredError } from '../utils/authErrors.js';
import { authMethod } from '../types/acpTypes.js';
export interface QwenConnectionResult {
sessionCreated: boolean;
@@ -27,30 +29,153 @@ export class QwenConnectionHandler {
*
* @param connection - ACP connection instance
* @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(
connection: AcpConnection,
workingDir: string,
cliEntryPath: string,
options?: {
autoAuthenticate?: boolean;
},
): Promise<QwenConnectionResult> {
const connectId = Date.now();
console.log(`[QwenAgentManager] 🚀 CONNECT() CALLED - ID: ${connectId}`);
const sessionCreated = false;
const requiresAuth = false;
const autoAuthenticate = options?.autoAuthenticate ?? true;
let sessionCreated = false;
let requiresAuth = false;
// Build extra CLI arguments (only essential parameters)
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)
// This prevents automatic session creation on every connection which was
// causing unwanted authentication prompts
// Try to restore existing session or create new session
// Note: Auto-restore on connect is disabled to avoid surprising loads
// 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(`[QwenAgentManager] ✅ CONNECT() COMPLETED SUCCESSFULLY`);
console.log(`========================================\n`);
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));
}
}
}
}

View File

@@ -576,6 +576,7 @@ export class WebViewProvider {
const connectResult = await this.agentManager.connect(
workingDir,
bundledCliEntry,
options,
);
console.log('[WebViewProvider] Agent connected successfully');
this.agentInitialized = true;