| import fs from "node:fs/promises"; |
| import os from "node:os"; |
| import path from "node:path"; |
| import { afterEach, describe, expect, it, vi } from "vitest"; |
| import { getMemorySearchManager, type MemoryIndexManager } from "./index.js"; |
|
|
| const { watchMock } = vi.hoisted(() => ({ |
| watchMock: vi.fn(() => ({ |
| on: vi.fn(), |
| close: vi.fn(async () => undefined), |
| })), |
| })); |
|
|
| vi.mock("chokidar", () => ({ |
| default: { watch: watchMock }, |
| watch: watchMock, |
| })); |
|
|
| vi.mock("./sqlite-vec.js", () => ({ |
| loadSqliteVecExtension: async () => ({ ok: false, error: "sqlite-vec disabled in tests" }), |
| })); |
|
|
| vi.mock("./embeddings.js", () => ({ |
| createEmbeddingProvider: async () => ({ |
| requestedProvider: "openai", |
| provider: { |
| id: "mock", |
| model: "mock-embed", |
| embedQuery: async () => [1, 0], |
| embedBatch: async (texts: string[]) => texts.map(() => [1, 0]), |
| }, |
| }), |
| })); |
|
|
| describe("memory watcher config", () => { |
| let manager: MemoryIndexManager | null = null; |
| let workspaceDir = ""; |
| let extraDir = ""; |
|
|
| afterEach(async () => { |
| watchMock.mockClear(); |
| if (manager) { |
| await manager.close(); |
| manager = null; |
| } |
| if (workspaceDir) { |
| await fs.rm(workspaceDir, { recursive: true, force: true }); |
| workspaceDir = ""; |
| extraDir = ""; |
| } |
| }); |
|
|
| it("watches markdown globs and ignores dependency directories", async () => { |
| workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-memory-watch-")); |
| extraDir = path.join(workspaceDir, "extra"); |
| await fs.mkdir(path.join(workspaceDir, "memory"), { recursive: true }); |
| await fs.mkdir(extraDir, { recursive: true }); |
| await fs.writeFile(path.join(extraDir, "notes.md"), "hello"); |
|
|
| const cfg = { |
| agents: { |
| defaults: { |
| workspace: workspaceDir, |
| memorySearch: { |
| provider: "openai", |
| model: "mock-embed", |
| store: { path: path.join(workspaceDir, "index.sqlite"), vector: { enabled: false } }, |
| sync: { watch: true, watchDebounceMs: 25, onSessionStart: false, onSearch: false }, |
| query: { minScore: 0, hybrid: { enabled: false } }, |
| extraPaths: [extraDir], |
| }, |
| }, |
| list: [{ id: "main", default: true }], |
| }, |
| }; |
|
|
| const result = await getMemorySearchManager({ cfg, agentId: "main" }); |
| expect(result.manager).not.toBeNull(); |
| if (!result.manager) { |
| throw new Error("manager missing"); |
| } |
| manager = result.manager; |
|
|
| expect(watchMock).toHaveBeenCalledTimes(1); |
| const [watchedPaths, options] = watchMock.mock.calls[0] as [string[], Record<string, unknown>]; |
| expect(watchedPaths).toEqual( |
| expect.arrayContaining([ |
| path.join(workspaceDir, "MEMORY.md"), |
| path.join(workspaceDir, "memory.md"), |
| path.join(workspaceDir, "memory", "**", "*.md"), |
| path.join(extraDir, "**", "*.md"), |
| ]), |
| ); |
| expect(options.ignoreInitial).toBe(true); |
| expect(options.awaitWriteFinish).toEqual({ stabilityThreshold: 25, pollInterval: 100 }); |
|
|
| const ignored = options.ignored as ((watchPath: string) => boolean) | undefined; |
| expect(ignored).toBeTypeOf("function"); |
| expect(ignored?.(path.join(workspaceDir, "memory", "node_modules", "pkg", "index.md"))).toBe( |
| true, |
| ); |
| expect(ignored?.(path.join(workspaceDir, "memory", ".venv", "lib", "python.md"))).toBe(true); |
| expect(ignored?.(path.join(workspaceDir, "memory", "project", "notes.md"))).toBe(false); |
| }); |
| }); |
|
|