Forráskód Böngészése

fix: invalidate agent cache on transcript changes

fanghanjun 6 hónapja
szülő
commit
8b3d5be408

+ 35 - 1
packages/core/src/__tests__/agent-session.test.ts

@@ -129,7 +129,11 @@ vi.mock("@mariozechner/pi-ai", async () => {
 });
 
 import { runAgentSession, evictAgentCache } from "../agent/agent-session.js";
-import { appendTranscriptEvent, readTranscriptEvents } from "../interaction/session-transcript.js";
+import {
+  appendManualSessionMessages,
+  appendTranscriptEvent,
+  readTranscriptEvents,
+} from "../interaction/session-transcript.js";
 import { restoreAgentMessagesFromTranscript } from "../interaction/session-transcript-restore.js";
 
 describe("runAgentSession cache — bookId switch", () => {
@@ -153,6 +157,7 @@ describe("runAgentSession cache — bookId switch", () => {
 
   afterEach(async () => {
     evictAgentCache("s1");
+    evictAgentCache("s-cache-seq");
     evictAgentCache("s-error");
     await rm(projectRoot, { recursive: true, force: true });
   });
@@ -282,6 +287,35 @@ describe("runAgentSession cache — bookId switch", () => {
     expect(streamCalls.at(-1)?.model.baseUrl).toBe("https://two.example/v1");
   });
 
+  it("rebuilds cached Agent when transcript committed seq changes outside cache", async () => {
+    const model = { provider: "anthropic", id: "fake", api: "anthropic-messages" } as any;
+    const pipeline = {} as any;
+
+    await runAgentSession(
+      { sessionId: "s-cache-seq", bookId: "book-a", language: "zh", pipeline, projectRoot, model },
+      "hello",
+    );
+
+    await appendManualSessionMessages(projectRoot, "s-cache-seq", [{
+      role: "assistant",
+      content: [{ type: "text", text: "manual fallback persisted" }],
+      api: "anthropic-messages",
+      provider: "anthropic",
+      model: "fake",
+      usage: EMPTY_USAGE,
+      stopReason: "stop",
+      timestamp: Date.now(),
+    } as any], "fallback-input");
+
+    await runAgentSession(
+      { sessionId: "s-cache-seq", bookId: "book-a", language: "zh", pipeline, projectRoot, model },
+      "again",
+    );
+
+    expect(agentInstances).toHaveLength(2);
+    expect(JSON.stringify(streamCalls.at(-1)?.context.messages)).toContain("manual fallback persisted");
+  });
+
   it("enables system file read by default for the session read tool", async () => {
     const model = { provider: "x", id: "y", api: "anthropic-messages" } as any;
     const pipeline = {} as any;

+ 5 - 2
packages/core/src/agent/agent-session.ts

@@ -444,8 +444,10 @@ export async function runAgentSession(
 
   // ----- Resolve or create Agent -----
   let cached = agentCache.get(sessionId);
+  let currentCommittedSeq: number | undefined;
 
   if (cached) {
+    currentCommittedSeq = await latestCommittedSeq(projectRoot, sessionId);
     // Evict and rebuild if model protocol identity OR bookId changed. Both are
     // captured into the Agent at construction time (model via initialState,
     // bookId via closures in systemPrompt / tools / transformContext), so a
@@ -453,8 +455,9 @@ export async function runAgentSession(
     const modelChanged = cached.modelIdentity !== requestedModelIdentity;
     const bookChanged = cached.bookId !== bookId;
     const readPermissionChanged = cached.allowSystemFileRead !== allowSystemFileRead;
+    const transcriptChanged = cached.lastCommittedSeq !== currentCommittedSeq;
 
-    if (modelChanged || bookChanged || readPermissionChanged) {
+    if (modelChanged || bookChanged || readPermissionChanged || transcriptChanged) {
       agentCache.delete(sessionId);
       cached = undefined;
     }
@@ -501,7 +504,7 @@ export async function runAgentSession(
       bookId,
       modelIdentity: requestedModelIdentity,
       allowSystemFileRead,
-      lastCommittedSeq: await latestCommittedSeq(projectRoot, sessionId),
+      lastCommittedSeq: currentCommittedSeq ?? await latestCommittedSeq(projectRoot, sessionId),
       lastActive: Date.now(),
     };
     agentCache.set(sessionId, cached);