diff --git a/packages/dmoss-agent/src/core/session/session-manager.ts b/packages/dmoss-agent/src/core/session/session-manager.ts index 705ac93..97f6560 100644 --- a/packages/dmoss-agent/src/core/session/session-manager.ts +++ b/packages/dmoss-agent/src/core/session/session-manager.ts @@ -579,7 +579,14 @@ function buildSessionContext(state: SessionState): Message[] { const compactionIdx = path.findIndex( (entry) => entry.type === "compaction" && entry.id === compaction.id, ); - let foundFirstKept = false; + // If firstKeptEntryId is missing from the path (corruption/race — the codec + // only warns on load), the kept tail would otherwise be silently dropped on + // replay, losing recent history the compaction summary does NOT cover. Fall + // back to keeping every pre-compaction entry instead of dropping them. + const firstKeptExists = path + .slice(0, compactionIdx < 0 ? 0 : compactionIdx) + .some((entry) => entry.id === compaction.firstKeptEntryId); + let foundFirstKept = !firstKeptExists; for (let i = 0; i < compactionIdx; i++) { const entry = path[i]; if (entry.id === compaction.firstKeptEntryId) { diff --git a/packages/dmoss-agent/test/session-compaction-replay.spec.mjs b/packages/dmoss-agent/test/session-compaction-replay.spec.mjs new file mode 100644 index 0000000..bba68be --- /dev/null +++ b/packages/dmoss-agent/test/session-compaction-replay.spec.mjs @@ -0,0 +1,44 @@ +#!/usr/bin/env node +/** + * Compaction replay must not silently drop kept history when + * compaction.firstKeptEntryId is missing from the path (corruption/race — the + * JSONL codec only warns on load). Before the fix, buildSessionContext never + * flipped foundFirstKept, so every pre-compaction entry was dropped, losing + * recent history the summary does NOT cover. Red before / green after. + * + * Run: + * npm run build -w @rdk-moss/agent + * node packages/dmoss-agent/test/session-compaction-replay.spec.mjs + */ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { SessionManager } from '../dist/core/session/session-manager.js'; + +const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dmoss-compaction-replay-')); +try { + const sm = new SessionManager(tmpDir); + const key = 's1'; + + await sm.append(key, { role: 'user', content: 'PRECOMP-1' }); + await sm.append(key, { role: 'assistant', content: 'PRECOMP-2' }); + await sm.append(key, { role: 'user', content: 'PRECOMP-3' }); + // Compaction whose firstKeptEntryId does NOT exist in the path. + await sm.appendCompaction(key, 'THE-COMPACTION-SUMMARY', 'nonexistent-kept-id', 100); + await sm.append(key, { role: 'assistant', content: 'POSTCOMP-1' }); + + const ctx = await sm.load(key); + const text = JSON.stringify(ctx); + + assert.match(text, /THE-COMPACTION-SUMMARY/, 'compaction summary must be replayed'); + assert.match(text, /POSTCOMP-1/, 'post-compaction message must be present'); + // The core bug: pre-compaction kept messages must NOT vanish when + // firstKeptEntryId is missing. + assert.match(text, /PRECOMP-1/, 'pre-compaction history must not be lost when firstKeptEntryId is missing'); + assert.match(text, /PRECOMP-3/, 'all pre-compaction messages preserved'); + + console.log(' [PASS] compaction replay preserves history when firstKeptEntryId is missing'); +} finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); +}