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", 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..9d5fdd5 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; 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 ?? []; + } + 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 {