feat: persist session ID across restarts

When the Hermes Gateway returns X-Hermes-Session-Id, it is now
saved to settings.chatSessionId via the ChatPersistence interface.
On restart, the factory restores the session ID into the ApiClient
so subsequent requests continue the same conversation.

Changes:
- Added chatSessionId to HermesChatSettings + DEFAULT_SETTINGS
- Extended ChatPersistence with loadSessionId/saveSessionId
- Added ApiClient.setSessionId() for restoring saved IDs
- ChatView.handleSend() persists sessionId when received
- main.ts factory restores session ID and updates status bar

Fixes the 'Marco!/Bolo!' test — conversation continuity now
survives Obsidian restarts.
This commit is contained in:
Helm 2026-07-10 13:45:49 -04:00
parent 175cfa2c61
commit 7b857803a7
5 changed files with 27 additions and 7 deletions

12
main.js

File diff suppressed because one or more lines are too long

View file

@ -39,6 +39,11 @@ export class ApiClient {
return this.currentSessionId;
}
/** Restore a previously-saved session ID (from plugin data). */
setSessionId(id: string): void {
this.currentSessionId = id || null;
}
/** The root URL without the /v1 suffix, e.g. http://192.168.1.12:8642 */
private get rootUrl(): string {
// baseUrl ends with /v1; strip it to reach the server root

View file

@ -16,10 +16,12 @@ export interface ApiClient {
): Promise<ChatCompletionResponse>;
}
/** Persistence interface — saves/loads chat messages via plugin data. */
/** Persistence interface — saves/loads chat state via plugin data. */
export interface ChatPersistence {
loadMessages(): Message[];
saveMessages(messages: Message[]): Promise<void>;
loadSessionId(): string;
saveSessionId(sessionId: string): Promise<void>;
}
export const VIEW_TYPE = "hermes-chat-view";
@ -547,6 +549,7 @@ export class ChatView extends ItemView {
if (response.sessionId) {
this.chatState.sessionId = response.sessionId;
this.updateStatus(response.model, response.sessionId);
this.persistence?.saveSessionId(response.sessionId);
}
} catch (error: any) {
if (contentDiv) {

View file

@ -25,11 +25,21 @@ export default class HermesChatPlugin extends Plugin {
this.settings.chatMessages = messages;
await this.saveData(this.settings);
},
loadSessionId: () => this.settings.chatSessionId ?? "",
saveSessionId: async (id) => {
this.settings.chatSessionId = id;
await this.saveData(this.settings);
},
};
const view = new ChatView(leaf, client, this.contextBridge!, persistence);
view.setContextActive(this.settings.enableContextInjection);
view.setSlashCommands(this.getAllCommands());
view.setChatFontSize(this.settings.chatFontSize);
// Restore previous session for conversation continuity
if (this.settings.chatSessionId) {
client.setSessionId(this.settings.chatSessionId);
view.updateStatus("hermes-agent", this.settings.chatSessionId);
}
return view;
}
);

View file

@ -11,6 +11,7 @@ export interface HermesChatSettings {
chatFontSize: number;
customCommands: { trigger: string; description: string; prompt: string }[];
chatMessages: Message[];
chatSessionId: string;
}
/** Built-in slash commands shipped with the plugin. */
@ -40,6 +41,7 @@ export const DEFAULT_SETTINGS: HermesChatSettings = {
chatFontSize: 14,
customCommands: [],
chatMessages: [],
chatSessionId: "",
};
export class HermesChatSettingTab extends PluginSettingTab {