Enhance memory management and compaction features

- Implement global memory layer for cross-project patterns.
- Improve memory entry scoring with recency and tokenization.
- Adjust MAX_TEXT limit from 400 to 800 for better context retention.
- Add TTL pruning for stale memory entries.
- Capture and summarize dropped content during compaction.
- Update tests to reflect changes in memory behavior and compaction logic.
This commit is contained in:
asepharyana
2026-09-08 21:27:47 +07:00
parent b1ff00d840
commit 7db69f19da
8 changed files with 522 additions and 36 deletions
+29 -1
View File
@@ -1,6 +1,6 @@
import { expect, test } from 'bun:test';
import type { ModelMessage } from 'ai';
import { detachOrphanedItems, dropOrphanedResults, pruneToFit, prunePreservingItems } from '../src/prune';
import { detachOrphanedItems, droppedSpan, dropOrphanedResults, estimateTokens, pruneToFit, prunePreservingItems } from '../src/prune';
const kinds = (messages: ModelMessage[]) =>
messages.map((m) => (Array.isArray(m.content) ? `${m.role}:${m.content.map((p) => p.type).join('+')}` : m.role));
@@ -438,6 +438,34 @@ test('compaction removes a plain assistant item reference without inline reasoni
expect(JSON.stringify(fitted)).toContain('answer');
});
test('droppedSpan is empty when only reasoning was removed', () => {
const messages: ModelMessage[] = [
{ role: 'user', content: 'q' },
{
role: 'assistant',
content: [
{ type: 'reasoning', text: 't', providerOptions: { openai: { itemId: 'rs1' } } },
{ type: 'text', text: 'ans', providerOptions: { openai: { itemId: 'msg1' } } },
],
},
];
// pruneToFit with high threshold only strips reasoning, keeping ans
const fitted = pruneToFit({ messages, threshold: 20000, estimate: estimateTokens });
expect(droppedSpan(messages, fitted)).toEqual([]);
});
test('droppedSpan captures pruned tool content', () => {
const msgs: ModelMessage[] = [{ role: 'user', content: 'do thing' }];
for (let i = 0; i < 20; i++) {
msgs.push({ role: 'assistant', content: [{ type: 'tool-call', toolCallId: `t${i}`, toolName: 'grep', input: { pattern: 'x' } }] });
msgs.push({ role: 'tool', content: [{ type: 'tool-result', toolCallId: `t${i}`, toolName: 'grep', output: { type: 'text', value: 'x'.repeat(3000) } }] });
}
const fitted = pruneToFit({ messages: msgs, threshold: 6000, estimate: estimateTokens });
const span = droppedSpan(msgs, fitted);
expect(span.length).toBeGreaterThan(0);
expect(span.length + fitted.length).toBe(msgs.length);
});
test('the user prompt survives even the narrowest rung', () => {
const messages = transcript(200, 4000);
const fitted = pruneToFit({ messages, threshold: 100, estimate });