Files
fusion/packages/engine/src/agent-memory-index.ts
Fusion 86381e4ad5 feat(FN-4393): complete Step 2 — add memory index builder
Fusion-Task-Id: FN-4393
Fusion-Task-Lineage: 3615215d-9caa-4402-9258-a5a5de137dd6
2026-05-13 23:41:35 -07:00

84 lines
2.7 KiB
TypeScript

import { readFile } from "node:fs/promises";
import { join } from "node:path";
type BuildMemoryIndexInput = {
rootDir: string;
agentId: string;
};
type HeadingEntry = {
heading: string;
summary?: string;
};
const MAX_INDEX_BYTES = 800;
function clampUtf8(input: string, maxBytes: number): string {
if (Buffer.byteLength(input, "utf8") <= maxBytes) return input;
let out = "";
for (const char of input) {
if (Buffer.byteLength(out + char, "utf8") > maxBytes) break;
out += char;
}
return out;
}
function parseHeadings(content: string): HeadingEntry[] {
const lines = content.split(/\r?\n/);
const entries: HeadingEntry[] = [];
for (let i = 0; i < lines.length; i += 1) {
const line = lines[i]?.trim() ?? "";
if (!line.startsWith("## ")) continue;
const heading = line.slice(3).trim();
if (!heading) continue;
let summary: string | undefined;
for (let j = i + 1; j < lines.length; j += 1) {
const candidate = (lines[j] ?? "").trim();
if (!candidate) continue;
if (candidate.startsWith("## ")) break;
summary = candidate;
break;
}
entries.push({ heading, summary });
}
return entries;
}
async function buildFileSection(sectionHeader: string, displayPath: string, fsPath: string): Promise<string> {
try {
const content = await readFile(fsPath, "utf-8");
const headings = parseHeadings(content);
const lines = [sectionHeader, `- ${displayPath}`];
for (const entry of headings) {
const descriptor = entry.summary ? ` — ${entry.summary}` : "";
lines.push(` - "${entry.heading}"${descriptor}`);
}
return `${lines.join("\n")}\n`;
} catch {
return "";
}
}
export async function buildMemoryIndex({ rootDir, agentId }: BuildMemoryIndexInput): Promise<string> {
const safeAgentId = agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
const agentDisplayPath = `.fusion/agent-memory/${safeAgentId}/MEMORY.md`;
const agentFsPath = join(rootDir, ".fusion", "agent-memory", safeAgentId, "MEMORY.md");
const projectDisplayPath = ".fusion/memory/MEMORY.md";
const projectFsPath = join(rootDir, ".fusion", "memory", "MEMORY.md");
const [agentSection, projectSection] = await Promise.all([
buildFileSection("## Agent Memory Index (use fn_memory_search / fn_memory_get to read)", agentDisplayPath, agentFsPath),
buildFileSection("## Project Memory Index", projectDisplayPath, projectFsPath),
]);
const assembled = [agentSection, projectSection].filter(Boolean).join("\n").trim();
if (!assembled) return "";
if (Buffer.byteLength(assembled, "utf8") <= MAX_INDEX_BYTES) return assembled;
return `${clampUtf8(assembled, MAX_INDEX_BYTES - 1)}…`;
}