fix: preserve multimodal memory sync inputs

This commit is contained in:
Junebugg1214 2026-03-13 12:39:58 -04:00
parent 598c7c32f7
commit 37197b883d
2 changed files with 59 additions and 5 deletions

View File

@ -315,6 +315,26 @@ describe("memory index", () => {
expect(audioResults.some((result) => result.path.endsWith("meeting.wav"))).toBe(true);
});
it("indexes a multimodal extra path provided as a direct file path", async () => {
const mediaDir = path.join(workspaceDir, "media-single-file");
const imagePath = path.join(mediaDir, "diagram.png");
await fs.mkdir(mediaDir, { recursive: true });
await fs.writeFile(imagePath, Buffer.from("png"));
const cfg = createCfg({
storePath: path.join(workspaceDir, `index-multimodal-file-${randomUUID()}.sqlite`),
provider: "gemini",
model: "gemini-embedding-2-preview",
extraPaths: [imagePath],
multimodal: { enabled: true, modalities: ["image"] },
});
const manager = await getPersistentManager(cfg);
await manager.sync({ reason: "test" });
const imageResults = await manager.search("image");
expect(imageResults.some((result) => result.path.endsWith("diagram.png"))).toBe(true);
});
it("skips oversized multimodal inputs without aborting sync", async () => {
const mediaDir = path.join(workspaceDir, "media-oversize");
await fs.mkdir(mediaDir, { recursive: true });

View File

@ -3,7 +3,7 @@ import fsSync from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import type { DatabaseSync } from "node:sqlite";
import chokidar, { FSWatcher } from "chokidar";
import chokidar from "chokidar";
import { resolveAgentDir } from "../agents/agent-scope.js";
import { ResolvedMemorySearchConfig } from "../agents/memory-search.js";
import { type OpenClawConfig } from "../config/config.js";
@ -35,6 +35,11 @@ import {
} from "./internal.js";
import { type MemoryFileEntry } from "./internal.js";
import { ensureMemoryIndexSchema } from "./memory-schema.js";
import {
buildCaseInsensitiveExtensionGlob,
getMemoryMultimodalExtensions,
isMemoryMultimodalEnabled,
} from "./multimodal.js";
import type { SessionFileEntry } from "./session-files.js";
import {
buildSessionEntry,
@ -62,6 +67,11 @@ type MemorySyncProgressState = {
report: (update: MemorySyncProgressUpdate) => void;
};
type MemoryWatcher = {
on(event: "add" | "change" | "unlink", listener: () => void): unknown;
close(): Promise<void>;
};
const META_KEY = "memory_index_meta_v1";
const VECTOR_TABLE = "chunks_vec";
const FTS_TABLE = "chunks_fts";
@ -121,7 +131,7 @@ export abstract class MemoryManagerSyncOps {
loadError?: string;
} = { enabled: false, available: false };
protected vectorReady: Promise<boolean> | null = null;
protected watcher: FSWatcher | null = null;
protected watcher: MemoryWatcher | null = null;
protected watchTimer: NodeJS.Timeout | null = null;
protected sessionWatchTimer: NodeJS.Timeout | null = null;
protected sessionUnsubscribe: (() => void) | null = null;
@ -384,9 +394,27 @@ export abstract class MemoryManagerSyncOps {
}
if (stat.isDirectory()) {
watchPaths.add(path.join(entry, "**", "*.md"));
if (isMemoryMultimodalEnabled(this.settings.multimodal)) {
for (const modality of this.settings.multimodal.modalities) {
for (const extension of getMemoryMultimodalExtensions(modality)) {
watchPaths.add(
path.join(entry, "**", buildCaseInsensitiveExtensionGlob(extension)),
);
}
}
}
continue;
}
if (stat.isFile() && entry.toLowerCase().endsWith(".md")) {
if (
stat.isFile() &&
(entry.toLowerCase().endsWith(".md") ||
(isMemoryMultimodalEnabled(this.settings.multimodal) &&
this.settings.multimodal.modalities.some((modality) =>
getMemoryMultimodalExtensions(modality).some((extension) =>
entry.toLowerCase().endsWith(extension),
),
)))
) {
watchPaths.add(entry);
}
} catch {
@ -650,9 +678,15 @@ export abstract class MemoryManagerSyncOps {
return;
}
const files = await listMemoryFiles(this.workspaceDir, this.settings.extraPaths);
const files = await listMemoryFiles(
this.workspaceDir,
this.settings.extraPaths,
this.settings.multimodal,
);
const fileEntries = (
await Promise.all(files.map(async (file) => buildFileEntry(file, this.workspaceDir)))
await Promise.all(
files.map(async (file) => buildFileEntry(file, this.workspaceDir, this.settings.multimodal)),
)
).filter((entry): entry is MemoryFileEntry => entry !== null);
log.debug("memory sync: indexing memory files", {
files: fileEntries.length,