Files
openclaw/src/agents/tools/memory-tool.citations.test.ts
2026-02-02 23:45:05 -08:00

124 lines
4.1 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
let backend: "builtin" | "qmd" = "builtin";
const stubManager = {
search: vi.fn(async () => [
{
path: "MEMORY.md",
startLine: 5,
endLine: 7,
score: 0.9,
snippet: "@@ -5,3 @@\nAssistant: noted",
source: "memory" as const,
},
]),
readFile: vi.fn(),
status: () => ({
backend,
files: 1,
chunks: 1,
dirty: false,
workspaceDir: "/workspace",
dbPath: "/workspace/.memory/index.sqlite",
provider: "builtin",
model: "builtin",
requestedProvider: "builtin",
sources: ["memory" as const],
sourceCounts: [{ source: "memory" as const, files: 1, chunks: 1 }],
}),
sync: vi.fn(),
probeVectorAvailability: vi.fn(async () => true),
close: vi.fn(),
};
vi.mock("../../memory/index.js", () => {
return {
getMemorySearchManager: async () => ({ manager: stubManager }),
};
});
import { createMemorySearchTool } from "./memory-tool.js";
beforeEach(() => {
vi.clearAllMocks();
});
describe("memory search citations", () => {
it("appends source information when citations are enabled", async () => {
backend = "builtin";
const cfg = { memory: { citations: "on" }, agents: { list: [{ id: "main", default: true }] } };
const tool = createMemorySearchTool({ config: cfg });
if (!tool) {
throw new Error("tool missing");
}
const result = await tool.execute("call_citations_on", { query: "notes" });
const details = result.details as { results: Array<{ snippet: string; citation?: string }> };
expect(details.results[0]?.snippet).toMatch(/Source: MEMORY.md#L5-L7/);
expect(details.results[0]?.citation).toBe("MEMORY.md#L5-L7");
});
it("leaves snippet untouched when citations are off", async () => {
backend = "builtin";
const cfg = { memory: { citations: "off" }, agents: { list: [{ id: "main", default: true }] } };
const tool = createMemorySearchTool({ config: cfg });
if (!tool) {
throw new Error("tool missing");
}
const result = await tool.execute("call_citations_off", { query: "notes" });
const details = result.details as { results: Array<{ snippet: string; citation?: string }> };
expect(details.results[0]?.snippet).not.toMatch(/Source:/);
expect(details.results[0]?.citation).toBeUndefined();
});
it("clamps decorated snippets to qmd injected budget", async () => {
backend = "qmd";
const cfg = {
memory: { citations: "on", backend: "qmd", qmd: { limits: { maxInjectedChars: 20 } } },
agents: { list: [{ id: "main", default: true }] },
};
const tool = createMemorySearchTool({ config: cfg });
if (!tool) {
throw new Error("tool missing");
}
const result = await tool.execute("call_citations_qmd", { query: "notes" });
const details = result.details as { results: Array<{ snippet: string; citation?: string }> };
expect(details.results[0]?.snippet.length).toBeLessThanOrEqual(20);
});
it("honors auto mode for direct chats", async () => {
backend = "builtin";
const cfg = {
memory: { citations: "auto" },
agents: { list: [{ id: "main", default: true }] },
};
const tool = createMemorySearchTool({
config: cfg,
agentSessionKey: "agent:main:discord:dm:u123",
});
if (!tool) {
throw new Error("tool missing");
}
const result = await tool.execute("auto_mode_direct", { query: "notes" });
const details = result.details as { results: Array<{ snippet: string }> };
expect(details.results[0]?.snippet).toMatch(/Source:/);
});
it("suppresses citations for auto mode in group chats", async () => {
backend = "builtin";
const cfg = {
memory: { citations: "auto" },
agents: { list: [{ id: "main", default: true }] },
};
const tool = createMemorySearchTool({
config: cfg,
agentSessionKey: "agent:main:discord:group:c123",
});
if (!tool) {
throw new Error("tool missing");
}
const result = await tool.execute("auto_mode_group", { query: "notes" });
const details = result.details as { results: Array<{ snippet: string }> };
expect(details.results[0]?.snippet).not.toMatch(/Source:/);
});
});