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"');
|
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", () => {
|
it("contains inlined workspace code", () => {
|
||||||
const content = readFileSync(bundlePath, "utf-8");
|
const content = readFileSync(bundlePath, "utf-8");
|
||||||
// TaskStore from @fusion/core
|
// TaskStore from @fusion/core
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
readProjectMemory,
|
readProjectMemory,
|
||||||
readProjectMemoryWithBackend,
|
readProjectMemoryWithBackend,
|
||||||
searchProjectMemory,
|
searchProjectMemory,
|
||||||
|
getProjectMemory,
|
||||||
resolveMemoryInstructionContext,
|
resolveMemoryInstructionContext,
|
||||||
} from "../project-memory.js";
|
} from "../project-memory.js";
|
||||||
|
|
||||||
@@ -649,4 +650,45 @@ describe("project-memory", () => {
|
|||||||
expect(results.some((result) => result.path === ".fusion/memory/DREAMS.md")).toBe(true);
|
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 {
|
import {
|
||||||
ensureOpenClawMemoryFiles,
|
ensureOpenClawMemoryFiles,
|
||||||
memoryLongTermPath,
|
memoryLongTermPath,
|
||||||
|
resolveMemoryBackend,
|
||||||
|
MEMORY_BACKEND_SETTINGS_KEYS,
|
||||||
|
DEFAULT_MEMORY_BACKEND,
|
||||||
|
scheduleQmdInstallAndRefresh,
|
||||||
type MemorySearchOptions,
|
type MemorySearchOptions,
|
||||||
type MemorySearchResult,
|
type MemorySearchResult,
|
||||||
type MemoryGetOptions,
|
type MemoryGetOptions,
|
||||||
@@ -93,17 +97,6 @@ type MemorySettings = {
|
|||||||
[key: string]: unknown;
|
[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 ─────────────────────────────────────────
|
// ── Memory Instruction Context ─────────────────────────────────────────
|
||||||
|
|
||||||
@@ -236,13 +229,6 @@ export async function ensureMemoryFileWithBackend(
|
|||||||
rootDir: string,
|
rootDir: string,
|
||||||
settings?: MemorySettings,
|
settings?: MemorySettings,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const {
|
|
||||||
resolveMemoryBackend,
|
|
||||||
MEMORY_BACKEND_SETTINGS_KEYS,
|
|
||||||
DEFAULT_MEMORY_BACKEND,
|
|
||||||
scheduleQmdInstallAndRefresh,
|
|
||||||
} = await getMemoryBackendUtils();
|
|
||||||
|
|
||||||
const backendType =
|
const backendType =
|
||||||
(settings?.[MEMORY_BACKEND_SETTINGS_KEYS.MEMORY_BACKEND_TYPE] as string) ||
|
(settings?.[MEMORY_BACKEND_SETTINGS_KEYS.MEMORY_BACKEND_TYPE] as string) ||
|
||||||
DEFAULT_MEMORY_BACKEND;
|
DEFAULT_MEMORY_BACKEND;
|
||||||
@@ -296,7 +282,6 @@ export async function readProjectMemoryWithBackend(
|
|||||||
rootDir: string,
|
rootDir: string,
|
||||||
settings?: MemorySettings,
|
settings?: MemorySettings,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { resolveMemoryBackend } = await getMemoryBackendUtils();
|
|
||||||
const backend = resolveMemoryBackend(settings);
|
const backend = resolveMemoryBackend(settings);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -313,7 +298,6 @@ export async function searchProjectMemory(
|
|||||||
options: MemorySearchOptions,
|
options: MemorySearchOptions,
|
||||||
settings?: MemorySettings,
|
settings?: MemorySettings,
|
||||||
): Promise<MemorySearchResult[]> {
|
): Promise<MemorySearchResult[]> {
|
||||||
const { resolveMemoryBackend } = await getMemoryBackendUtils();
|
|
||||||
const backend = resolveMemoryBackend(settings);
|
const backend = resolveMemoryBackend(settings);
|
||||||
if (!backend.search) {
|
if (!backend.search) {
|
||||||
return [];
|
return [];
|
||||||
@@ -326,7 +310,6 @@ export async function getProjectMemory(
|
|||||||
options: MemoryGetOptions,
|
options: MemoryGetOptions,
|
||||||
settings?: MemorySettings,
|
settings?: MemorySettings,
|
||||||
): Promise<MemoryGetResult> {
|
): Promise<MemoryGetResult> {
|
||||||
const { resolveMemoryBackend } = await getMemoryBackendUtils();
|
|
||||||
const backend = resolveMemoryBackend(settings);
|
const backend = resolveMemoryBackend(settings);
|
||||||
if (!backend.get) {
|
if (!backend.get) {
|
||||||
throw new Error(`Memory backend '${backend.type}' does not support memory_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");
|
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 () => {
|
it("creates daily and dreams files for per-agent memory lookup", async () => {
|
||||||
const [searchTool] = createMemoryTools(tempDir, { memoryBackendType: "file" }, {
|
const [searchTool] = createMemoryTools(tempDir, { memoryBackendType: "file" }, {
|
||||||
agentMemory: {
|
agentMemory: {
|
||||||
|
|||||||
Reference in New Issue
Block a user