Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 151 additions & 7 deletions test/recall-text-cleanup.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@ const jiti = jitiFactory(import.meta.url, {
},
});

// IMPORTANT: Get retriever/embedder module references BEFORE importing index.ts.
// This is because index.ts captures the createRetriever/createEmbedder binding at
// import time. We must reassign the module's exports before index.ts loads.
const retrieverModuleForMock = jiti("../src/retriever.js");
const embedderModuleForMock = jiti("../src/embedder.js");
const origCreateRetriever = retrieverModuleForMock.createRetriever;
const origCreateEmbedder = embedderModuleForMock.createEmbedder;

const pluginModule = jiti("../index.ts");
const memoryLanceDBProPlugin = pluginModule.default || pluginModule;
const { registerMemoryRecallTool, registerMemoryStoreTool } = jiti("../src/tools.ts");
const { MemoryRetriever } = jiti("../src/retriever.ts");
const { MemoryRetriever } = jiti("../src/retriever.js");
const { buildSmartMetadata, stringifySmartMetadata } = jiti("../src/smart-metadata.ts");

function makeApiCapture() {
Expand Down Expand Up @@ -325,6 +333,9 @@ describe("recall text cleanup", () => {

afterEach(() => {
MemoryRetriever.prototype.retrieve = originalRetrieve;
// Restore factory functions on the .js module (same cache as index.ts uses)
retrieverModuleForMock.createRetriever = origCreateRetriever;
embedderModuleForMock.createEmbedder = origCreateEmbedder;
rmSync(workspaceDir, { recursive: true, force: true });
});

Expand Down Expand Up @@ -361,7 +372,35 @@ describe("recall text cleanup", () => {
});

it("removes retrieval metadata from auto-recall injected text", async () => {
MemoryRetriever.prototype.retrieve = async () => makeResults();
// jiti caches ./src/retriever.js (used by index.ts) and ../src/retriever.ts
// (used by the test) as SEPARATE module instances. Patching
// MemoryRetriever.prototype does NOT reach the instance the plugin creates
// via createRetriever. Instead we intercept the factory.
const mockResults = makeResults();
const retrieverMod = jiti("../src/retriever.js");
retrieverMod.createRetriever = function mockCreateRetriever(store, embedder, config, options) {
return {
async retrieve(context = {}) {
return mockResults;
},
getConfig() {
return { mode: "hybrid" };
},
setAccessTracker() {},
setStatsCollector() {},
};
};
const embedderMod = jiti("../src/embedder.js");
embedderMod.createEmbedder = function mockCreateEmbedder() {
return {
async embedQuery() {
return new Float32Array(384).fill(0);
},
async embedPassage() {
return new Float32Array(384).fill(0);
},
};
};

const harness = createPluginApiHarness({
resolveRoot: workspaceDir,
Expand Down Expand Up @@ -427,7 +466,33 @@ describe("recall text cleanup", () => {
});

it("applies auto-recall item/char budgets before injecting context", async () => {
MemoryRetriever.prototype.retrieve = async () => makeManyResults(5);
// Intercept the factory functions instead of patching prototype (same jiti
// cache mismatch reason as the test above).
const mockResults = makeManyResults(5);
const retrieverMod = jiti("../src/retriever.js");
retrieverMod.createRetriever = function mockCreateRetriever(store, embedder, config, options) {
return {
async retrieve(context = {}) {
return mockResults;
},
getConfig() {
return { mode: "hybrid" };
},
setAccessTracker() {},
setStatsCollector() {},
};
};
const embedderMod = jiti("../src/embedder.js");
embedderMod.createEmbedder = function mockCreateEmbedder() {
return {
async embedQuery() {
return new Float32Array(384).fill(0);
},
async embedPassage() {
return new Float32Array(384).fill(0);
},
};
};

const harness = createPluginApiHarness({
resolveRoot: workspaceDir,
Expand Down Expand Up @@ -462,7 +527,33 @@ describe("recall text cleanup", () => {
});

it("auto-recall only injects confirmed non-archived memories", async () => {
MemoryRetriever.prototype.retrieve = async () => makeGovernanceFilteredResults();
// Intercept the factory functions instead of patching prototype (same jiti
// cache mismatch reason as the test above).
const mockResults = makeGovernanceFilteredResults();
const retrieverMod = jiti("../src/retriever.js");
retrieverMod.createRetriever = function mockCreateRetriever(store, embedder, config, options) {
return {
async retrieve(context = {}) {
return mockResults;
},
getConfig() {
return { mode: "hybrid" };
},
setAccessTracker() {},
setStatsCollector() {},
};
};
const embedderMod = jiti("../src/embedder.js");
embedderMod.createEmbedder = function mockCreateEmbedder() {
return {
async embedQuery() {
return new Float32Array(384).fill(0);
},
async embedPassage() {
return new Float32Array(384).fill(0);
},
};
};

const harness = createPluginApiHarness({
resolveRoot: workspaceDir,
Expand Down Expand Up @@ -551,7 +642,33 @@ describe("recall text cleanup", () => {
});

it("filters USER.md-exclusive facts from auto-recall injected text", async () => {
MemoryRetriever.prototype.retrieve = async () => makeUserMdExclusiveResults();
// Intercept the factory functions instead of patching prototype (same jiti
// cache mismatch reason as the test above).
const mockResults = makeUserMdExclusiveResults();
const retrieverMod = jiti("../src/retriever.js");
retrieverMod.createRetriever = function mockCreateRetriever(store, embedder, config, options) {
return {
async retrieve(context = {}) {
return mockResults;
},
getConfig() {
return { mode: "hybrid" };
},
setAccessTracker() {},
setStatsCollector() {},
};
};
const embedderMod = jiti("../src/embedder.js");
embedderMod.createEmbedder = function mockCreateEmbedder() {
return {
async embedQuery() {
return new Float32Array(384).fill(0);
},
async embedPassage() {
return new Float32Array(384).fill(0);
},
};
};

const harness = createPluginApiHarness({
resolveRoot: workspaceDir,
Expand Down Expand Up @@ -607,7 +724,33 @@ describe("recall text cleanup", () => {
});

it("filters legacy addressing memories from auto-recall injected text", async () => {
MemoryRetriever.prototype.retrieve = async () => makeLegacyAddressingResults();
// Intercept the factory functions instead of patching prototype (same jiti
// cache mismatch reason as the test above).
const mockResults = makeLegacyAddressingResults();
const retrieverMod = jiti("../src/retriever.js");
retrieverMod.createRetriever = function mockCreateRetriever(store, embedder, config, options) {
return {
async retrieve(context = {}) {
return mockResults;
},
getConfig() {
return { mode: "hybrid" };
},
setAccessTracker() {},
setStatsCollector() {},
};
};
const embedderMod = jiti("../src/embedder.js");
embedderMod.createEmbedder = function mockCreateEmbedder() {
return {
async embedQuery() {
return new Float32Array(384).fill(0);
},
async embedPassage() {
return new Float32Array(384).fill(0);
},
};
};

const harness = createPluginApiHarness({
resolveRoot: workspaceDir,
Expand Down Expand Up @@ -640,7 +783,7 @@ describe("recall text cleanup", () => {

assert.ok(output);
assert.match(output.prependContext, /remember this/);
assert.doesNotMatch(output.prependContext, /希望在主会话中被称呼为“宙斯”/);
assert.doesNotMatch(output.prependContext, /希望在主会话中被称呼为"宙斯"/);
});

it("respects filterRecall=false for memory_recall output", async () => {
Expand All @@ -659,3 +802,4 @@ describe("recall text cleanup", () => {
assert.match(res.content[0].text, /称呼偏好:宙斯/);
});
});

Loading