
























@@ -2,10 +2,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
22import type { SessionEntry } from "../config/sessions.js";
3344const streamSimpleMock = vi.fn();
5+const readFileMock = vi.fn();
6+const parseSessionEntriesMock = vi.fn();
7+const migrateSessionEntriesMock = vi.fn();
58const buildSessionContextMock = vi.fn();
6-const getLeafEntryMock = vi.fn();
7-const branchMock = vi.fn();
8-const resetLeafMock = vi.fn();
99const ensureOpenClawModelsJsonMock = vi.fn();
1010const discoverAuthStorageMock = vi.fn();
1111const discoverModelsMock = vi.fn();
@@ -29,16 +29,18 @@ vi.mock("@mariozechner/pi-ai", async () => {
2929};
3030});
313132+vi.mock("node:fs/promises", () => ({
33+default: {
34+readFile: (...args: unknown[]) => readFileMock(...args),
35+},
36+readFile: (...args: unknown[]) => readFileMock(...args),
37+}));
38+3239vi.mock("@mariozechner/pi-coding-agent", () => ({
40+buildSessionContext: (...args: unknown[]) => buildSessionContextMock(...args),
3341generateSummary: vi.fn(async () => "summary"),
34-SessionManager: {
35-open: () => ({
36-getLeafEntry: getLeafEntryMock,
37-branch: branchMock,
38-resetLeaf: resetLeafMock,
39-buildSessionContext: buildSessionContextMock,
40-}),
41-},
42+migrateSessionEntries: (...args: unknown[]) => migrateSessionEntriesMock(...args),
43+parseSessionEntries: (...args: unknown[]) => parseSessionEntriesMock(...args),
4244}));
43454446vi.mock("./models-config.js", () => ({
@@ -216,6 +218,19 @@ function createAssistantTranscriptMessage(
216218};
217219}
218220221+function createTranscriptEntry(params: { id: string; parentId?: string | null; message: unknown }) {
222+return {
223+type: "message",
224+id: params.id,
225+parentId: params.parentId ?? null,
226+message: params.message,
227+};
228+}
229+230+function mockTranscriptEntries(entries: unknown[]) {
231+parseSessionEntriesMock.mockReturnValue(entries);
232+}
233+219234function mockActiveTranscript(messages: unknown[]) {
220235getActiveEmbeddedRunSnapshotMock.mockReturnValue({
221236transcriptLeafId: "assistant-1",
@@ -266,10 +281,10 @@ function expectSeedOnlyUserContext(context: unknown) {
266281describe("runBtwSideQuestion", () => {
267282beforeEach(() => {
268283streamSimpleMock.mockReset();
284+readFileMock.mockReset();
285+parseSessionEntriesMock.mockReset();
286+migrateSessionEntriesMock.mockReset();
269287buildSessionContextMock.mockReset();
270-getLeafEntryMock.mockReset();
271-branchMock.mockReset();
272-resetLeafMock.mockReset();
273288ensureOpenClawModelsJsonMock.mockReset();
274289discoverAuthStorageMock.mockReset();
275290discoverModelsMock.mockReset();
@@ -284,10 +299,25 @@ describe("runBtwSideQuestion", () => {
284299registerProviderStreamForModelMock.mockReset();
285300diagDebugMock.mockReset();
286301287-buildSessionContextMock.mockReturnValue({
288-messages: [{ role: "user", content: [{ type: "text", text: "hi" }], timestamp: 1 }],
302+readFileMock.mockResolvedValue("mock transcript");
303+parseSessionEntriesMock.mockReturnValue([
304+createTranscriptEntry({
305+id: "user-1",
306+message: { role: "user", content: [{ type: "text", text: "hi" }], timestamp: 1 },
307+}),
308+createTranscriptEntry({
309+id: "assistant-1",
310+parentId: "user-1",
311+message: {
312+role: "assistant",
313+content: [{ type: "text", text: "hello" }],
314+timestamp: 2,
315+},
316+}),
317+]);
318+buildSessionContextMock.mockImplementation((entries: Array<{ message?: unknown }> = []) => {
319+return { messages: entries.flatMap((entry) => (entry.message ? [entry.message] : [])) };
289320});
290-getLeafEntryMock.mockReturnValue(null);
291321resolveModelWithRegistryMock.mockReturnValue({
292322provider: "anthropic",
293323id: "claude-sonnet-4-6",
@@ -662,47 +692,70 @@ describe("runBtwSideQuestion", () => {
662692});
663693664694it("branches away from an unresolved trailing user turn before building BTW context", async () => {
665-getLeafEntryMock.mockReturnValue({
666-type: "message",
695+const assistantEntry = createTranscriptEntry({
696+id: "assistant-1",
697+message: createAssistantTranscriptMessage([{ type: "text", text: "seed answer" }]),
698+});
699+const trailingUserEntry = createTranscriptEntry({
700+id: "user-2",
667701parentId: "assistant-1",
668-message: { role: "user" },
702+message: createUserTranscriptMessage([{ type: "text", text: "unfinished task" }]),
669703});
704+mockTranscriptEntries([assistantEntry, trailingUserEntry]);
670705mockDoneAnswer(MATH_ANSWER);
671706672707const result = await runMathSideQuestion();
673708674-expect(branchMock).toHaveBeenCalledWith("assistant-1");
675-expect(resetLeafMock).not.toHaveBeenCalled();
676-expect(buildSessionContextMock).toHaveBeenCalledTimes(1);
709+expect(buildSessionContextMock).toHaveBeenCalledWith([assistantEntry]);
677710expect(result).toEqual({ text: MATH_ANSWER });
678711});
679712680713it("branches to the active run snapshot leaf when the session is busy", async () => {
714+const userEntry = createTranscriptEntry({
715+id: "user-seed",
716+message: createUserTranscriptMessage(),
717+});
718+const assistantEntry = createTranscriptEntry({
719+id: "assistant-seed",
720+parentId: "user-seed",
721+message: createAssistantTranscriptMessage([{ type: "text", text: "seed answer" }]),
722+});
723+const newerEntry = createTranscriptEntry({
724+id: "newer-user",
725+parentId: "assistant-seed",
726+message: createUserTranscriptMessage([{ type: "text", text: "newer unfinished task" }]),
727+});
728+mockTranscriptEntries([userEntry, assistantEntry, newerEntry]);
681729getActiveEmbeddedRunSnapshotMock.mockReturnValue({
682730transcriptLeafId: "assistant-seed",
683731});
684732mockDoneAnswer(MATH_ANSWER);
685733686734const result = await runMathSideQuestion();
687735688-expect(branchMock).toHaveBeenCalledWith("assistant-seed");
689-expect(getLeafEntryMock).not.toHaveBeenCalled();
736+expect(buildSessionContextMock).toHaveBeenCalledWith([userEntry, assistantEntry]);
690737expect(result).toEqual({ text: MATH_ANSWER });
691738});
692739693740it("falls back when the active run snapshot leaf no longer exists", async () => {
741+const userEntry = createTranscriptEntry({
742+id: "user-seed",
743+message: createUserTranscriptMessage(),
744+});
745+const assistantEntry = createTranscriptEntry({
746+id: "assistant-seed",
747+parentId: "user-seed",
748+message: createAssistantTranscriptMessage([{ type: "text", text: "seed answer" }]),
749+});
750+mockTranscriptEntries([userEntry, assistantEntry]);
694751getActiveEmbeddedRunSnapshotMock.mockReturnValue({
695752transcriptLeafId: "assistant-gone",
696753});
697-branchMock.mockImplementationOnce(() => {
698-throw new Error("Entry 3235c7c4 not found");
699-});
700754mockDoneAnswer(MATH_ANSWER);
701755702756const result = await runMathSideQuestion();
703757704-expect(branchMock).toHaveBeenCalledWith("assistant-gone");
705-expect(resetLeafMock).toHaveBeenCalled();
758+expect(buildSessionContextMock).toHaveBeenCalledWith([userEntry, assistantEntry]);
706759expect(result).toEqual({ text: MATH_ANSWER });
707760expect(diagDebugMock).toHaveBeenCalledWith(
708761expect.stringContaining("btw snapshot leaf unavailable: sessionId=session-1"),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。