mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat(vscode-ide-companion): implement session rehydration for loading past conversations
- Add rehydratingSessionId flag to track session loading state - Route message chunks as discrete messages during rehydration instead of streaming - Update session handlers to properly manage conversation switching - Improve session manager documentation
This commit is contained in:
@@ -37,6 +37,10 @@ export class QwenAgentManager {
|
|||||||
private connectionHandler: QwenConnectionHandler;
|
private connectionHandler: QwenConnectionHandler;
|
||||||
private sessionUpdateHandler: QwenSessionUpdateHandler;
|
private sessionUpdateHandler: QwenSessionUpdateHandler;
|
||||||
private currentWorkingDir: string = process.cwd();
|
private currentWorkingDir: string = process.cwd();
|
||||||
|
// When loading a past session via ACP, the CLI replays history through
|
||||||
|
// session/update notifications. We set this flag to route message chunks
|
||||||
|
// (user/assistant) as discrete chat messages instead of live streaming.
|
||||||
|
private rehydratingSessionId: string | null = null;
|
||||||
// Cache the last used AuthStateManager so internal calls (e.g. fallback paths)
|
// Cache the last used AuthStateManager so internal calls (e.g. fallback paths)
|
||||||
// can reuse it and avoid forcing a fresh authentication unnecessarily.
|
// can reuse it and avoid forcing a fresh authentication unnecessarily.
|
||||||
private defaultAuthStateManager?: AuthStateManager;
|
private defaultAuthStateManager?: AuthStateManager;
|
||||||
@@ -53,6 +57,55 @@ export class QwenAgentManager {
|
|||||||
|
|
||||||
// Set ACP connection callbacks
|
// Set ACP connection callbacks
|
||||||
this.connection.onSessionUpdate = (data: AcpSessionUpdate) => {
|
this.connection.onSessionUpdate = (data: AcpSessionUpdate) => {
|
||||||
|
// If we are rehydrating a loaded session, map message chunks into
|
||||||
|
// full messages for the UI, instead of streaming behavior.
|
||||||
|
try {
|
||||||
|
const targetId = this.rehydratingSessionId;
|
||||||
|
if (
|
||||||
|
targetId &&
|
||||||
|
typeof data === 'object' &&
|
||||||
|
data &&
|
||||||
|
'update' in data &&
|
||||||
|
(data as { sessionId?: string }).sessionId === targetId
|
||||||
|
) {
|
||||||
|
const update = (
|
||||||
|
data as unknown as {
|
||||||
|
update: { sessionUpdate: string; content?: { text?: string } };
|
||||||
|
}
|
||||||
|
).update;
|
||||||
|
const text = update?.content?.text || '';
|
||||||
|
if (update?.sessionUpdate === 'user_message_chunk' && text) {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Rehydration: routing user message chunk',
|
||||||
|
);
|
||||||
|
this.callbacks.onMessage?.({
|
||||||
|
role: 'user',
|
||||||
|
content: text,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (update?.sessionUpdate === 'agent_message_chunk' && text) {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Rehydration: routing agent message chunk',
|
||||||
|
);
|
||||||
|
this.callbacks.onMessage?.({
|
||||||
|
role: 'assistant',
|
||||||
|
content: text,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// For other types during rehydration, fall through to normal handler
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Rehydration: non-text update, forwarding to handler',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[QwenAgentManager] Rehydration routing failed:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default handling path
|
||||||
this.sessionUpdateHandler.handleSessionUpdate(data);
|
this.sessionUpdateHandler.handleSessionUpdate(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -986,6 +1039,12 @@ export class QwenAgentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Route upcoming session/update messages as discrete messages for replay
|
||||||
|
this.rehydratingSessionId = sessionId;
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Rehydration start for session:',
|
||||||
|
sessionId,
|
||||||
|
);
|
||||||
console.log(
|
console.log(
|
||||||
'[QwenAgentManager] Attempting session/load via ACP for session:',
|
'[QwenAgentManager] Attempting session/load via ACP for session:',
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -1032,6 +1091,10 @@ export class QwenAgentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
// End rehydration routing regardless of outcome
|
||||||
|
console.log('[QwenAgentManager] Rehydration end for session:', sessionId);
|
||||||
|
this.rehydratingSessionId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ import type { QwenSession, QwenMessage } from './qwenSessionReader.js';
|
|||||||
*
|
*
|
||||||
* This service provides direct filesystem access to save and load sessions
|
* This service provides direct filesystem access to save and load sessions
|
||||||
* without relying on the CLI's ACP session/save method.
|
* without relying on the CLI's ACP session/save method.
|
||||||
|
*
|
||||||
|
* Note: This is primarily used as a fallback mechanism when ACP methods are
|
||||||
|
* unavailable or fail. In normal operation, ACP session/list and session/load
|
||||||
|
* should be preferred for consistency with the CLI.
|
||||||
*/
|
*/
|
||||||
export class QwenSessionManager {
|
export class QwenSessionManager {
|
||||||
private qwenDir: string;
|
private qwenDir: string;
|
||||||
|
|||||||
@@ -598,24 +598,22 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
|
|
||||||
// Try to load session via ACP (now we should be connected)
|
// Try to load session via ACP (now we should be connected)
|
||||||
try {
|
try {
|
||||||
|
// Set current id and clear UI first so replayed updates append afterwards
|
||||||
|
this.currentConversationId = sessionId;
|
||||||
|
this.sendToWebView({
|
||||||
|
type: 'qwenSessionSwitched',
|
||||||
|
data: { sessionId, messages: [], session: sessionDetails },
|
||||||
|
});
|
||||||
|
|
||||||
const loadResponse = await this.agentManager.loadSessionViaAcp(
|
const loadResponse = await this.agentManager.loadSessionViaAcp(
|
||||||
sessionId,
|
sessionId,
|
||||||
(sessionDetails?.cwd as string | undefined) || undefined,
|
(sessionDetails?.cwd as string | undefined) || undefined,
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
'[SessionMessageHandler] session/load succeeded:',
|
'[SessionMessageHandler] session/load succeeded (per ACP spec result is null; actual history comes via session/update):',
|
||||||
loadResponse,
|
loadResponse,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.currentConversationId = sessionId;
|
|
||||||
|
|
||||||
const messages = await this.agentManager.getSessionMessages(sessionId);
|
|
||||||
|
|
||||||
this.sendToWebView({
|
|
||||||
type: 'qwenSessionSwitched',
|
|
||||||
data: { sessionId, messages, session: sessionDetails },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset title flag when switching sessions
|
// Reset title flag when switching sessions
|
||||||
this.isTitleSet = false;
|
this.isTitleSet = false;
|
||||||
|
|
||||||
@@ -1029,17 +1027,15 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||||||
|
|
||||||
// Try ACP load first
|
// Try ACP load first
|
||||||
try {
|
try {
|
||||||
await this.agentManager.loadSessionViaAcp(sessionId);
|
// Pre-clear UI so replayed updates append afterwards
|
||||||
|
|
||||||
this.currentConversationId = sessionId;
|
this.currentConversationId = sessionId;
|
||||||
|
|
||||||
const messages = await this.agentManager.getSessionMessages(sessionId);
|
|
||||||
|
|
||||||
this.sendToWebView({
|
this.sendToWebView({
|
||||||
type: 'qwenSessionSwitched',
|
type: 'qwenSessionSwitched',
|
||||||
data: { sessionId, messages },
|
data: { sessionId, messages: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.agentManager.loadSessionViaAcp(sessionId);
|
||||||
|
|
||||||
// Reset title flag when resuming sessions
|
// Reset title flag when resuming sessions
|
||||||
this.isTitleSet = false;
|
this.isTitleSet = false;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user