feat(FN-3207): remove runtime memory-backend side-load
Merged FN-3207 and FN-3242: streamlined the memory backend by removing the runtime side-load pattern in `project-memory.ts` (simplified from 25+ lines), added regression tests across core, engine, and CLI bundle to catch the import issue at build time, and updated ChatView with a CSS fix for file me Fusion-Task-Id: FN-3207
This commit is contained in:
5
.changeset/fn-3207-fix-memory-backend-runtime-import.md
Normal file
5
.changeset/fn-3207-fix-memory-backend-runtime-import.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Fix project memory tools failing in fresh worktrees and bundled runtime contexts when an internal memory backend artifact is missing. `fn_memory_search` and `fn_memory_get` now resolve the backend through bundled runtime code instead of a fragile side-load import path.
|
||||
@@ -42,6 +42,12 @@ describe("CLI bundle output", () => {
|
||||
expect(content).not.toContain('"@fusion/engine"');
|
||||
});
|
||||
|
||||
it("does not contain runtime memory-backend side-load imports", () => {
|
||||
const content = readFileSync(bundlePath, "utf-8");
|
||||
expect(content).not.toMatch(/await\s+import\(\s*["']\.\/memory-backend\.js["']\s*\)/);
|
||||
expect(content).not.toMatch(/await\s+import\(\s*["']\.\.\/memory-backend\.js["']\s*\)/);
|
||||
});
|
||||
|
||||
it("contains inlined workspace code", () => {
|
||||
const content = readFileSync(bundlePath, "utf-8");
|
||||
// TaskStore from @fusion/core
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
readProjectMemory,
|
||||
readProjectMemoryWithBackend,
|
||||
searchProjectMemory,
|
||||
getProjectMemory,
|
||||
resolveMemoryInstructionContext,
|
||||
} from "../project-memory.js";
|
||||
|
||||
@@ -649,4 +650,45 @@ describe("project-memory", () => {
|
||||
expect(results.some((result) => result.path === ".fusion/memory/DREAMS.md")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getProjectMemory", () => {
|
||||
it("reads bounded memory window via file backend", async () => {
|
||||
const memoryDir = join(testDir, ".fusion", "memory");
|
||||
await mkdir(memoryDir, { recursive: true });
|
||||
await writeFile(
|
||||
join(memoryDir, "MEMORY.md"),
|
||||
"# Memory\nline-a\nline-b\nline-c\nline-d\n",
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const result = await getProjectMemory(
|
||||
testDir,
|
||||
{ path: ".fusion/memory/MEMORY.md", startLine: 2, lineCount: 2 },
|
||||
{ memoryBackendType: "file" },
|
||||
);
|
||||
|
||||
expect(result.path).toBe(".fusion/memory/MEMORY.md");
|
||||
expect(result.content).toBe("line-a\nline-b");
|
||||
expect(result.startLine).toBe(2);
|
||||
expect(result.endLine).toBe(3);
|
||||
expect(result.totalLines).toBeGreaterThanOrEqual(5);
|
||||
expect(result.backend).toBe("file");
|
||||
});
|
||||
|
||||
it("returns qmd backend marker for memory_get contract", async () => {
|
||||
const memoryDir = join(testDir, ".fusion", "memory");
|
||||
await mkdir(memoryDir, { recursive: true });
|
||||
await writeFile(join(memoryDir, "MEMORY.md"), "# Memory\nqmd-line\n", "utf-8");
|
||||
|
||||
const result = await getProjectMemory(
|
||||
testDir,
|
||||
{ path: ".fusion/memory/MEMORY.md", startLine: 1, lineCount: 5 },
|
||||
{ memoryBackendType: "qmd" },
|
||||
);
|
||||
|
||||
expect(result.path).toBe(".fusion/memory/MEMORY.md");
|
||||
expect(result.content).toContain("qmd-line");
|
||||
expect(result.backend).toBe("qmd");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,10 @@ import { existsSync } from "node:fs";
|
||||
import {
|
||||
ensureOpenClawMemoryFiles,
|
||||
memoryLongTermPath,
|
||||
resolveMemoryBackend,
|
||||
MEMORY_BACKEND_SETTINGS_KEYS,
|
||||
DEFAULT_MEMORY_BACKEND,
|
||||
scheduleQmdInstallAndRefresh,
|
||||
type MemorySearchOptions,
|
||||
type MemorySearchResult,
|
||||
type MemoryGetOptions,
|
||||
@@ -93,17 +97,6 @@ type MemorySettings = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
// Import memory backend utilities lazily to avoid circular dependencies
|
||||
async function getMemoryBackendUtils() {
|
||||
const module = await import("./memory-backend.js");
|
||||
return {
|
||||
resolveMemoryBackend: module.resolveMemoryBackend,
|
||||
getMemoryBackendCapabilities: module.getMemoryBackendCapabilities,
|
||||
MEMORY_BACKEND_SETTINGS_KEYS: module.MEMORY_BACKEND_SETTINGS_KEYS,
|
||||
DEFAULT_MEMORY_BACKEND: module.DEFAULT_MEMORY_BACKEND,
|
||||
scheduleQmdInstallAndRefresh: module.scheduleQmdInstallAndRefresh,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Memory Instruction Context ─────────────────────────────────────────
|
||||
|
||||
@@ -236,13 +229,6 @@ export async function ensureMemoryFileWithBackend(
|
||||
rootDir: string,
|
||||
settings?: MemorySettings,
|
||||
): Promise<boolean> {
|
||||
const {
|
||||
resolveMemoryBackend,
|
||||
MEMORY_BACKEND_SETTINGS_KEYS,
|
||||
DEFAULT_MEMORY_BACKEND,
|
||||
scheduleQmdInstallAndRefresh,
|
||||
} = await getMemoryBackendUtils();
|
||||
|
||||
const backendType =
|
||||
(settings?.[MEMORY_BACKEND_SETTINGS_KEYS.MEMORY_BACKEND_TYPE] as string) ||
|
||||
DEFAULT_MEMORY_BACKEND;
|
||||
@@ -296,7 +282,6 @@ export async function readProjectMemoryWithBackend(
|
||||
rootDir: string,
|
||||
settings?: MemorySettings,
|
||||
): Promise<string> {
|
||||
const { resolveMemoryBackend } = await getMemoryBackendUtils();
|
||||
const backend = resolveMemoryBackend(settings);
|
||||
|
||||
try {
|
||||
@@ -313,7 +298,6 @@ export async function searchProjectMemory(
|
||||
options: MemorySearchOptions,
|
||||
settings?: MemorySettings,
|
||||
): Promise<MemorySearchResult[]> {
|
||||
const { resolveMemoryBackend } = await getMemoryBackendUtils();
|
||||
const backend = resolveMemoryBackend(settings);
|
||||
if (!backend.search) {
|
||||
return [];
|
||||
@@ -326,7 +310,6 @@ export async function getProjectMemory(
|
||||
options: MemoryGetOptions,
|
||||
settings?: MemorySettings,
|
||||
): Promise<MemoryGetResult> {
|
||||
const { resolveMemoryBackend } = await getMemoryBackendUtils();
|
||||
const backend = resolveMemoryBackend(settings);
|
||||
if (!backend.get) {
|
||||
throw new Error(`Memory backend '${backend.type}' does not support memory_get`);
|
||||
|
||||
@@ -271,6 +271,35 @@ describe("createMemoryTools", () => {
|
||||
expect(getResult.content[0]!.text).toContain("roadmap sequencing");
|
||||
});
|
||||
|
||||
it("uses project memory backend for fn_memory_search/fn_memory_get when agent memory is absent", async () => {
|
||||
await core.ensureOpenClawMemoryFiles(tempDir);
|
||||
await appendFile(
|
||||
join(tempDir, ".fusion", "memory", "MEMORY.md"),
|
||||
"\n- Runtime import regressions should be caught in bundle tests.\n",
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const [searchTool, getTool] = createMemoryTools(tempDir, { memoryBackendType: "file" });
|
||||
|
||||
const searchResult = await (searchTool as any).execute("call-project-search", {
|
||||
query: "runtime import regressions",
|
||||
limit: 5,
|
||||
}, undefined, undefined, undefined);
|
||||
|
||||
expect(searchResult.details.results.length).toBeGreaterThan(0);
|
||||
expect(searchResult.details.results.some((hit: any) => hit.path === ".fusion/memory/MEMORY.md")).toBe(true);
|
||||
|
||||
const getResult = await (getTool as any).execute("call-project-get", {
|
||||
path: ".fusion/memory/MEMORY.md",
|
||||
startLine: 1,
|
||||
lineCount: 30,
|
||||
}, undefined, undefined, undefined);
|
||||
|
||||
expect(getResult.content[0]!.text).toContain(".fusion/memory/MEMORY.md");
|
||||
expect(getResult.content[0]!.text).toContain("Runtime import regressions");
|
||||
expect(getResult.details.backend).toBe("file");
|
||||
});
|
||||
|
||||
it("creates daily and dreams files for per-agent memory lookup", async () => {
|
||||
const [searchTool] = createMemoryTools(tempDir, { memoryBackendType: "file" }, {
|
||||
agentMemory: {
|
||||
|
||||
Reference in New Issue
Block a user