fix: keep session transcript pointers fresh after compaction (#50688)

Co-authored-by: Frank Yang <frank.ekn@gmail.com>
This commit is contained in:
Sathvik Veerapaneni
2026-03-23 10:58:07 -04:00
committed by GitHub
parent dd132ea77b
commit d2e8ed3632
13 changed files with 976 additions and 774 deletions

View File

@@ -15,16 +15,6 @@ const runEmbeddedPiAgentMock = vi.fn();
const runCliAgentMock = vi.fn();
const runWithModelFallbackMock = vi.fn();
const runtimeErrorMock = vi.fn();
const runMemoryFlushIfNeededMock = vi.hoisted(() =>
vi.fn(async ({ sessionEntry }) => sessionEntry),
);
const createReplyMediaPathNormalizerMock = vi.hoisted(() =>
vi.fn(
(_params?: unknown) =>
async <T>(payload: T) =>
payload,
),
);
vi.mock("../../agents/model-fallback.js", () => ({
runWithModelFallback: (params: {
@@ -68,14 +58,6 @@ vi.mock("../../runtime.js", async () => {
};
});
vi.mock("./agent-runner-memory.runtime.js", () => ({
runMemoryFlushIfNeeded: (params: unknown) => runMemoryFlushIfNeededMock(params),
}));
vi.mock("./reply-media-paths.runtime.js", () => ({
createReplyMediaPathNormalizer: (params: unknown) => createReplyMediaPathNormalizerMock(params),
}));
vi.mock("./queue.js", async () => {
const actual = await vi.importActual<typeof import("./queue.js")>("./queue.js");
return {
@@ -103,40 +85,10 @@ type RunWithModelFallbackParams = {
};
beforeEach(() => {
vi.useRealTimers();
vi.clearAllTimers();
runEmbeddedPiAgentMock.mockClear();
runCliAgentMock.mockClear();
runWithModelFallbackMock.mockClear();
runtimeErrorMock.mockClear();
runMemoryFlushIfNeededMock.mockClear();
runMemoryFlushIfNeededMock.mockImplementation(
async ({
sessionEntry,
followupRun,
}: {
sessionEntry?: SessionEntry;
followupRun: FollowupRun;
}) => {
if (!sessionEntry || (sessionEntry.totalTokens ?? 0) < 1_000_000) {
return sessionEntry;
}
await runWithModelFallbackMock({
provider: followupRun.run.provider,
model: followupRun.run.model,
run: async (provider: string, model: string) =>
await runEmbeddedPiAgentMock({
provider,
model,
prompt: "Pre-compaction memory flush.",
enforceFinalTag: provider.includes("gemini") ? true : undefined,
}),
});
return sessionEntry;
},
);
createReplyMediaPathNormalizerMock.mockClear();
createReplyMediaPathNormalizerMock.mockImplementation(() => async (payload) => payload);
loadCronStoreMock.mockClear();
// Default: no cron jobs in store.
loadCronStoreMock.mockResolvedValue({ version: 1, jobs: [] });
@@ -153,7 +105,6 @@ beforeEach(() => {
});
afterEach(() => {
vi.clearAllTimers();
vi.useRealTimers();
resetSystemEventsForTest();
});
@@ -388,6 +339,11 @@ describe("runReplyAgent auto-compaction token update", () => {
);
}
async function normalizeComparablePath(filePath: string): Promise<string> {
const parent = await fs.realpath(path.dirname(filePath)).catch(() => path.dirname(filePath));
return path.join(parent, path.basename(filePath));
}
function createBaseRun(params: {
storePath: string;
sessionEntry: Record<string, unknown>;
@@ -436,6 +392,7 @@ describe("runReplyAgent auto-compaction token update", () => {
const sessionKey = "main";
const sessionEntry = {
sessionId: "session",
sessionFile: path.join(tmp, "session.jsonl"),
updatedAt: Date.now(),
totalTokens: 181_000,
compactionCount: 0,
@@ -524,6 +481,7 @@ describe("runReplyAgent auto-compaction token update", () => {
payloads: [{ text: "done" }],
meta: {
agentMeta: {
sessionId: "session-rotated",
usage: { input: 190_000, output: 8_000, total: 198_000 },
lastCallUsage: { input: 10_000, output: 3_000, total: 13_000 },
compactionCount: 2,
@@ -568,6 +526,10 @@ describe("runReplyAgent auto-compaction token update", () => {
const stored = JSON.parse(await fs.readFile(storePath, "utf-8"));
expect(stored[sessionKey].totalTokens).toBe(10_000);
expect(stored[sessionKey].compactionCount).toBe(2);
expect(stored[sessionKey].sessionId).toBe("session-rotated");
expect(await normalizeComparablePath(stored[sessionKey].sessionFile)).toBe(
await normalizeComparablePath(path.join(tmp, "session-rotated.jsonl")),
);
});
it("accumulates compactions across fallback attempts without double-counting a single attempt", async () => {