fix: reduce session.messages() calls with event-based caching to prevent memory leaks
- Replace session.messages() fetch in context-window-monitor with message.updated event cache - Replace session.messages() fetch in preemptive-compaction with message.updated event cache - Add per-session transcript cache (5min TTL) to avoid full rebuild per tool call - Remove session.messages() from background-agent polling (use event-based progress) - Add TTL pruning to todo-continuation-enforcer session state Map - Add setInterval.unref() to tool-input-cache cleanup timer Fixes #1222
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"
|
||||
import { existsSync, unlinkSync, readFileSync } from "fs"
|
||||
import {
|
||||
buildTranscriptFromSession,
|
||||
deleteTempTranscript,
|
||||
clearTranscriptCache,
|
||||
} from "./transcript"
|
||||
|
||||
function createMockClient(messages: unknown[] = []) {
|
||||
return {
|
||||
session: {
|
||||
messages: mock(() =>
|
||||
Promise.resolve({
|
||||
data: messages,
|
||||
})
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe("transcript caching", () => {
|
||||
afterEach(() => {
|
||||
clearTranscriptCache()
|
||||
})
|
||||
|
||||
// #given same session called twice
|
||||
// #when buildTranscriptFromSession is invoked
|
||||
// #then session.messages() should be called only once (cached)
|
||||
it("should cache transcript and not re-fetch for same session", async () => {
|
||||
const client = createMockClient([
|
||||
{
|
||||
info: { role: "assistant" },
|
||||
parts: [
|
||||
{
|
||||
type: "tool",
|
||||
tool: "bash",
|
||||
state: { status: "completed", input: { command: "ls" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const path1 = await buildTranscriptFromSession(
|
||||
client,
|
||||
"ses_cache1",
|
||||
"/tmp",
|
||||
"bash",
|
||||
{ command: "echo hi" }
|
||||
)
|
||||
|
||||
const path2 = await buildTranscriptFromSession(
|
||||
client,
|
||||
"ses_cache1",
|
||||
"/tmp",
|
||||
"read",
|
||||
{ path: "/tmp/file" }
|
||||
)
|
||||
|
||||
// session.messages() called only once
|
||||
expect(client.session.messages).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Both return valid paths
|
||||
expect(path1).not.toBeNull()
|
||||
expect(path2).not.toBeNull()
|
||||
|
||||
// Second call should append the new tool entry
|
||||
if (path2) {
|
||||
const content = readFileSync(path2, "utf-8")
|
||||
expect(content).toContain("Read")
|
||||
}
|
||||
|
||||
deleteTempTranscript(path1)
|
||||
deleteTempTranscript(path2)
|
||||
})
|
||||
|
||||
// #given different sessions
|
||||
// #when buildTranscriptFromSession called for each
|
||||
// #then session.messages() should be called for each
|
||||
it("should not share cache between different sessions", async () => {
|
||||
const client = createMockClient([])
|
||||
|
||||
await buildTranscriptFromSession(client, "ses_a", "/tmp", "bash", {})
|
||||
await buildTranscriptFromSession(client, "ses_b", "/tmp", "bash", {})
|
||||
|
||||
expect(client.session.messages).toHaveBeenCalledTimes(2)
|
||||
|
||||
clearTranscriptCache()
|
||||
})
|
||||
|
||||
// #given clearTranscriptCache is called
|
||||
// #when buildTranscriptFromSession called again
|
||||
// #then should re-fetch
|
||||
it("should re-fetch after cache is cleared", async () => {
|
||||
const client = createMockClient([])
|
||||
|
||||
await buildTranscriptFromSession(client, "ses_clear", "/tmp", "bash", {})
|
||||
clearTranscriptCache()
|
||||
await buildTranscriptFromSession(client, "ses_clear", "/tmp", "bash", {})
|
||||
|
||||
expect(client.session.messages).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user