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:
yiliang114
2025-12-09 00:14:35 +08:00
parent f4edcc5cd2
commit 58b9e477bc
3 changed files with 79 additions and 16 deletions

View File

@@ -37,6 +37,10 @@ export class QwenAgentManager {
private connectionHandler: QwenConnectionHandler;
private sessionUpdateHandler: QwenSessionUpdateHandler;
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)
// can reuse it and avoid forcing a fresh authentication unnecessarily.
private defaultAuthStateManager?: AuthStateManager;
@@ -53,6 +57,55 @@ export class QwenAgentManager {
// Set ACP connection callbacks
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);
};
@@ -986,6 +1039,12 @@ export class QwenAgentManager {
}
try {
// Route upcoming session/update messages as discrete messages for replay
this.rehydratingSessionId = sessionId;
console.log(
'[QwenAgentManager] Rehydration start for session:',
sessionId,
);
console.log(
'[QwenAgentManager] Attempting session/load via ACP for session:',
sessionId,
@@ -1032,6 +1091,10 @@ export class QwenAgentManager {
}
throw error;
} finally {
// End rehydration routing regardless of outcome
console.log('[QwenAgentManager] Rehydration end for session:', sessionId);
this.rehydratingSessionId = null;
}
}

View File

@@ -15,6 +15,10 @@ import type { QwenSession, QwenMessage } from './qwenSessionReader.js';
*
* This service provides direct filesystem access to save and load sessions
* 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 {
private qwenDir: string;

View File

@@ -598,24 +598,22 @@ export class SessionMessageHandler extends BaseMessageHandler {
// Try to load session via ACP (now we should be connected)
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(
sessionId,
(sessionDetails?.cwd as string | undefined) || undefined,
);
console.log(
'[SessionMessageHandler] session/load succeeded:',
'[SessionMessageHandler] session/load succeeded (per ACP spec result is null; actual history comes via session/update):',
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
this.isTitleSet = false;
@@ -1029,17 +1027,15 @@ export class SessionMessageHandler extends BaseMessageHandler {
// Try ACP load first
try {
await this.agentManager.loadSessionViaAcp(sessionId);
// Pre-clear UI so replayed updates append afterwards
this.currentConversationId = sessionId;
const messages = await this.agentManager.getSessionMessages(sessionId);
this.sendToWebView({
type: 'qwenSessionSwitched',
data: { sessionId, messages },
data: { sessionId, messages: [] },
});
await this.agentManager.loadSessionViaAcp(sessionId);
// Reset title flag when resuming sessions
this.isTitleSet = false;