fix: chat history persistence and webview state restoration

This commit is contained in:
g1nation
2026-05-01 19:41:00 +09:00
parent e696d29f2f
commit ac902c1841
4 changed files with 57 additions and 14 deletions
+9
View File
@@ -1,3 +1,12 @@
# Patch Notes - v2.33.6 (2026-05-01)
## 🛠️ Critical Bug Fixes
- **Chat Persistence Overhaul:** Resolved an issue where chat history disappeared after switching sidebar tabs.
- **Webview State Management:** Implemented `vscode.setState` for instant UI restoration.
- **Internal Message Logic:** Refined message filtering to ensure intermediate agent steps are visible to the user.
---
# Patch Notes - v2.33.5 (2026-05-01) # Patch Notes - v2.33.5 (2026-05-01)
## 🎨 UI Refinement ## 🎨 UI Refinement
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "g1nation", "name": "g1nation",
"displayName": "G1nation", "displayName": "G1nation",
"description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.", "description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.",
"version": "2.33.5", "version": "2.33.6",
"publisher": "connectailab", "publisher": "connectailab",
"license": "MIT", "license": "MIT",
"icon": "assets/icon.png", "icon": "assets/icon.png",
+2 -3
View File
@@ -119,7 +119,7 @@ export class AgentExecutor {
} }
public getHistory() { public getHistory() {
return this.chatHistory.filter(message => !message.internal); return this.chatHistory.filter(message => !message.internal || message.role === 'assistant');
} }
public setHistory(history: ChatMessage[]) { public setHistory(history: ChatMessage[]) {
@@ -394,7 +394,7 @@ export class AgentExecutor {
// 5. Execute Actions // 5. Execute Actions
const rationale = this.parseRationale(aiResponseText); const rationale = this.parseRationale(aiResponseText);
const assistantMessage: ChatMessage = { role: 'assistant', content: aiResponseText, internal: true, rationale }; const assistantMessage: ChatMessage = { role: 'assistant', content: aiResponseText, internal: false, rationale };
this.chatHistory.push(assistantMessage); this.chatHistory.push(assistantMessage);
this.statusBarManager.updateStatus(AgentStatus.Executing); this.statusBarManager.updateStatus(AgentStatus.Executing);
@@ -443,7 +443,6 @@ export class AgentExecutor {
return; return;
} }
assistantMessage.internal = false;
this.emitHistoryChanged(); this.emitHistoryChanged();
this.statusBarManager.updateStatus(AgentStatus.Success); this.statusBarManager.updateStatus(AgentStatus.Success);
this.webview.postMessage({ type: 'streamChunk', value: aiResponseText }); this.webview.postMessage({ type: 'streamChunk', value: aiResponseText });
+45 -10
View File
@@ -200,7 +200,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
if (!this._view) return; if (!this._view) return;
const blankChatActive = this._context.globalState.get<boolean>(SidebarChatProvider.blankChatStateKey, false); const blankChatActive = this._context.globalState.get<boolean>(SidebarChatProvider.blankChatStateKey, false);
if (blankChatActive) { const currentHistory = this._agent.getHistory();
if (blankChatActive && currentHistory.length === 0) {
return; return;
} }
@@ -212,7 +214,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
await this._context.globalState.update(SidebarChatProvider.activeSessionStateKey, null); await this._context.globalState.update(SidebarChatProvider.activeSessionStateKey, null);
} }
const currentHistory = this._agent.getHistory();
if (currentHistory.length > 0) { if (currentHistory.length > 0) {
this._view.webview.postMessage({ type: 'restoreHistory', value: currentHistory }); this._view.webview.postMessage({ type: 'restoreHistory', value: currentHistory });
await this._persistLastVisibleChat(currentHistory); await this._persistLastVisibleChat(currentHistory);
@@ -1893,6 +1894,29 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
const vscode = acquireVsCodeApi(); const vscode = acquireVsCodeApi();
const chat = document.getElementById('chat'); const chat = document.getElementById('chat');
const input = document.getElementById('input'); const input = document.getElementById('input');
// [State Persistence - Tier 0] 즉시 복원 (Instant Restore from WebView State)
const previousState = vscode.getState();
if (previousState && previousState.history && previousState.history.length > 0) {
console.log('[G1nation] Restoring from Webview State...');
renderHistory(previousState.history);
}
function saveWebviewState(history) {
vscode.setState({ history });
}
function renderHistory(history) {
if (!history || history.length === 0) return;
chat.innerHTML = '';
history.forEach(m => {
if (!m) return;
// Only skip truly internal system messages, keep assistant thoughts
if (m.role === 'system' && m.internal) return;
addMsg(m.content, m.role === 'assistant' ? 'assistant' : 'user', m.rationale);
});
chat.scrollTop = chat.scrollHeight;
}
const sendBtn = document.getElementById('sendBtn'); const sendBtn = document.getElementById('sendBtn');
const stopBtn = document.getElementById('stopBtn'); const stopBtn = document.getElementById('stopBtn');
const cancelBtn = document.getElementById('cancelBtn'); const cancelBtn = document.getElementById('cancelBtn');
@@ -2112,6 +2136,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
switch(msg.type) { switch(msg.type) {
case 'addMessage': case 'addMessage':
addMsg(msg.value, msg.role, msg.rationale); addMsg(msg.value, msg.role, msg.rationale);
// Update state for non-streamed messages
const s = vscode.getState() || { history: [] };
s.history.push({ role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.value, rationale: msg.rationale });
saveWebviewState(s.history);
break; break;
case 'streamStart': case 'streamStart':
thinkingBar.classList.remove('active'); thinkingBar.classList.remove('active');
@@ -2128,9 +2156,15 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
} }
break; break;
case 'streamEnd': case 'streamEnd':
if (streamBody) streamBody.classList.remove('stream-active'); if (streamBody) {
streamBody.classList.remove('stream-active');
// Update state after stream finishes
const state = vscode.getState() || { history: [] };
state.history.push({ role: 'assistant', content: streamBody._parent._raw });
saveWebviewState(state.history);
}
streamBody = null; streamBody = null;
// \uc0dd\uc131 \uc644\ub8cc \uc2dc Stop \ubc84\ud2bc \uc228\uae30\uace0 Send \ubcf5\uad50 // 생성 완료 시 Stop 버튼 숨기고 Send 복구
setGenerating(false); setGenerating(false);
resetStepper(); resetStepper();
Sound.success(); Sound.success();
@@ -2143,12 +2177,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
: (Array.isArray(historyPayload?.history) ? historyPayload.history : []); : (Array.isArray(historyPayload?.history) ? historyPayload.history : []);
if (history && history.length > 0) { if (history && history.length > 0) {
chat.innerHTML = ''; renderHistory(history);
history.forEach(m => { saveWebviewState(history);
if (!m || m.internal) return;
addMsg(m.content, m.role === 'assistant' ? 'assistant' : 'user', m.rationale);
});
chat.scrollTop = chat.scrollHeight;
} }
if (historyPayload?.negativePrompt !== undefined) { if (historyPayload?.negativePrompt !== undefined) {
negativePrompt.value = historyPayload.negativePrompt; negativePrompt.value = historyPayload.negativePrompt;
@@ -2358,6 +2388,11 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
setDraftActive(false); setDraftActive(false);
setGenerating(true); setGenerating(true);
thinkingBar.classList.add('active'); thinkingBar.classList.add('active');
// Save state after sending
const currentState = vscode.getState() || { history: [] };
currentState.history.push({ role: 'user', content: val });
saveWebviewState(currentState.history);
} }
sendBtn.onclick = send; sendBtn.onclick = send;