refactor(test): dedupe session-memory hook setup

This commit is contained in:
Peter Steinberger
2026-02-15 15:09:26 +00:00
parent 71c1d09f22
commit beffb6fe48

View File

@@ -40,6 +40,44 @@ function createMockSessionContent(
.join("\n"); .join("\n");
} }
async function runNewWithPreviousSession(params: {
sessionContent: string;
cfg?: (tempDir: string) => OpenClawConfig;
}): Promise<{ tempDir: string; files: string[]; memoryContent: string }> {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
const sessionFile = await writeWorkspaceFile({
dir: sessionsDir,
name: "test-session.jsonl",
content: params.sessionContent,
});
const cfg =
params.cfg?.(tempDir) ??
({
agents: { defaults: { workspace: tempDir } },
} satisfies OpenClawConfig);
const event = createHookEvent("command", "new", "agent:main:main", {
cfg,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
},
});
await handler(event);
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
const memoryContent =
files.length > 0 ? await fs.readFile(path.join(memoryDir, files[0]), "utf-8") : "";
return { tempDir, files, memoryContent };
}
describe("session-memory hook", () => { describe("session-memory hook", () => {
it("skips non-command events", async () => { it("skips non-command events", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-"); const tempDir = await makeTempWorkspace("openclaw-session-memory-");
@@ -70,10 +108,6 @@ describe("session-memory hook", () => {
}); });
it("creates memory file with session content on /new command", async () => { it("creates memory file with session content on /new command", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
// Create a mock session file with user/assistant messages // Create a mock session file with user/assistant messages
const sessionContent = createMockSessionContent([ const sessionContent = createMockSessionContent([
{ role: "user", content: "Hello there" }, { role: "user", content: "Hello there" },
@@ -81,33 +115,10 @@ describe("session-memory hook", () => {
{ role: "user", content: "What is 2+2?" }, { role: "user", content: "What is 2+2?" },
{ role: "assistant", content: "2+2 equals 4" }, { role: "assistant", content: "2+2 equals 4" },
]); ]);
const sessionFile = await writeWorkspaceFile({ const { files, memoryContent } = await runNewWithPreviousSession({ sessionContent });
dir: sessionsDir,
name: "test-session.jsonl",
content: sessionContent,
});
const cfg: OpenClawConfig = {
agents: { defaults: { workspace: tempDir } },
};
const event = createHookEvent("command", "new", "agent:main:main", {
cfg,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
},
});
await handler(event);
// Memory file should be created
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
expect(files.length).toBe(1); expect(files.length).toBe(1);
// Read the memory file and verify content // Read the memory file and verify content
const memoryContent = await fs.readFile(path.join(memoryDir, files[0]), "utf-8");
expect(memoryContent).toContain("user: Hello there"); expect(memoryContent).toContain("user: Hello there");
expect(memoryContent).toContain("assistant: Hi! How can I help?"); expect(memoryContent).toContain("assistant: Hi! How can I help?");
expect(memoryContent).toContain("user: What is 2+2?"); expect(memoryContent).toContain("user: What is 2+2?");
@@ -115,10 +126,6 @@ describe("session-memory hook", () => {
}); });
it("filters out non-message entries (tool calls, system)", async () => { it("filters out non-message entries (tool calls, system)", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
// Create session with mixed entry types // Create session with mixed entry types
const sessionContent = createMockSessionContent([ const sessionContent = createMockSessionContent([
{ role: "user", content: "Hello" }, { role: "user", content: "Hello" },
@@ -127,29 +134,7 @@ describe("session-memory hook", () => {
{ type: "tool_result", result: "found it" }, { type: "tool_result", result: "found it" },
{ role: "user", content: "Thanks" }, { role: "user", content: "Thanks" },
]); ]);
const sessionFile = await writeWorkspaceFile({ const { memoryContent } = await runNewWithPreviousSession({ sessionContent });
dir: sessionsDir,
name: "test-session.jsonl",
content: sessionContent,
});
const cfg: OpenClawConfig = {
agents: { defaults: { workspace: tempDir } },
};
const event = createHookEvent("command", "new", "agent:main:main", {
cfg,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
},
});
await handler(event);
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
const memoryContent = await fs.readFile(path.join(memoryDir, files[0]), "utf-8");
// Only user/assistant messages should be present // Only user/assistant messages should be present
expect(memoryContent).toContain("user: Hello"); expect(memoryContent).toContain("user: Hello");
@@ -162,10 +147,6 @@ describe("session-memory hook", () => {
}); });
it("filters out inter-session user messages", async () => { it("filters out inter-session user messages", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
const sessionContent = [ const sessionContent = [
JSON.stringify({ JSON.stringify({
type: "message", type: "message",
@@ -184,29 +165,7 @@ describe("session-memory hook", () => {
message: { role: "user", content: "External follow-up" }, message: { role: "user", content: "External follow-up" },
}), }),
].join("\n"); ].join("\n");
const sessionFile = await writeWorkspaceFile({ const { memoryContent } = await runNewWithPreviousSession({ sessionContent });
dir: sessionsDir,
name: "test-session.jsonl",
content: sessionContent,
});
const cfg: OpenClawConfig = {
agents: { defaults: { workspace: tempDir } },
};
const event = createHookEvent("command", "new", "agent:main:main", {
cfg,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
},
});
await handler(event);
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
const memoryContent = await fs.readFile(path.join(memoryDir, files[0]), "utf-8");
expect(memoryContent).not.toContain("Forwarded internal instruction"); expect(memoryContent).not.toContain("Forwarded internal instruction");
expect(memoryContent).toContain("assistant: Acknowledged"); expect(memoryContent).toContain("assistant: Acknowledged");
@@ -214,39 +173,13 @@ describe("session-memory hook", () => {
}); });
it("filters out command messages starting with /", async () => { it("filters out command messages starting with /", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
const sessionContent = createMockSessionContent([ const sessionContent = createMockSessionContent([
{ role: "user", content: "/help" }, { role: "user", content: "/help" },
{ role: "assistant", content: "Here is help info" }, { role: "assistant", content: "Here is help info" },
{ role: "user", content: "Normal message" }, { role: "user", content: "Normal message" },
{ role: "user", content: "/new" }, { role: "user", content: "/new" },
]); ]);
const sessionFile = await writeWorkspaceFile({ const { memoryContent } = await runNewWithPreviousSession({ sessionContent });
dir: sessionsDir,
name: "test-session.jsonl",
content: sessionContent,
});
const cfg: OpenClawConfig = {
agents: { defaults: { workspace: tempDir } },
};
const event = createHookEvent("command", "new", "agent:main:main", {
cfg,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
},
});
await handler(event);
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
const memoryContent = await fs.readFile(path.join(memoryDir, files[0]), "utf-8");
// Command messages should be filtered out // Command messages should be filtered out
expect(memoryContent).not.toContain("/help"); expect(memoryContent).not.toContain("/help");
@@ -257,48 +190,26 @@ describe("session-memory hook", () => {
}); });
it("respects custom messages config (limits to N messages)", async () => { it("respects custom messages config (limits to N messages)", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
// Create 10 messages // Create 10 messages
const entries = []; const entries = [];
for (let i = 1; i <= 10; i++) { for (let i = 1; i <= 10; i++) {
entries.push({ role: "user", content: `Message ${i}` }); entries.push({ role: "user", content: `Message ${i}` });
} }
const sessionContent = createMockSessionContent(entries); const sessionContent = createMockSessionContent(entries);
const sessionFile = await writeWorkspaceFile({ const { memoryContent } = await runNewWithPreviousSession({
dir: sessionsDir, sessionContent,
name: "test-session.jsonl", cfg: (tempDir) => ({
content: sessionContent, agents: { defaults: { workspace: tempDir } },
}); hooks: {
internal: {
// Configure to only include last 3 messages entries: {
const cfg: OpenClawConfig = { "session-memory": { enabled: true, messages: 3 },
agents: { defaults: { workspace: tempDir } }, },
hooks: {
internal: {
entries: {
"session-memory": { enabled: true, messages: 3 },
}, },
}, },
}, }),
};
const event = createHookEvent("command", "new", "agent:main:main", {
cfg,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
},
}); });
await handler(event);
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
const memoryContent = await fs.readFile(path.join(memoryDir, files[0]), "utf-8");
// Only last 3 messages should be present // Only last 3 messages should be present
expect(memoryContent).not.toContain("user: Message 1\n"); expect(memoryContent).not.toContain("user: Message 1\n");
expect(memoryContent).not.toContain("user: Message 7\n"); expect(memoryContent).not.toContain("user: Message 7\n");
@@ -308,10 +219,6 @@ describe("session-memory hook", () => {
}); });
it("filters messages before slicing (fix for #2681)", async () => { it("filters messages before slicing (fix for #2681)", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
// Create session with many tool entries interspersed with messages // Create session with many tool entries interspersed with messages
// This tests that we filter FIRST, then slice - not the other way around // This tests that we filter FIRST, then slice - not the other way around
const entries = [ const entries = [
@@ -327,39 +234,20 @@ describe("session-memory hook", () => {
{ role: "assistant", content: "Fourth message" }, { role: "assistant", content: "Fourth message" },
]; ];
const sessionContent = createMockSessionContent(entries); const sessionContent = createMockSessionContent(entries);
const sessionFile = await writeWorkspaceFile({ const { memoryContent } = await runNewWithPreviousSession({
dir: sessionsDir, sessionContent,
name: "test-session.jsonl", cfg: (tempDir) => ({
content: sessionContent, agents: { defaults: { workspace: tempDir } },
}); hooks: {
internal: {
// Request 3 messages - if we sliced first, we'd only get 1-2 messages entries: {
// because the last 3 lines include tool entries "session-memory": { enabled: true, messages: 3 },
const cfg: OpenClawConfig = { },
agents: { defaults: { workspace: tempDir } },
hooks: {
internal: {
entries: {
"session-memory": { enabled: true, messages: 3 },
}, },
}, },
}, }),
};
const event = createHookEvent("command", "new", "agent:main:main", {
cfg,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
},
}); });
await handler(event);
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
const memoryContent = await fs.readFile(path.join(memoryDir, files[0]), "utf-8");
// Should have exactly 3 user/assistant messages (the last 3) // Should have exactly 3 user/assistant messages (the last 3)
expect(memoryContent).not.toContain("First message"); expect(memoryContent).not.toContain("First message");
expect(memoryContent).toContain("user: Third message"); expect(memoryContent).toContain("user: Third message");
@@ -368,70 +256,18 @@ describe("session-memory hook", () => {
}); });
it("handles empty session files gracefully", async () => { it("handles empty session files gracefully", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
const sessionFile = await writeWorkspaceFile({
dir: sessionsDir,
name: "test-session.jsonl",
content: "",
});
const cfg: OpenClawConfig = {
agents: { defaults: { workspace: tempDir } },
};
const event = createHookEvent("command", "new", "agent:main:main", {
cfg,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
},
});
// Should not throw // Should not throw
await handler(event); const { files } = await runNewWithPreviousSession({ sessionContent: "" });
// Memory file should still be created with metadata
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
expect(files.length).toBe(1); expect(files.length).toBe(1);
}); });
it("handles session files with fewer messages than requested", async () => { it("handles session files with fewer messages than requested", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
// Only 2 messages but requesting 15 (default) // Only 2 messages but requesting 15 (default)
const sessionContent = createMockSessionContent([ const sessionContent = createMockSessionContent([
{ role: "user", content: "Only message 1" }, { role: "user", content: "Only message 1" },
{ role: "assistant", content: "Only message 2" }, { role: "assistant", content: "Only message 2" },
]); ]);
const sessionFile = await writeWorkspaceFile({ const { memoryContent } = await runNewWithPreviousSession({ sessionContent });
dir: sessionsDir,
name: "test-session.jsonl",
content: sessionContent,
});
const cfg: OpenClawConfig = {
agents: { defaults: { workspace: tempDir } },
};
const event = createHookEvent("command", "new", "agent:main:main", {
cfg,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
},
});
await handler(event);
const memoryDir = path.join(tempDir, "memory");
const files = await fs.readdir(memoryDir);
const memoryContent = await fs.readFile(path.join(memoryDir, files[0]), "utf-8");
// Both messages should be included // Both messages should be included
expect(memoryContent).toContain("user: Only message 1"); expect(memoryContent).toContain("user: Only message 1");