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:
Kartik
2026-04-20 20:09:45 +05:30
committed by GitHub
co-authored by Livia Ellen
parent 5520226b5b
commit 4e611e8dba
31 changed files with 1530 additions and 364 deletions
+7 -1
View File
@@ -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({});
});
});
+7 -5
View File
@@ -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", () => {
+96
View File
@@ -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");
});
});
+3 -10
View File
@@ -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",
}),
);
});
+1 -1
View File
@@ -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", () => {
+3 -3
View File
@@ -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 });
}
});
});