fix: flush embedded transcript before afterTurn (#97342)

Fixes #97301

Co-authored-by: OpenClaw PR Agent <openclaw-pr-agent@example.invalid>
This commit is contained in:
WhatsSkiLL
2026-06-28 12:22:58 -07:00
committed by GitHub
co-authored by OpenClaw PR Agent
parent 9f66f61d05
commit 7ac8b48a08
3 changed files with 39 additions and 0 deletions
@@ -2285,6 +2285,36 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
expect(runLlmInput).not.toHaveBeenCalled();
});
it("flushes the embedded session transcript before afterTurn", async () => {
const events: string[] = [];
const afterTurn = vi.fn(async () => {
events.push("afterTurn");
});
hoisted.sessionManager.rewriteFile.mockImplementation(() => {
events.push("flush");
});
await createContextEngineAttemptRunner({
contextEngine: createTestContextEngine({ afterTurn }),
sessionKey,
tempPaths,
attemptOverrides: {
currentInboundEventKind: "room_event",
currentInboundContext: { text: "[OpenClaw room event]" },
suppressNextUserMessagePersistence: true,
transcriptPrompt: "",
},
sessionPrompt: async (session) => {
session.messages = [...session.messages, doneMessage];
},
});
const afterTurnIndex = events.indexOf("afterTurn");
expect(afterTurn).toHaveBeenCalledTimes(1);
expect(afterTurnIndex).not.toBe(-1);
expect(events.slice(0, afterTurnIndex)).toContain("flush");
});
it("forwards sessionKey to bootstrap, assemble, and afterTurn", async () => {
const { bootstrap, assemble } = createContextEngineBootstrapAndAssemble();
const afterTurn = vi.fn(async (_params: { sessionKey?: string }) => {});
@@ -57,6 +57,7 @@ type SessionManagerMocks = {
resetLeaf: UnknownMock;
buildSessionContext: Mock<() => { messages: AgentMessage[] }>;
appendCustomEntry: UnknownMock;
rewriteFile: UnknownMock;
flushPendingToolResults: UnknownMock;
clearPendingToolResults: UnknownMock;
removeTrailingEntries: UnknownMock;
@@ -207,6 +208,7 @@ const hoisted = vi.hoisted((): AttemptSpawnWorkspaceHoisted => {
resetLeaf: vi.fn(),
buildSessionContext: vi.fn<() => { messages: AgentMessage[] }>(() => ({ messages: [] })),
appendCustomEntry: vi.fn(),
rewriteFile: vi.fn(),
flushPendingToolResults: vi.fn(),
clearPendingToolResults: vi.fn(),
removeTrailingEntries: vi.fn(() => 0),
@@ -1033,6 +1035,7 @@ export function resetEmbeddedAttemptHarness(
.mockReset()
.mockReturnValue({ messages: params.sessionMessages ?? [] });
hoisted.sessionManager.appendCustomEntry.mockReset();
hoisted.sessionManager.rewriteFile.mockReset();
if (params.subscribeImpl) {
hoisted.subscribeEmbeddedAgentSessionMock.mockImplementation(params.subscribeImpl);
}
@@ -5110,6 +5110,12 @@ export async function runEmbeddedAttempt(
log.warn(`failed to persist prompt error entry: ${String(entryErr)}`);
}
}
if (activeContextEngine && !beforeAgentFinalizeRevisionReason) {
// Context-engine afterTurn hooks may reconcile against the jsonl, so
// materialize the active turn before finalization reads from disk.
flushSessionManagerFile(activeSessionManager);
}
});
// Let the active context engine run its post-turn lifecycle. These hooks