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
This commit is contained in:
Mario Zechner 2025-12-09 16:55:42 +01:00
parent 02c7f9ea51
commit 8226975080

View file

@ -191,7 +191,9 @@ export class SessionManager {
private sessionDir: string; private sessionDir: string;
private enabled: boolean = true; private enabled: boolean = true;
private sessionInitialized: boolean = false; 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) { constructor(continueSession: boolean = false, customSessionPath?: string) {
this.sessionDir = this.getSessionDirectory(); this.sessionDir = this.getSessionDirectory();
@ -202,6 +204,10 @@ export class SessionManager {
this.loadSessionId(); this.loadSessionId();
// Mark as initialized since we're loading an existing session // Mark as initialized since we're loading an existing session
this.sessionInitialized = existsSync(this.sessionFile); this.sessionInitialized = existsSync(this.sessionFile);
// Load entries into memory
if (this.sessionInitialized) {
this.inMemoryEntries = this.loadEntriesFromFile();
}
} else if (continueSession) { } else if (continueSession) {
const mostRecent = this.findMostRecentlyModifiedSession(); const mostRecent = this.findMostRecentlyModifiedSession();
if (mostRecent) { if (mostRecent) {
@ -209,6 +215,8 @@ export class SessionManager {
this.loadSessionId(); this.loadSessionId();
// Mark as initialized since we're loading an existing session // Mark as initialized since we're loading an existing session
this.sessionInitialized = true; this.sessionInitialized = true;
// Load entries into memory
this.inMemoryEntries = this.loadEntriesFromFile();
} else { } else {
this.initNewSession(); this.initNewSession();
} }
@ -241,9 +249,10 @@ export class SessionManager {
this.sessionFile = join(this.sessionDir, `${timestamp}_${this.sessionId}.jsonl`); 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 { reset(): void {
this.pendingMessages = []; this.pendingEntries = [];
this.inMemoryEntries = [];
this.sessionInitialized = false; this.sessionInitialized = false;
this.initNewSession(); this.initNewSession();
} }
@ -284,7 +293,7 @@ export class SessionManager {
} }
startSession(state: AgentState): void { startSession(state: AgentState): void {
if (!this.enabled || this.sessionInitialized) return; if (this.sessionInitialized) return;
this.sessionInitialized = true; this.sessionInitialized = true;
const entry: SessionHeader = { const entry: SessionHeader = {
@ -296,17 +305,24 @@ export class SessionManager {
modelId: state.model.id, modelId: state.model.id,
thinkingLevel: state.thinkingLevel, thinkingLevel: state.thinkingLevel,
}; };
appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n");
// Write any queued messages // Always track in memory
for (const msg of this.pendingMessages) { this.inMemoryEntries.push(entry);
appendFileSync(this.sessionFile, JSON.stringify(msg) + "\n"); 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 { saveMessage(message: any): void {
if (!this.enabled) return;
const entry: SessionMessageEntry = { const entry: SessionMessageEntry = {
type: "message", type: "message",
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
@ -314,14 +330,18 @@ export class SessionManager {
}; };
if (!this.sessionInitialized) { if (!this.sessionInitialized) {
this.pendingMessages.push(entry); this.pendingEntries.push(entry);
} else { } 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 { saveThinkingLevelChange(thinkingLevel: string): void {
if (!this.enabled) return;
const entry: ThinkingLevelChangeEntry = { const entry: ThinkingLevelChangeEntry = {
type: "thinking_level_change", type: "thinking_level_change",
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
@ -329,14 +349,18 @@ export class SessionManager {
}; };
if (!this.sessionInitialized) { if (!this.sessionInitialized) {
this.pendingMessages.push(entry); this.pendingEntries.push(entry);
} else { } 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 { saveModelChange(provider: string, modelId: string): void {
if (!this.enabled) return;
const entry: ModelChangeEntry = { const entry: ModelChangeEntry = {
type: "model_change", type: "model_change",
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
@ -345,15 +369,24 @@ export class SessionManager {
}; };
if (!this.sessionInitialized) { if (!this.sessionInitialized) {
this.pendingMessages.push(entry); this.pendingEntries.push(entry);
} else { } 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 { saveCompaction(entry: CompactionEntry): void {
if (!this.enabled) return; // Always track in memory
appendFileSync(this.sessionFile, JSON.stringify(entry) + "\n"); 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 []; if (!existsSync(this.sessionFile)) return [];
const content = readFileSync(this.sessionFile, "utf8"); const content = readFileSync(this.sessionFile, "utf8");
@ -416,6 +449,21 @@ export class SessionManager {
return entries; 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 * Load all sessions for the current directory with metadata
*/ */
@ -523,6 +571,13 @@ export class SessionManager {
this.loadSessionId(); this.loadSessionId();
// Mark as initialized since we're loading an existing session // Mark as initialized since we're loading an existing session
this.sessionInitialized = existsSync(path); this.sessionInitialized = existsSync(path);
// Load entries into memory for consistency
if (this.sessionInitialized) {
this.inMemoryEntries = this.loadEntriesFromFile();
} else {
this.inMemoryEntries = [];
}
this.pendingEntries = [];
} }
/** /**