From 82033518616d340ee0d6da745976dbbc137d0a10 Mon Sep 17 00:00:00 2001 From: Gram Ricko Date: Sun, 21 Jun 2026 08:17:24 +0000 Subject: [PATCH 1/3] Load session list dynamically from ACP backend on startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The extension stored session metadata only in VS Code workspaceState, which is in-memory only in code-server — lost on browser reload. Now on ACP connect, the extension calls the backend's built-in list_sessions RPC and populates the session list dynamically. Clicking an old session calls session/load to restore Hermes context. No file fallback or dual persistence needed — backend is the source of truth. --- src/chatPanel.ts | 27 +++++++++++++++++++++++++++ src/extension.ts | 2 ++ src/sessionManager.ts | 7 +++++++ src/sessionStore.ts | 16 ++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/src/chatPanel.ts b/src/chatPanel.ts index e880545..ee1be16 100644 --- a/src/chatPanel.ts +++ b/src/chatPanel.ts @@ -45,6 +45,33 @@ export class ChatPanelProvider implements vscode.WebviewViewProvider { this.store = new SessionStore(context); } + /** Sync sessions from the ACP backend after connection, so the session list survives reloads. */ + async syncSessionsFromBackend(): Promise { + try { + const sessions = await this.session.listSessions(); + if (!sessions || sessions.length === 0) return; + this.log(`[session] syncing ${sessions.length} backend sessions`); + + // Backend returns sessions sorted by updated_at descending (most recent first). + // Reverse so most recent gets pushed last (appears first in reversed view). + let added = 0; + for (const bs of [...sessions].reverse()) { + const exists = this.store.allSessions().some(s => s.acpSessionId === bs.sessionId); + if (!exists) { + const insertPos = Math.max(0, this.store.allSessions().length - 1); + this.store.addBackendSession(bs.sessionId, bs.title || 'Session', bs.updatedAt, insertPos); + added++; + } + } + if (added > 0) { + this.log(`[session] added ${added} backend sessions to store`); + this.broadcastSessions(this.store); + } + } catch (err) { + this.log(`[session] backend sync failed: ${err}`); + } + } + resolveWebviewView(webviewView: vscode.WebviewView): void { this.view = webviewView; diff --git a/src/extension.ts b/src/extension.ts index 666d7ad..1e4c33d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -432,6 +432,8 @@ export async function activate(context: vscode.ExtensionContext): Promise await client.start(); outputChannel.appendLine('[acp] connected'); setStatus('connected'); + // Sync session list from backend so it survives browser reloads + void panel.syncSessionsFromBackend(); } catch (err) { outputChannel.appendLine(`[acp] connect failed: ${err}`); setStatus('disconnected'); diff --git a/src/sessionManager.ts b/src/sessionManager.ts index b76b36f..62da413 100644 --- a/src/sessionManager.ts +++ b/src/sessionManager.ts @@ -88,6 +88,13 @@ export class SessionManager { return this.sessionId; } + /** Fetch all ACP sessions from the Hermes backend. */ + async listSessions(): Promise<{ sessionId: string; title?: string; updatedAt?: string }[]> { + const result = await this.client.call('list_sessions', {}); + const response = result as { sessions?: { sessionId: string; title?: string; updatedAt?: string }[] } | undefined; + return response?.sessions ?? []; + } + async ensureSession(cwd: string): Promise { if (this.sessionId) { this.log(`[session] reusing ${this.sessionId}`); diff --git a/src/sessionStore.ts b/src/sessionStore.ts index 6249a98..cef338f 100644 --- a/src/sessionStore.ts +++ b/src/sessionStore.ts @@ -128,6 +128,22 @@ export class SessionStore { } } + /** Add a backend session (from ACP list_sessions) to the store without activating it. */ + addBackendSession(acpSessionId: string, title: string, updatedAt: string | undefined, insertPos: number): void { + const session: ChatSession = { + id: `acp-${acpSessionId}`, + title, + createdAt: updatedAt ? new Date(updatedAt).getTime() : Date.now(), + messages: [], + acpSessionId, + }; + this.sessions.splice(insertPos, 0, session); + if (this.sessions.length > MAX_SESSIONS) { + this.sessions = this.sessions.slice(-MAX_SESSIONS); + } + this.persist(); + } + // ── Persistence ──────────────────────────────────── private persist(): void { From fb666695f82a7d1b2605e7d946f2d7001413e0aa Mon Sep 17 00:00:00 2001 From: Gram Ricko Date: Sun, 21 Jun 2026 08:41:18 +0000 Subject: [PATCH 2/3] Bump to 3.0.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cd9188c..72b1eeb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "hermes-code-agent", "displayName": "Hermes Code Agent", "description": "VS Code sidebar for the Hermes AI agent. Streams chat, runs tools, manages sessions. Multi-model (Claude, Codex). Communicates over ACP.", - "version": "3.0.3", + "version": "3.0.4", "publisher": "gitricko", "author": "gitricko", "license": "MIT", From b6f6bc8ebfe4bec3d6e4bf41a8515eb25c514136 Mon Sep 17 00:00:00 2001 From: Gram Ricko Date: Sun, 21 Jun 2026 08:50:59 +0000 Subject: [PATCH 3/3] Fix: use correct ACP method name 'session/list' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ACP protocol method is session/list (kebab slash), not list_sessions. Confirmed in acp/meta.py: AGENT_METHODS['session_list'] = 'session/list'. Previous build was silently failing — the call returned an error and listSessions() returned empty, so no backend sessions appeared in the UI. Local testing showed only the 2 'hi' sessions (created in the local store on this session) were visible — the 5 backend sessions in state.db were never synced because the RPC call was returning an error response that got swallowed by the empty-array fallback. Field names (sessionId, title, updatedAt) are correct — Pydantic serializes with by_alias=True so camelCase comes over the wire. --- src/sessionManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sessionManager.ts b/src/sessionManager.ts index 62da413..9d5fdd5 100644 --- a/src/sessionManager.ts +++ b/src/sessionManager.ts @@ -89,9 +89,9 @@ export class SessionManager { } /** Fetch all ACP sessions from the Hermes backend. */ - async listSessions(): Promise<{ sessionId: string; title?: string; updatedAt?: string }[]> { - const result = await this.client.call('list_sessions', {}); - const response = result as { sessions?: { sessionId: string; title?: string; updatedAt?: string }[] } | undefined; + async listSessions(): Promise<{ sessionId: string; title?: string; updatedAt?: string; cwd?: string }[]> { + const result = await this.client.call('session/list', {}); + const response = result as { sessions?: { sessionId: string; title?: string; updatedAt?: string; cwd?: string }[] } | undefined; return response?.sessions ?? []; }