docs: update memory tool list, CLI usage, and config file reading logic (#4861)
Co-authored-by: Livia Ellen <liviaellen@msn.com>
This commit is contained in:
@@ -113,9 +113,15 @@ describe("readPluginAuth", () => {
|
||||
expect(auth.userId).toBe("user-snake");
|
||||
});
|
||||
|
||||
it("returns empty object when JSON is invalid", () => {
|
||||
it("throws error when JSON is invalid (prevents config destruction)", () => {
|
||||
mockExists.mockReturnValue(true);
|
||||
mockReadText.mockReturnValue("not valid json {{{");
|
||||
expect(() => readPluginAuth()).toThrow(/Failed to parse[\s\S]*Fix the JSON syntax error/);
|
||||
});
|
||||
|
||||
it("returns empty object when config file is empty", () => {
|
||||
mockExists.mockReturnValue(true);
|
||||
mockReadText.mockReturnValue(" ");
|
||||
expect(readPluginAuth()).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,9 +74,10 @@ describe("mem0ConfigSchema.parse() — defaults", () => {
|
||||
expect(cfg.customCategories).toBe(DEFAULT_CUSTOM_CATEGORIES);
|
||||
});
|
||||
|
||||
it("customPrompt defaults to DEFAULT_CUSTOM_INSTRUCTIONS", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" });
|
||||
expect(cfg.customPrompt).toBe(DEFAULT_CUSTOM_INSTRUCTIONS);
|
||||
// v3.0.0: customPrompt removed, use customInstructions instead
|
||||
it("customPrompt input falls back to customInstructions", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key", customPrompt: "My prompt" });
|
||||
expect(cfg.customInstructions).toBe("My prompt");
|
||||
});
|
||||
|
||||
it("oss defaults to undefined", () => {
|
||||
@@ -296,13 +297,14 @@ describe("mem0ConfigSchema.parse() — explicit overrides", () => {
|
||||
expect(cfg.customInstructions).toBe(custom);
|
||||
});
|
||||
|
||||
it("custom customPrompt overrides defaults", () => {
|
||||
// v3.0.0: customPrompt renamed to customInstructions (backwards compat: customPrompt maps to customInstructions)
|
||||
it("customPrompt input maps to customInstructions output", () => {
|
||||
const custom = "My custom prompt";
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
apiKey: "k",
|
||||
customPrompt: custom,
|
||||
});
|
||||
expect(cfg.customPrompt).toBe(custom);
|
||||
expect(cfg.customInstructions).toBe(custom);
|
||||
});
|
||||
|
||||
it("custom customCategories override defaults", () => {
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
isNoiseMessage,
|
||||
isGenericAssistantMessage,
|
||||
isSessionSpecificContent,
|
||||
stripNoiseFromContent,
|
||||
filterMessagesForExtraction,
|
||||
} from "../filtering.ts";
|
||||
|
||||
describe("isNoiseMessage", () => {
|
||||
it("returns true for heartbeat messages", () => {
|
||||
expect(isNoiseMessage("HEARTBEAT_OK")).toBe(true);
|
||||
expect(isNoiseMessage("NO_REPLY")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for single-word acknowledgments", () => {
|
||||
expect(isNoiseMessage("ok")).toBe(true);
|
||||
expect(isNoiseMessage("done")).toBe(true);
|
||||
expect(isNoiseMessage("thanks")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for substantive content", () => {
|
||||
expect(isNoiseMessage("My name is John and I live in Tokyo")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isGenericAssistantMessage", () => {
|
||||
it("returns true for generic acknowledgments", () => {
|
||||
expect(isGenericAssistantMessage("I see you've shared this. How can I help?")).toBe(true);
|
||||
expect(isGenericAssistantMessage("Got it! How can I assist you?")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for substantive responses", () => {
|
||||
expect(isGenericAssistantMessage("Based on the code, the bug is in line 42 where the null check is missing.")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isSessionSpecificContent", () => {
|
||||
it("returns true for tool availability discussions", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"I do not currently see Mem0 write/update/delete tools exposed in this session."
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for plugin capability statements", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"As of 2026-04-18, the openclaw-mem0 plugin does not expose a memory wiki capability."
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for session-specific tool lists", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"The tools I have access to in this session are memory_search and memory_get."
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for user preferences", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"My name is Kartik and I like to watch Pokemon."
|
||||
)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for technical facts that span sessions", () => {
|
||||
expect(isSessionSpecificContent(
|
||||
"The project uses TypeScript 5.0 and Node.js 20."
|
||||
)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("filterMessagesForExtraction", () => {
|
||||
it("filters out session-specific tool discussions", () => {
|
||||
const messages = [
|
||||
{ role: "user", content: "What tools do you have access to?" },
|
||||
{ role: "assistant", content: "I have memory_search and memory_get tools. The other tools are not exposed in this session." },
|
||||
{ role: "user", content: "My name is Kartik" },
|
||||
{ role: "assistant", content: "Got it, I'll remember that!" },
|
||||
];
|
||||
|
||||
const filtered = filterMessagesForExtraction(messages);
|
||||
|
||||
// Should keep the name statement, filter the tool discussion
|
||||
expect(filtered.some(m => m.content.includes("Kartik"))).toBe(true);
|
||||
expect(filtered.some(m => m.content.includes("not exposed in this session"))).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps valuable user facts", () => {
|
||||
const messages = [
|
||||
{ role: "user", content: "I prefer dark mode and use VS Code for development." },
|
||||
{ role: "assistant", content: "Noted! Dark mode in VS Code is great for reducing eye strain." },
|
||||
];
|
||||
|
||||
const filtered = filterMessagesForExtraction(messages);
|
||||
expect(filtered.length).toBe(2);
|
||||
expect(filtered[0].content).toContain("dark mode");
|
||||
});
|
||||
});
|
||||
@@ -42,6 +42,7 @@ beforeEach(() => {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("providerToBackend — search", () => {
|
||||
// v3.0.0: keyword_search, reranking removed from SDK
|
||||
it("delegates to provider.search with correct options", async () => {
|
||||
const provider = createMockProvider();
|
||||
const backend = providerToBackend(provider as any, DEFAULT_USER);
|
||||
@@ -49,8 +50,6 @@ describe("providerToBackend — search", () => {
|
||||
const results = await backend.search("hello world", {
|
||||
topK: 10,
|
||||
threshold: 0.5,
|
||||
keyword: true,
|
||||
rerank: true,
|
||||
filters: { category: "preference" },
|
||||
});
|
||||
|
||||
@@ -58,8 +57,6 @@ describe("providerToBackend — search", () => {
|
||||
user_id: DEFAULT_USER,
|
||||
top_k: 10,
|
||||
threshold: 0.5,
|
||||
keyword_search: true,
|
||||
reranking: true,
|
||||
filters: { category: "preference" },
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
@@ -73,12 +70,11 @@ describe("providerToBackend — search", () => {
|
||||
|
||||
await backend.search("query");
|
||||
|
||||
// v3.0.0: keyword_search, reranking removed
|
||||
expect(provider.search).toHaveBeenCalledWith("query", {
|
||||
user_id: DEFAULT_USER,
|
||||
top_k: undefined,
|
||||
threshold: undefined,
|
||||
keyword_search: undefined,
|
||||
reranking: undefined,
|
||||
filters: undefined,
|
||||
source: "OPENCLAW",
|
||||
});
|
||||
@@ -119,6 +115,7 @@ describe("providerToBackend — add", () => {
|
||||
);
|
||||
});
|
||||
|
||||
// v3.0.0: immutable, expiration_date removed from SDK
|
||||
it("forwards optional add options", async () => {
|
||||
const provider = createMockProvider();
|
||||
const backend = providerToBackend(provider as any, DEFAULT_USER);
|
||||
@@ -126,9 +123,7 @@ describe("providerToBackend — add", () => {
|
||||
await backend.add("fact", undefined, {
|
||||
runId: "run-1",
|
||||
metadata: { source: "test" },
|
||||
immutable: true,
|
||||
infer: false,
|
||||
expires: "2027-01-01",
|
||||
});
|
||||
|
||||
expect(provider.add).toHaveBeenCalledWith(
|
||||
@@ -137,9 +132,7 @@ describe("providerToBackend — add", () => {
|
||||
user_id: DEFAULT_USER,
|
||||
run_id: "run-1",
|
||||
metadata: { source: "test" },
|
||||
immutable: true,
|
||||
infer: false,
|
||||
expiration_date: "2027-01-01",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("telemetry", () => {
|
||||
});
|
||||
|
||||
it("exports PLUGIN_VERSION", () => {
|
||||
expect(PLUGIN_VERSION).toBe("1.0.6");
|
||||
expect(PLUGIN_VERSION).toBe("1.0.7");
|
||||
});
|
||||
|
||||
it("captureEvent does not throw", () => {
|
||||
|
||||
@@ -37,7 +37,6 @@ function createMockToolDeps(overrides = {}): ToolDeps {
|
||||
searchThreshold: 0.5,
|
||||
customInstructions: "test",
|
||||
customCategories: {},
|
||||
customPrompt: "test",
|
||||
} as any,
|
||||
provider: {
|
||||
search: vi
|
||||
@@ -108,13 +107,14 @@ describe("registerAllTools", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("registers tools without a second argument (required, not optional)", () => {
|
||||
it("registers tools with optional: false metadata", () => {
|
||||
const ctx = createMockToolDeps();
|
||||
registerAllTools(ctx);
|
||||
|
||||
const calls = (ctx.api.registerTool as ReturnType<typeof vi.fn>).mock.calls;
|
||||
for (const call of calls) {
|
||||
expect(call).toHaveLength(1);
|
||||
expect(call).toHaveLength(2);
|
||||
expect(call[1]).toEqual({ optional: false });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user