From 82269750804d7a8f6d29a88a9de8c4f2fee7943a Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 9 Dec 2025 16:55:42 +0100 Subject: [PATCH] feat(coding-agent): maintain in-memory session entries for --no-session compaction support SessionManager now tracks all entries in memory regardless of whether file persistence is enabled. This allows compaction to work in --no-session mode where no session file is created. - Added inMemoryEntries array to store session entries - All save methods now push to inMemoryEntries - loadEntries() returns in-memory entries when file persistence is disabled - File persistence only writes when enabled flag is true - Session loading (constructor, setSessionFile) populates in-memory entries --- .../coding-agent/src/core/session-manager.ts | 99 ++++++++++++++----- 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/packages/coding-agent/src/core/session-manager.ts b/packages/coding-agent/src/core/session-manager.ts index 584e48ea..71291d54 100644 --- a/packages/coding-agent/src/core/session-manager.ts +++ b/packages/coding-agent/src/core/session-manager.ts @@ -191,7 +191,9 @@ export class SessionManager { private sessionDir: string; private enabled: boolean = true; private sessionInitialized: boolean = false; - private pendingMessages: any[] = []; + private pendingEntries: SessionEntry[] = []; + // In-memory entries for --no-session mode (when enabled=false) + private inMemoryEntries: SessionEntry[] = []; constructor(continueSession: boolean = false, customSessionPath?: string) { this.sessionDir = this.getSessionDirectory(); @@ -202,6 +204,10 @@ export class SessionManager { this.loadSessionId(); // Mark as initialized since we're loading an existing session this.sessionInitialized = existsSync(this.sessionFile); + // Load entries into memory + if (this.sessionInitialized) { + this.inMemoryEntries = this.loadEntriesFromFile(); + } } else if (continueSession) { const mostRecent = this.findMostRecentlyModifiedSession(); if (mostRecent) { @@ -209,6 +215,8 @@ export class SessionManager { this.loadSessionId(); // Mark as initialized since we're loading an existing session this.sessionInitialized = true; + // Load entries into memory + this.inMemoryEntries = this.loadEntriesFromFile(); } else { this.initNewSession(); } @@ -241,9 +249,10 @@ export class SessionManager { this.sessionFile = join(this.sessionDir, `${timestamp}_${this.sessionId}.jsonl`); } - /** Reset to a fresh session. Clears pending messages and starts a new session file. */ + /** Reset to a fresh session. Clears pending entries and starts a new session file. */ reset(): void { - this.pendingMessages = []; + this.pendingEntries = []; + this.inMemoryEntries = []; this.sessionInitialized = false; this.initNewSession(); } @@ -284,7 +293,7 @@ export class SessionManager { } startSession(state: AgentState): void { - if (!this.enabled || this.sessionInitialized) return; + if (this.sessionInitialized) return; this.sessionInitialized = true; const entry: SessionHeader = { @@ -296,17 +305,24 @@ export class SessionManager { modelId: state.model.id, thinkingLevel: state.thinkingLevel, }; - appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); - // Write any queued messages - for (const msg of this.pendingMessages) { - appendFileSync(this.sessionFile, JSON.stringify(msg) + "\n"); + // Always track in memory + this.inMemoryEntries.push(entry); + for (const pending of this.pendingEntries) { + this.inMemoryEntries.push(pending); + } + this.pendingEntries = []; + + // Write to file only if enabled + if (this.enabled) { + appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); + for (const memEntry of this.inMemoryEntries.slice(1)) { + appendFileSync(this.sessionFile, JSON.stringify(memEntry) + "\n"); + } } - this.pendingMessages = []; } saveMessage(message: any): void { - if (!this.enabled) return; const entry: SessionMessageEntry = { type: "message", timestamp: new Date().toISOString(), @@ -314,14 +330,18 @@ export class SessionManager { }; if (!this.sessionInitialized) { - this.pendingMessages.push(entry); + this.pendingEntries.push(entry); } else { - appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); + // Always track in memory + this.inMemoryEntries.push(entry); + // Write to file only if enabled + if (this.enabled) { + appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); + } } } saveThinkingLevelChange(thinkingLevel: string): void { - if (!this.enabled) return; const entry: ThinkingLevelChangeEntry = { type: "thinking_level_change", timestamp: new Date().toISOString(), @@ -329,14 +349,18 @@ export class SessionManager { }; if (!this.sessionInitialized) { - this.pendingMessages.push(entry); + this.pendingEntries.push(entry); } else { - appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); + // Always track in memory + this.inMemoryEntries.push(entry); + // Write to file only if enabled + if (this.enabled) { + appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); + } } } saveModelChange(provider: string, modelId: string): void { - if (!this.enabled) return; const entry: ModelChangeEntry = { type: "model_change", timestamp: new Date().toISOString(), @@ -345,15 +369,24 @@ export class SessionManager { }; if (!this.sessionInitialized) { - this.pendingMessages.push(entry); + this.pendingEntries.push(entry); } else { - appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); + // Always track in memory + this.inMemoryEntries.push(entry); + // Write to file only if enabled + if (this.enabled) { + appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); + } } } saveCompaction(entry: CompactionEntry): void { - if (!this.enabled) return; - appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); + // Always track in memory + this.inMemoryEntries.push(entry); + // Write to file only if enabled + if (this.enabled) { + appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); + } } /** @@ -394,9 +427,9 @@ export class SessionManager { } /** - * Load all entries from the session file. + * Load entries directly from the session file (internal helper). */ - loadEntries(): SessionEntry[] { + private loadEntriesFromFile(): SessionEntry[] { if (!existsSync(this.sessionFile)) return []; const content = readFileSync(this.sessionFile, "utf8"); @@ -416,6 +449,21 @@ export class SessionManager { return entries; } + /** + * Load all entries from the session file or in-memory store. + * When file persistence is enabled, reads from file (source of truth for resumed sessions). + * When disabled (--no-session), returns in-memory entries. + */ + loadEntries(): SessionEntry[] { + // If file persistence is enabled and file exists, read from file + if (this.enabled && existsSync(this.sessionFile)) { + return this.loadEntriesFromFile(); + } + + // Otherwise return in-memory entries (for --no-session mode) + return [...this.inMemoryEntries]; + } + /** * Load all sessions for the current directory with metadata */ @@ -523,6 +571,13 @@ export class SessionManager { this.loadSessionId(); // Mark as initialized since we're loading an existing session this.sessionInitialized = existsSync(path); + // Load entries into memory for consistency + if (this.sessionInitialized) { + this.inMemoryEntries = this.loadEntriesFromFile(); + } else { + this.inMemoryEntries = []; + } + this.pendingEntries = []; } /**