Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opencode-mem",
"version": "2.39.0",
"version": "2.40.0",
"description": "OpenCode plugin that gives coding agents persistent memory using local Turso/libSQL vector search",
"type": "module",
"main": "dist/plugin.js",
Expand Down
57 changes: 38 additions & 19 deletions tests/plugin-turso-refcount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,26 +79,45 @@ describe("plugin Turso refcount (#443)", () => {
const id = await userPromptManager.savePrompt("sess-b", "msg-b", "/tmp/p", "world");

// Fake a warmed local model so the close path has something to release.
// Provide dispose explicitly: earlier suite mocks may replace embeddingService
// with a stub that lacks the real EmbeddingService methods.
// Restore afterwards: leaving a stub `pipe` object poisons later suite embeds
// (`pipe is not a function` in profile-write).
const { embeddingService } = await import("../src/services/embedding.js");
const released: string[] = [];
Object.assign(embeddingService, {
pipe: { dispose: async () => released.push("dispose") },
isWarmedUp: true,
async dispose() {
await (this as { pipe?: { dispose?: () => Promise<void> } }).pipe?.dispose?.();
},
});

await memoryClient.close();
expect(pluginTursoRefCount()).toBe(1);
expect((await userPromptManager.getPromptById(id))?.content).toBe("world");
// Sibling instance still live: the process-wide model must stay loaded.
expect(released).toEqual([]);

await memoryClient.close();
expect(pluginTursoRefCount()).toBe(0);
expect(released).toEqual(["dispose"]);
const previous = {
pipe: embeddingService.pipe,
isWarmedUp: embeddingService.isWarmedUp,
dispose: embeddingService.dispose,
};
try {
Object.assign(embeddingService, {
pipe: { dispose: async () => released.push("dispose") },
isWarmedUp: true,
});
// Earlier suite mocks may replace embeddingService without dispose.
if (typeof embeddingService.dispose !== "function") {
Object.assign(embeddingService, {
async dispose() {
const pipe = this.pipe as { dispose?: () => Promise<void> } | null;
this.pipe = null;
this.isWarmedUp = false;
await pipe?.dispose?.();
},
});
}

await memoryClient.close();
expect(pluginTursoRefCount()).toBe(1);
expect((await userPromptManager.getPromptById(id))?.content).toBe("world");
// Sibling instance still live: the process-wide model must stay loaded.
expect(released).toEqual([]);

await memoryClient.close();
expect(pluginTursoRefCount()).toBe(0);
expect(released).toEqual(["dispose"]);
} finally {
embeddingService.pipe = previous.pipe;
embeddingService.isWarmedUp = previous.isWarmedUp;
embeddingService.dispose = previous.dispose;
}
});
});
Loading