diff --git a/platform/core/src/index.ts b/platform/core/src/index.ts index e95270de4..cc88ebf87 100644 --- a/platform/core/src/index.ts +++ b/platform/core/src/index.ts @@ -114,7 +114,7 @@ export { export type { ModelCatalogProvider, PipelineModelPreset } from "./llm-model-catalog"; export { parseManifest, generateManifest } from "./manifest"; export { parseSoul, generateSoul } from "./soul"; -export { parseMemory, generateMemory } from "./memory"; +export { parseMemory, generateMemory, type ParsedMemory } from "./memory"; export { NETWORK_MODES, normalizeNetworkMode, diff --git a/platform/core/src/memory.ts b/platform/core/src/memory.ts index af730bd8e..245785596 100644 --- a/platform/core/src/memory.ts +++ b/platform/core/src/memory.ts @@ -1,6 +1,61 @@ -export function parseMemory(markdown: string): Record { - // TODO: Parse memory markdown into structured data - return { raw: markdown }; +export interface ParsedMemory { + /** Content under the "## User Profile" section. */ + userProfile: string; + /** Content under the "## Key Facts" section. */ + keyFacts: string; + /** Content under the "## Ongoing Context" section. */ + ongoingContext: string; + /** Content between MANUAL:START and MANUAL:END markers. */ + manualNotes: string; + /** The full raw markdown input, preserved for round-tripping. */ + raw: string; +} + +/** + * Parse a Signet memory markdown file into structured sections. + * + * Extracts content from well-known `## ` headings and the + * `` / `` block. Any content + * outside recognized sections is ignored — the `raw` field always + * contains the original markdown for lossless round-tripping. + */ +export function parseMemory(markdown: string): ParsedMemory { + const sections: Record = {}; + let currentSection: string | null = null; + const sectionLines: string[] = []; + + const lines = markdown.split("\n"); + for (const line of lines) { + const headingMatch = line.match(/^##\s+(.+)/); + if (headingMatch) { + // Flush previous section + if (currentSection !== null) { + sections[currentSection] = sectionLines.join("\n").trim(); + } + currentSection = headingMatch[1].trim(); + sectionLines.length = 0; + } else if (currentSection !== null) { + sectionLines.push(line); + } + } + // Flush last section + if (currentSection !== null) { + sections[currentSection] = sectionLines.join("\n").trim(); + } + + // Extract manual notes block + const manualMatch = markdown.match( + /([\s\S]*?)/, + ); + const manualNotes = manualMatch ? manualMatch[1].trim() : ""; + + return { + userProfile: sections["User Profile"] ?? "", + keyFacts: sections["Key Facts"] ?? "", + ongoingContext: sections["Ongoing Context"] ?? "", + manualNotes, + raw: markdown, + }; } export function generateMemory(): string {