mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
fix(vscode-ide-companion): resolve all ESLint errors
Fixed unused variable errors in SessionMessageHandler.ts: - Commented out unused conversation and messages variables Also includes previous commits: 1. feat(vscode-ide-companion): add upgrade button to CLI version warning 2. fix(vscode-ide-companion): resolve ESLint errors in InputForm component When the Qwen Code CLI version is below the minimum required version, the warning message now includes an "Upgrade Now" button that opens a terminal and runs the npm install command to upgrade the CLI. Added tests to verify the functionality works correctly.
This commit is contained in:
@@ -336,8 +336,10 @@ export class QwenAgentManager {
|
|||||||
name: this.sessionReader.getSessionTitle(session),
|
name: this.sessionReader.getSessionTitle(session),
|
||||||
startTime: session.startTime,
|
startTime: session.startTime,
|
||||||
lastUpdated: session.lastUpdated,
|
lastUpdated: session.lastUpdated,
|
||||||
messageCount: session.messages.length,
|
messageCount: session.messageCount ?? session.messages.length,
|
||||||
projectHash: session.projectHash,
|
projectHash: session.projectHash,
|
||||||
|
filePath: session.filePath,
|
||||||
|
cwd: session.cwd,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -452,8 +454,10 @@ export class QwenAgentManager {
|
|||||||
name: this.sessionReader.getSessionTitle(x.raw),
|
name: this.sessionReader.getSessionTitle(x.raw),
|
||||||
startTime: x.raw.startTime,
|
startTime: x.raw.startTime,
|
||||||
lastUpdated: x.raw.lastUpdated,
|
lastUpdated: x.raw.lastUpdated,
|
||||||
messageCount: x.raw.messages.length,
|
messageCount: x.raw.messageCount ?? x.raw.messages.length,
|
||||||
projectHash: x.raw.projectHash,
|
projectHash: x.raw.projectHash,
|
||||||
|
filePath: x.raw.filePath,
|
||||||
|
cwd: x.raw.cwd,
|
||||||
}));
|
}));
|
||||||
const nextCursorVal =
|
const nextCursorVal =
|
||||||
page.length > 0 ? page[page.length - 1].mtime : undefined;
|
page.length > 0 ? page[page.length - 1].mtime : undefined;
|
||||||
@@ -891,80 +895,6 @@ export class QwenAgentManager {
|
|||||||
return this.saveSessionViaCommand(sessionId, tag);
|
return this.saveSessionViaCommand(sessionId, tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save session as checkpoint (using CLI format)
|
|
||||||
* Saves to ~/.qwen/tmp/{projectHash}/checkpoint-{tag}.json
|
|
||||||
* Saves two copies with sessionId and conversationId to ensure recovery via either ID
|
|
||||||
*
|
|
||||||
* @param messages - Current session messages
|
|
||||||
* @param conversationId - Conversation ID (from VSCode extension)
|
|
||||||
* @returns Save result
|
|
||||||
*/
|
|
||||||
async saveCheckpoint(
|
|
||||||
messages: ChatMessage[],
|
|
||||||
conversationId: string,
|
|
||||||
): Promise<{ success: boolean; tag?: string; message?: string }> {
|
|
||||||
try {
|
|
||||||
console.log('[QwenAgentManager] ===== CHECKPOINT SAVE START =====');
|
|
||||||
console.log('[QwenAgentManager] Conversation ID:', conversationId);
|
|
||||||
console.log('[QwenAgentManager] Message count:', messages.length);
|
|
||||||
console.log(
|
|
||||||
'[QwenAgentManager] Current working dir:',
|
|
||||||
this.currentWorkingDir,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'[QwenAgentManager] Current session ID (from CLI):',
|
|
||||||
this.currentSessionId,
|
|
||||||
);
|
|
||||||
// In ACP mode, the CLI does not accept arbitrary slash commands like
|
|
||||||
// "/chat save". To ensure we never block on unsupported features,
|
|
||||||
// persist checkpoints directly to ~/.qwen/tmp using our SessionManager.
|
|
||||||
const qwenMessages = messages.map((m) => ({
|
|
||||||
// Generate minimal QwenMessage shape expected by the writer
|
|
||||||
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
type: m.role === 'user' ? ('user' as const) : ('qwen' as const),
|
|
||||||
content: m.content,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const tag = await this.sessionManager.saveCheckpoint(
|
|
||||||
qwenMessages,
|
|
||||||
conversationId,
|
|
||||||
this.currentWorkingDir,
|
|
||||||
this.currentSessionId || undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
return { success: true, tag };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[QwenAgentManager] ===== CHECKPOINT SAVE FAILED =====');
|
|
||||||
console.error('[QwenAgentManager] Error:', error);
|
|
||||||
console.error(
|
|
||||||
'[QwenAgentManager] Error stack:',
|
|
||||||
error instanceof Error ? error.stack : 'N/A',
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: error instanceof Error ? error.message : String(error),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save session directly to file system (without relying on ACP)
|
|
||||||
*
|
|
||||||
* @param messages - Current session messages
|
|
||||||
* @param sessionName - Session name
|
|
||||||
* @returns Save result
|
|
||||||
*/
|
|
||||||
async saveSessionDirect(
|
|
||||||
messages: ChatMessage[],
|
|
||||||
sessionName: string,
|
|
||||||
): Promise<{ success: boolean; sessionId?: string; message?: string }> {
|
|
||||||
// Use checkpoint format instead of session format
|
|
||||||
// This matches CLI's /chat save behavior
|
|
||||||
return this.saveCheckpoint(messages, sessionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to load session via ACP session/load method
|
* Try to load session via ACP session/load method
|
||||||
* This method will only be used if CLI version supports it
|
* This method will only be used if CLI version supports it
|
||||||
@@ -1152,16 +1082,6 @@ export class QwenAgentManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -54,9 +54,18 @@ export class QwenConnectionHandler {
|
|||||||
// Show warning if CLI version is below minimum requirement
|
// Show warning if CLI version is below minimum requirement
|
||||||
if (!versionInfo.isSupported) {
|
if (!versionInfo.isSupported) {
|
||||||
// Wait to determine release version number
|
// Wait to determine release version number
|
||||||
vscode.window.showWarningMessage(
|
const selection = await 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 ${MIN_CLI_VERSION_FOR_SESSION_METHODS} or later.`,
|
`Qwen Code CLI version ${versionInfo.version} is below the minimum required version. Some features may not work properly. Please upgrade to version ${MIN_CLI_VERSION_FOR_SESSION_METHODS} or later.`,
|
||||||
|
'Upgrade Now',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle the user's selection
|
||||||
|
if (selection === 'Upgrade Now') {
|
||||||
|
// Open terminal and run npm install command
|
||||||
|
const terminal = vscode.window.createTerminal('Qwen Code CLI Upgrade');
|
||||||
|
terminal.show();
|
||||||
|
terminal.sendText('npm install -g @qwen-code/qwen-code@latest');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = vscode.workspace.getConfiguration('qwenCode');
|
const config = vscode.workspace.getConfiguration('qwenCode');
|
||||||
|
|||||||
@@ -51,131 +51,7 @@ export class QwenSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save current conversation as a checkpoint (matching CLI's /chat save format)
|
* Save current conversation as a named session
|
||||||
* Creates checkpoint with BOTH conversationId and sessionId as tags for compatibility
|
|
||||||
*
|
|
||||||
* @param messages - Current conversation messages
|
|
||||||
* @param conversationId - Conversation ID (from VSCode extension)
|
|
||||||
* @param sessionId - Session ID (from CLI tmp session file, optional)
|
|
||||||
* @param workingDir - Current working directory
|
|
||||||
* @returns Checkpoint tag
|
|
||||||
*/
|
|
||||||
async saveCheckpoint(
|
|
||||||
messages: QwenMessage[],
|
|
||||||
conversationId: string,
|
|
||||||
workingDir: string,
|
|
||||||
sessionId?: string,
|
|
||||||
): Promise<string> {
|
|
||||||
try {
|
|
||||||
console.log('[QwenSessionManager] ===== SAVEPOINT START =====');
|
|
||||||
console.log('[QwenSessionManager] Conversation ID:', conversationId);
|
|
||||||
console.log(
|
|
||||||
'[QwenSessionManager] Session ID:',
|
|
||||||
sessionId || 'not provided',
|
|
||||||
);
|
|
||||||
console.log('[QwenSessionManager] Working dir:', workingDir);
|
|
||||||
console.log('[QwenSessionManager] Message count:', messages.length);
|
|
||||||
|
|
||||||
// Get project directory (parent of chats directory)
|
|
||||||
const projectHash = this.getProjectHash(workingDir);
|
|
||||||
console.log('[QwenSessionManager] Project hash:', projectHash);
|
|
||||||
|
|
||||||
const projectDir = path.join(this.qwenDir, 'tmp', projectHash);
|
|
||||||
console.log('[QwenSessionManager] Project dir:', projectDir);
|
|
||||||
|
|
||||||
if (!fs.existsSync(projectDir)) {
|
|
||||||
console.log('[QwenSessionManager] Creating project directory...');
|
|
||||||
fs.mkdirSync(projectDir, { recursive: true });
|
|
||||||
console.log('[QwenSessionManager] Directory created');
|
|
||||||
} else {
|
|
||||||
console.log('[QwenSessionManager] Project directory already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert messages to checkpoint format (Gemini-style messages)
|
|
||||||
console.log(
|
|
||||||
'[QwenSessionManager] Converting messages to checkpoint format...',
|
|
||||||
);
|
|
||||||
const checkpointMessages = messages.map((msg, index) => {
|
|
||||||
console.log(
|
|
||||||
`[QwenSessionManager] Message ${index}: type=${msg.type}, contentLength=${msg.content?.length || 0}`,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
role: msg.type === 'user' ? 'user' : 'model',
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
text: msg.content,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
'[QwenSessionManager] Converted',
|
|
||||||
checkpointMessages.length,
|
|
||||||
'messages',
|
|
||||||
);
|
|
||||||
|
|
||||||
const jsonContent = JSON.stringify(checkpointMessages, null, 2);
|
|
||||||
console.log(
|
|
||||||
'[QwenSessionManager] JSON content length:',
|
|
||||||
jsonContent.length,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save with conversationId as primary tag
|
|
||||||
const convFilename = `checkpoint-${conversationId}.json`;
|
|
||||||
const convFilePath = path.join(projectDir, convFilename);
|
|
||||||
console.log(
|
|
||||||
'[QwenSessionManager] Saving checkpoint with conversationId:',
|
|
||||||
convFilePath,
|
|
||||||
);
|
|
||||||
fs.writeFileSync(convFilePath, jsonContent, 'utf-8');
|
|
||||||
|
|
||||||
// Also save with sessionId if provided (for compatibility with CLI session/load)
|
|
||||||
if (sessionId) {
|
|
||||||
const sessionFilename = `checkpoint-${sessionId}.json`;
|
|
||||||
const sessionFilePath = path.join(projectDir, sessionFilename);
|
|
||||||
console.log(
|
|
||||||
'[QwenSessionManager] Also saving checkpoint with sessionId:',
|
|
||||||
sessionFilePath,
|
|
||||||
);
|
|
||||||
fs.writeFileSync(sessionFilePath, jsonContent, 'utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify primary file exists
|
|
||||||
if (fs.existsSync(convFilePath)) {
|
|
||||||
const stats = fs.statSync(convFilePath);
|
|
||||||
console.log(
|
|
||||||
'[QwenSessionManager] Primary checkpoint verified, size:',
|
|
||||||
stats.size,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
'[QwenSessionManager] ERROR: Primary checkpoint does not exist after write!',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[QwenSessionManager] ===== CHECKPOINT SAVED =====');
|
|
||||||
console.log('[QwenSessionManager] Primary path:', convFilePath);
|
|
||||||
if (sessionId) {
|
|
||||||
console.log(
|
|
||||||
'[QwenSessionManager] Secondary path (sessionId):',
|
|
||||||
path.join(projectDir, `checkpoint-${sessionId}.json`),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return conversationId;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[QwenSessionManager] ===== CHECKPOINT SAVE FAILED =====');
|
|
||||||
console.error('[QwenSessionManager] Error:', error);
|
|
||||||
console.error(
|
|
||||||
'[QwenSessionManager] Error stack:',
|
|
||||||
error instanceof Error ? error.stack : 'N/A',
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save current conversation as a named session (checkpoint-like functionality)
|
|
||||||
*
|
*
|
||||||
* @param messages - Current conversation messages
|
* @param messages - Current conversation messages
|
||||||
* @param sessionName - Name/tag for the saved session
|
* @param sessionName - Name/tag for the saved session
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
|
import * as readline from 'readline';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
export interface QwenMessage {
|
export interface QwenMessage {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -32,6 +34,9 @@ export interface QwenSession {
|
|||||||
lastUpdated: string;
|
lastUpdated: string;
|
||||||
messages: QwenMessage[];
|
messages: QwenMessage[];
|
||||||
filePath?: string;
|
filePath?: string;
|
||||||
|
messageCount?: number;
|
||||||
|
firstUserText?: string;
|
||||||
|
cwd?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QwenSessionReader {
|
export class QwenSessionReader {
|
||||||
@@ -96,11 +101,17 @@ export class QwenSessionReader {
|
|||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = fs
|
const files = fs.readdirSync(chatsDir);
|
||||||
.readdirSync(chatsDir)
|
|
||||||
.filter((f) => f.startsWith('session-') && f.endsWith('.json'));
|
|
||||||
|
|
||||||
for (const file of files) {
|
const jsonSessionFiles = files.filter(
|
||||||
|
(f) => f.startsWith('session-') && f.endsWith('.json'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const jsonlSessionFiles = files.filter((f) =>
|
||||||
|
/^[0-9a-fA-F-]{32,36}\.jsonl$/.test(f),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const file of jsonSessionFiles) {
|
||||||
const filePath = path.join(chatsDir, file);
|
const filePath = path.join(chatsDir, file);
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
@@ -116,6 +127,23 @@ export class QwenSessionReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Support new JSONL session format produced by the CLI
|
||||||
|
for (const file of jsonlSessionFiles) {
|
||||||
|
const filePath = path.join(chatsDir, file);
|
||||||
|
try {
|
||||||
|
const session = await this.readJsonlSession(filePath, false);
|
||||||
|
if (session) {
|
||||||
|
sessions.push(session);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'[QwenSessionReader] Failed to read JSONL session file:',
|
||||||
|
filePath,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +156,25 @@ export class QwenSessionReader {
|
|||||||
): Promise<QwenSession | null> {
|
): Promise<QwenSession | null> {
|
||||||
// First try to find in all projects
|
// First try to find in all projects
|
||||||
const sessions = await this.getAllSessions(undefined, true);
|
const sessions = await this.getAllSessions(undefined, true);
|
||||||
return sessions.find((s) => s.sessionId === sessionId) || null;
|
const found = sessions.find((s) => s.sessionId === sessionId);
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the session points to a JSONL file, load full content on demand
|
||||||
|
if (
|
||||||
|
found.filePath &&
|
||||||
|
found.filePath.endsWith('.jsonl') &&
|
||||||
|
found.messages.length === 0
|
||||||
|
) {
|
||||||
|
const hydrated = await this.readJsonlSession(found.filePath, true);
|
||||||
|
if (hydrated) {
|
||||||
|
return hydrated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,7 +182,6 @@ export class QwenSessionReader {
|
|||||||
* Qwen CLI uses SHA256 hash of project path
|
* Qwen CLI uses SHA256 hash of project path
|
||||||
*/
|
*/
|
||||||
private async getProjectHash(workingDir: string): Promise<string> {
|
private async getProjectHash(workingDir: string): Promise<string> {
|
||||||
const crypto = await import('crypto');
|
|
||||||
return crypto.createHash('sha256').update(workingDir).digest('hex');
|
return crypto.createHash('sha256').update(workingDir).digest('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +189,14 @@ export class QwenSessionReader {
|
|||||||
* Get session title (based on first user message)
|
* Get session title (based on first user message)
|
||||||
*/
|
*/
|
||||||
getSessionTitle(session: QwenSession): string {
|
getSessionTitle(session: QwenSession): string {
|
||||||
|
// Prefer cached prompt text to avoid loading messages for JSONL sessions
|
||||||
|
if (session.firstUserText) {
|
||||||
|
return (
|
||||||
|
session.firstUserText.substring(0, 50) +
|
||||||
|
(session.firstUserText.length > 50 ? '...' : '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const firstUserMessage = session.messages.find((m) => m.type === 'user');
|
const firstUserMessage = session.messages.find((m) => m.type === 'user');
|
||||||
if (firstUserMessage) {
|
if (firstUserMessage) {
|
||||||
// Extract first 50 characters as title
|
// Extract first 50 characters as title
|
||||||
@@ -155,6 +208,137 @@ export class QwenSessionReader {
|
|||||||
return 'Untitled Session';
|
return 'Untitled Session';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a JSONL session file written by the CLI.
|
||||||
|
* When includeMessages is false, only lightweight metadata is returned.
|
||||||
|
*/
|
||||||
|
private async readJsonlSession(
|
||||||
|
filePath: string,
|
||||||
|
includeMessages: boolean,
|
||||||
|
): Promise<QwenSession | null> {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
const fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' });
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: fileStream,
|
||||||
|
crlfDelay: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
const messages: QwenMessage[] = [];
|
||||||
|
const seenUuids = new Set<string>();
|
||||||
|
let sessionId: string | undefined;
|
||||||
|
let startTime: string | undefined;
|
||||||
|
let firstUserText: string | undefined;
|
||||||
|
let cwd: string | undefined;
|
||||||
|
|
||||||
|
for await (const line of rl) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
obj = JSON.parse(trimmed) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sessionId && typeof obj.sessionId === 'string') {
|
||||||
|
sessionId = obj.sessionId;
|
||||||
|
}
|
||||||
|
if (!startTime && typeof obj.timestamp === 'string') {
|
||||||
|
startTime = obj.timestamp;
|
||||||
|
}
|
||||||
|
if (!cwd && typeof obj.cwd === 'string') {
|
||||||
|
cwd = obj.cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = typeof obj.type === 'string' ? obj.type : '';
|
||||||
|
if (type === 'user' || type === 'assistant') {
|
||||||
|
const uuid = typeof obj.uuid === 'string' ? obj.uuid : undefined;
|
||||||
|
if (uuid) {
|
||||||
|
seenUuids.add(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = this.contentToText(obj.message);
|
||||||
|
if (includeMessages) {
|
||||||
|
messages.push({
|
||||||
|
id: uuid || `${messages.length}`,
|
||||||
|
timestamp: typeof obj.timestamp === 'string' ? obj.timestamp : '',
|
||||||
|
type: type === 'user' ? 'user' : 'qwen',
|
||||||
|
content: text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!firstUserText && type === 'user' && text) {
|
||||||
|
firstUserText = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure stream is closed
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
if (!sessionId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectHash = cwd
|
||||||
|
? await this.getProjectHash(cwd)
|
||||||
|
: path.basename(path.dirname(path.dirname(filePath)));
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessionId,
|
||||||
|
projectHash,
|
||||||
|
startTime: startTime || new Date(stats.birthtimeMs).toISOString(),
|
||||||
|
lastUpdated: new Date(stats.mtimeMs).toISOString(),
|
||||||
|
messages: includeMessages ? messages : [],
|
||||||
|
filePath,
|
||||||
|
messageCount: seenUuids.size,
|
||||||
|
firstUserText,
|
||||||
|
cwd,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'[QwenSessionReader] Failed to parse JSONL session:',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract plain text from CLI Content structure
|
||||||
|
private contentToText(message: unknown): string {
|
||||||
|
try {
|
||||||
|
if (typeof message !== 'object' || message === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const typed = message as { parts?: unknown[] };
|
||||||
|
const parts = Array.isArray(typed.parts) ? typed.parts : [];
|
||||||
|
const texts: string[] = [];
|
||||||
|
for (const part of parts) {
|
||||||
|
if (typeof part !== 'object' || part === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const p = part as Record<string, unknown>;
|
||||||
|
if (typeof p.text === 'string') {
|
||||||
|
texts.push(p.text);
|
||||||
|
} else if (typeof p.data === 'string') {
|
||||||
|
texts.push(p.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return texts.join('\n');
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete session file
|
* Delete session file
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -73,11 +73,4 @@ export class MessageHandler {
|
|||||||
appendStreamContent(chunk: string): void {
|
appendStreamContent(chunk: string): void {
|
||||||
this.router.appendStreamContent(chunk);
|
this.router.appendStreamContent(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if saving checkpoint
|
|
||||||
*/
|
|
||||||
getIsSavingCheckpoint(): boolean {
|
|
||||||
return this.router.getIsSavingCheckpoint();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
PlanModeIcon,
|
PlanModeIcon,
|
||||||
CodeBracketsIcon,
|
CodeBracketsIcon,
|
||||||
HideContextIcon,
|
HideContextIcon,
|
||||||
ThinkingIcon,
|
// ThinkingIcon, // Temporarily disabled
|
||||||
SlashCommandIcon,
|
SlashCommandIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
@@ -92,7 +92,7 @@ export const InputForm: React.FC<InputFormProps> = ({
|
|||||||
isWaitingForResponse,
|
isWaitingForResponse,
|
||||||
isComposing,
|
isComposing,
|
||||||
editMode,
|
editMode,
|
||||||
thinkingEnabled,
|
// thinkingEnabled, // Temporarily disabled
|
||||||
activeFileName,
|
activeFileName,
|
||||||
activeSelection,
|
activeSelection,
|
||||||
skipAutoActiveContext,
|
skipAutoActiveContext,
|
||||||
@@ -103,7 +103,7 @@ export const InputForm: React.FC<InputFormProps> = ({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
onToggleEditMode,
|
onToggleEditMode,
|
||||||
onToggleThinking,
|
// onToggleThinking, // Temporarily disabled
|
||||||
onToggleSkipAutoActiveContext,
|
onToggleSkipAutoActiveContext,
|
||||||
onShowCommandMenu,
|
onShowCommandMenu,
|
||||||
onAttachContext,
|
onAttachContext,
|
||||||
@@ -236,15 +236,16 @@ export const InputForm: React.FC<InputFormProps> = ({
|
|||||||
{/* Spacer */}
|
{/* Spacer */}
|
||||||
<div className="flex-1 min-w-0" />
|
<div className="flex-1 min-w-0" />
|
||||||
|
|
||||||
|
{/* @yiliang114. closed temporarily */}
|
||||||
{/* Thinking button */}
|
{/* Thinking button */}
|
||||||
<button
|
{/* <button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn-icon-compact ${thinkingEnabled ? 'btn-icon-compact--active' : ''}`}
|
className={`btn-icon-compact ${thinkingEnabled ? 'btn-icon-compact--active' : ''}`}
|
||||||
title={thinkingEnabled ? 'Thinking on' : 'Thinking off'}
|
title={thinkingEnabled ? 'Thinking on' : 'Thinking off'}
|
||||||
onClick={onToggleThinking}
|
onClick={onToggleThinking}
|
||||||
>
|
>
|
||||||
<ThinkingIcon enabled={thinkingEnabled} />
|
<ThinkingIcon enabled={thinkingEnabled} />
|
||||||
</button>
|
</button> */}
|
||||||
|
|
||||||
{/* Command button */}
|
{/* Command button */}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -150,11 +150,4 @@ export class MessageRouter {
|
|||||||
appendStreamContent(chunk: string): void {
|
appendStreamContent(chunk: string): void {
|
||||||
this.sessionHandler.appendStreamContent(chunk);
|
this.sessionHandler.appendStreamContent(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if saving checkpoint
|
|
||||||
*/
|
|
||||||
getIsSavingCheckpoint(): boolean {
|
|
||||||
return this.sessionHandler.getIsSavingCheckpoint();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import type { ApprovalModeValue } from '../../types/approvalModeValueTypes.js';
|
|||||||
*/
|
*/
|
||||||
export class SessionMessageHandler extends BaseMessageHandler {
|
export class SessionMessageHandler extends BaseMessageHandler {
|
||||||
private currentStreamContent = '';
|
private currentStreamContent = '';
|
||||||
private isSavingCheckpoint = false;
|
|
||||||
private loginHandler: (() => Promise<void>) | null = null;
|
private loginHandler: (() => Promise<void>) | null = null;
|
||||||
private isTitleSet = false; // Flag to track if title has been set
|
private isTitleSet = false; // Flag to track if title has been set
|
||||||
|
|
||||||
@@ -153,13 +152,6 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
this.currentStreamContent = '';
|
this.currentStreamContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if saving checkpoint
|
|
||||||
*/
|
|
||||||
getIsSavingCheckpoint(): boolean {
|
|
||||||
return this.isSavingCheckpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt user to login and invoke the registered login handler/command.
|
* Prompt user to login and invoke the registered login handler/command.
|
||||||
* Returns true if a login was initiated.
|
* Returns true if a login was initiated.
|
||||||
@@ -385,41 +377,6 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
type: 'streamEnd',
|
type: 'streamEnd',
|
||||||
data: { timestamp: Date.now() },
|
data: { timestamp: Date.now() },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-save checkpoint
|
|
||||||
if (this.currentConversationId) {
|
|
||||||
try {
|
|
||||||
const conversation = await this.conversationStore.getConversation(
|
|
||||||
this.currentConversationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const messages = conversation?.messages || [];
|
|
||||||
|
|
||||||
this.isSavingCheckpoint = true;
|
|
||||||
|
|
||||||
const result = await this.agentManager.saveCheckpoint(
|
|
||||||
messages,
|
|
||||||
this.currentConversationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.isSavingCheckpoint = false;
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
console.log(
|
|
||||||
'[SessionMessageHandler] Checkpoint saved:',
|
|
||||||
result.tag,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
'[SessionMessageHandler] Checkpoint save failed:',
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
this.isSavingCheckpoint = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[SessionMessageHandler] Error sending message:', error);
|
console.error('[SessionMessageHandler] Error sending message:', error);
|
||||||
|
|
||||||
@@ -493,23 +450,6 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save current session before creating new one
|
|
||||||
if (this.currentConversationId && this.agentManager.isConnected) {
|
|
||||||
try {
|
|
||||||
const conversation = await this.conversationStore.getConversation(
|
|
||||||
this.currentConversationId,
|
|
||||||
);
|
|
||||||
const messages = conversation?.messages || [];
|
|
||||||
|
|
||||||
await this.agentManager.saveCheckpoint(
|
|
||||||
messages,
|
|
||||||
this.currentConversationId,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[SessionMessageHandler] Failed to auto-save:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
@@ -589,27 +529,6 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save current session before switching
|
|
||||||
if (
|
|
||||||
this.currentConversationId &&
|
|
||||||
this.currentConversationId !== sessionId &&
|
|
||||||
this.agentManager.isConnected
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const conversation = await this.conversationStore.getConversation(
|
|
||||||
this.currentConversationId,
|
|
||||||
);
|
|
||||||
const messages = conversation?.messages || [];
|
|
||||||
|
|
||||||
await this.agentManager.saveCheckpoint(
|
|
||||||
messages,
|
|
||||||
this.currentConversationId,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[SessionMessageHandler] Failed to auto-save:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get session details (includes cwd and filePath when using ACP)
|
// Get session details (includes cwd and filePath when using ACP)
|
||||||
let sessionDetails: Record<string, unknown> | null = null;
|
let sessionDetails: Record<string, unknown> | null = null;
|
||||||
try {
|
try {
|
||||||
@@ -852,11 +771,6 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
throw new Error('No active conversation to save');
|
throw new Error('No active conversation to save');
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversation = await this.conversationStore.getConversation(
|
|
||||||
this.currentConversationId,
|
|
||||||
);
|
|
||||||
const messages = conversation?.messages || [];
|
|
||||||
|
|
||||||
// Try ACP save first
|
// Try ACP save first
|
||||||
try {
|
try {
|
||||||
const response = await this.agentManager.saveSessionViaAcp(
|
const response = await this.agentManager.saveSessionViaAcp(
|
||||||
@@ -891,17 +805,6 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to direct save
|
|
||||||
const response = await this.agentManager.saveSessionDirect(
|
|
||||||
messages,
|
|
||||||
tag,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.sendToWebView({
|
|
||||||
type: 'saveSessionResponse',
|
|
||||||
data: response,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.handleGetQwenSessions();
|
await this.handleGetQwenSessions();
|
||||||
@@ -1036,20 +939,6 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to direct load
|
|
||||||
const messages = await this.agentManager.loadSessionDirect(sessionId);
|
|
||||||
|
|
||||||
if (messages) {
|
|
||||||
this.currentConversationId = sessionId;
|
|
||||||
|
|
||||||
this.sendToWebView({
|
|
||||||
type: 'qwenSessionSwitched',
|
|
||||||
data: { sessionId, messages },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error('Failed to load session');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.handleGetQwenSessions();
|
await this.handleGetQwenSessions();
|
||||||
|
|||||||
Reference in New Issue
Block a user