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)
## 🎨 UI Refinement
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "g1nation",
"displayName": "G1nation",
"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",
"license": "MIT",
"icon": "assets/icon.png",
+2 -3
View File
@@ -119,7 +119,7 @@ export class AgentExecutor {
}
public getHistory() {
return this.chatHistory.filter(message => !message.internal);
return this.chatHistory.filter(message => !message.internal || message.role === 'assistant');
}
public setHistory(history: ChatMessage[]) {
@@ -394,7 +394,7 @@ export class AgentExecutor {
// 5. Execute Actions
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.statusBarManager.updateStatus(AgentStatus.Executing);
@@ -443,7 +443,6 @@ export class AgentExecutor {
return;
}
assistantMessage.internal = false;
this.emitHistoryChanged();
this.statusBarManager.updateStatus(AgentStatus.Success);
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;
const blankChatActive = this._context.globalState.get<boolean>(SidebarChatProvider.blankChatStateKey, false);
if (blankChatActive) {
const currentHistory = this._agent.getHistory();
if (blankChatActive && currentHistory.length === 0) {
return;
}
@@ -212,7 +214,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
await this._context.globalState.update(SidebarChatProvider.activeSessionStateKey, null);
}
const currentHistory = this._agent.getHistory();
if (currentHistory.length > 0) {
this._view.webview.postMessage({ type: 'restoreHistory', value: currentHistory });
await this._persistLastVisibleChat(currentHistory);
@@ -1893,6 +1894,29 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
const vscode = acquireVsCodeApi();
const chat = document.getElementById('chat');
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 stopBtn = document.getElementById('stopBtn');
const cancelBtn = document.getElementById('cancelBtn');
@@ -2112,6 +2136,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
switch(msg.type) {
case 'addMessage':
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;
case 'streamStart':
thinkingBar.classList.remove('active');
@@ -2128,9 +2156,15 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
}
break;
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;
// \uc0dd\uc131 \uc644\ub8cc \uc2dc Stop \ubc84\ud2bc \uc228\uae30\uace0 Send \ubcf5\uad50
// 생성 완료 시 Stop 버튼 숨기고 Send 복구
setGenerating(false);
resetStepper();
Sound.success();
@@ -2143,12 +2177,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
: (Array.isArray(historyPayload?.history) ? historyPayload.history : []);
if (history && history.length > 0) {
chat.innerHTML = '';
history.forEach(m => {
if (!m || m.internal) return;
addMsg(m.content, m.role === 'assistant' ? 'assistant' : 'user', m.rationale);
});
chat.scrollTop = chat.scrollHeight;
renderHistory(history);
saveWebviewState(history);
}
if (historyPayload?.negativePrompt !== undefined) {
negativePrompt.value = historyPayload.negativePrompt;
@@ -2358,6 +2388,11 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
setDraftActive(false);
setGenerating(true);
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;