mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 10:11:24 +00:00
refactor(test): dedupe session-memory hook setup
This commit is contained in:
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user