mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix(session-memory): fallback to rotated transcript after /new
When /new rotates <session>.jsonl to <session>.jsonl.reset.*, the session-memory hook may read an empty active transcript and write header-only memory entries. Add fallback logic to read the latest .jsonl.reset.* sibling when the primary file has no usable content. Also add a unit test covering the rotated transcript path. Fixes #18088 Refs #17563
This commit is contained in:
committed by
Peter Steinberger
parent
769f7631d5
commit
19ae7a4e17
@@ -255,6 +255,51 @@ describe("session-memory hook", () => {
|
||||
expect(memoryContent).toContain("assistant: Fourth message");
|
||||
});
|
||||
|
||||
it("falls back to latest .jsonl.reset.* transcript when active file is empty", async () => {
|
||||
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
|
||||
const sessionsDir = path.join(tempDir, "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
|
||||
const activeSessionFile = await writeWorkspaceFile({
|
||||
dir: sessionsDir,
|
||||
name: "test-session.jsonl",
|
||||
content: "",
|
||||
});
|
||||
|
||||
// Simulate /new rotation where useful content is now in .reset.* file
|
||||
const resetContent = createMockSessionContent([
|
||||
{ role: "user", content: "Message from rotated transcript" },
|
||||
{ role: "assistant", content: "Recovered from reset fallback" },
|
||||
]);
|
||||
await writeWorkspaceFile({
|
||||
dir: sessionsDir,
|
||||
name: "test-session.jsonl.reset.2026-02-16T22-26-33.000Z",
|
||||
content: resetContent,
|
||||
});
|
||||
|
||||
const cfg = {
|
||||
agents: { defaults: { workspace: tempDir } },
|
||||
} satisfies OpenClawConfig;
|
||||
|
||||
const event = createHookEvent("command", "new", "agent:main:main", {
|
||||
cfg,
|
||||
previousSessionEntry: {
|
||||
sessionId: "test-123",
|
||||
sessionFile: activeSessionFile,
|
||||
},
|
||||
});
|
||||
|
||||
await handler(event);
|
||||
|
||||
const memoryDir = path.join(tempDir, "memory");
|
||||
const files = await fs.readdir(memoryDir);
|
||||
expect(files.length).toBe(1);
|
||||
const memoryContent = await fs.readFile(path.join(memoryDir, files[0]), "utf-8");
|
||||
|
||||
expect(memoryContent).toContain("user: Message from rotated transcript");
|
||||
expect(memoryContent).toContain("assistant: Recovered from reset fallback");
|
||||
});
|
||||
|
||||
it("handles empty session files gracefully", async () => {
|
||||
// Should not throw
|
||||
const { files } = await runNewWithPreviousSession({ sessionContent: "" });
|
||||
|
||||
@@ -67,6 +67,46 @@ async function getRecentSessionContent(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try the active transcript first; if /new already rotated it,
|
||||
* fallback to the latest .jsonl.reset.* sibling.
|
||||
*/
|
||||
async function getRecentSessionContentWithResetFallback(
|
||||
sessionFilePath: string,
|
||||
messageCount: number = 15,
|
||||
): Promise<string | null> {
|
||||
const primary = await getRecentSessionContent(sessionFilePath, messageCount);
|
||||
if (primary) {
|
||||
return primary;
|
||||
}
|
||||
|
||||
try {
|
||||
const dir = path.dirname(sessionFilePath);
|
||||
const base = path.basename(sessionFilePath);
|
||||
const resetPrefix = `${base}.reset.`;
|
||||
const files = await fs.readdir(dir);
|
||||
const resetCandidates = files.filter((name) => name.startsWith(resetPrefix)).toSorted();
|
||||
|
||||
if (resetCandidates.length === 0) {
|
||||
return primary;
|
||||
}
|
||||
|
||||
const latestResetPath = path.join(dir, resetCandidates[resetCandidates.length - 1]);
|
||||
const fallback = await getRecentSessionContent(latestResetPath, messageCount);
|
||||
|
||||
if (fallback) {
|
||||
log.debug("Loaded session content from reset fallback", {
|
||||
sessionFilePath,
|
||||
latestResetPath,
|
||||
});
|
||||
}
|
||||
|
||||
return fallback || primary;
|
||||
} catch {
|
||||
return primary;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save session context to memory when /new command is triggered
|
||||
*/
|
||||
@@ -119,8 +159,8 @@ const saveSessionToMemory: HookHandler = async (event) => {
|
||||
let sessionContent: string | null = null;
|
||||
|
||||
if (sessionFile) {
|
||||
// Get recent conversation content
|
||||
sessionContent = await getRecentSessionContent(sessionFile, messageCount);
|
||||
// Get recent conversation content, with fallback to rotated reset transcript.
|
||||
sessionContent = await getRecentSessionContentWithResetFallback(sessionFile, messageCount);
|
||||
log.debug("Session content loaded", {
|
||||
length: sessionContent?.length ?? 0,
|
||||
messageCount,
|
||||
|
||||
Reference in New Issue
Block a user