feat(memory-sdk): add memory event journal bridge

This commit is contained in:
Vincent Koc
2026-04-05 20:58:08 +01:00
parent fbbe2a1675
commit d1c7d9af80
14 changed files with 413 additions and 5 deletions

View File

@@ -0,0 +1,93 @@
import fs from "node:fs/promises";
import path from "node:path";
import type { MemoryDreamingPhaseName } from "./dreaming.js";
export const MEMORY_HOST_EVENT_LOG_RELATIVE_PATH = path.join("memory", ".dreams", "events.jsonl");
export type MemoryHostRecallRecordedEvent = {
type: "memory.recall.recorded";
timestamp: string;
query: string;
resultCount: number;
results: Array<{
path: string;
startLine: number;
endLine: number;
score: number;
}>;
};
export type MemoryHostPromotionAppliedEvent = {
type: "memory.promotion.applied";
timestamp: string;
memoryPath: string;
applied: number;
candidates: Array<{
key: string;
path: string;
startLine: number;
endLine: number;
score: number;
recallCount: number;
}>;
};
export type MemoryHostDreamCompletedEvent = {
type: "memory.dream.completed";
timestamp: string;
phase: MemoryDreamingPhaseName;
inlinePath?: string;
reportPath?: string;
lineCount: number;
storageMode: "inline" | "separate" | "both";
};
export type MemoryHostEvent =
| MemoryHostRecallRecordedEvent
| MemoryHostPromotionAppliedEvent
| MemoryHostDreamCompletedEvent;
export function resolveMemoryHostEventLogPath(workspaceDir: string): string {
return path.join(workspaceDir, MEMORY_HOST_EVENT_LOG_RELATIVE_PATH);
}
export async function appendMemoryHostEvent(
workspaceDir: string,
event: MemoryHostEvent,
): Promise<void> {
const eventLogPath = resolveMemoryHostEventLogPath(workspaceDir);
await fs.mkdir(path.dirname(eventLogPath), { recursive: true });
await fs.appendFile(eventLogPath, `${JSON.stringify(event)}\n`, "utf8");
}
export async function readMemoryHostEvents(params: {
workspaceDir: string;
limit?: number;
}): Promise<MemoryHostEvent[]> {
const eventLogPath = resolveMemoryHostEventLogPath(params.workspaceDir);
const raw = await fs.readFile(eventLogPath, "utf8").catch((err: unknown) => {
if ((err as NodeJS.ErrnoException)?.code === "ENOENT") {
return "";
}
throw err;
});
if (!raw.trim()) {
return [];
}
const events = raw
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.flatMap((line) => {
try {
return [JSON.parse(line) as MemoryHostEvent];
} catch {
return [];
}
});
if (!Number.isFinite(params.limit)) {
return events;
}
const limit = Math.max(0, Math.floor(params.limit as number));
return limit === 0 ? [] : events.slice(-limit);
}

View File

@@ -0,0 +1 @@
export * from "../memory-host-sdk/events.js";

View File

@@ -46,6 +46,12 @@ export {
withProgress,
withProgressTotals,
} from "./memory-core-host-runtime-cli.js";
export {
appendMemoryHostEvent,
readMemoryHostEvents,
resolveMemoryHostEventLogPath,
} from "./memory-core-host-events.js";
export type { MemoryHostEvent } from "./memory-core-host-events.js";
export {
resolveMemoryCorePluginConfig,
formatMemoryDreamingDay,

View File

@@ -0,0 +1,60 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
appendMemoryHostEvent,
readMemoryHostEvents,
resolveMemoryHostEventLogPath,
} from "./memory-host-events.js";
const tempDirs: string[] = [];
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
});
describe("memory host event journal helpers", () => {
it("appends and reads typed workspace events", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-host-events-"));
tempDirs.push(workspaceDir);
await appendMemoryHostEvent(workspaceDir, {
type: "memory.recall.recorded",
timestamp: "2026-04-05T12:00:00.000Z",
query: "glacier backup",
resultCount: 1,
results: [
{
path: "memory/2026-04-05.md",
startLine: 1,
endLine: 3,
score: 0.9,
},
],
});
await appendMemoryHostEvent(workspaceDir, {
type: "memory.dream.completed",
timestamp: "2026-04-05T13:00:00.000Z",
phase: "light",
lineCount: 4,
storageMode: "both",
inlinePath: path.join(workspaceDir, "memory", "2026-04-05.md"),
reportPath: path.join(workspaceDir, "memory", "dreaming", "light", "2026-04-05.md"),
});
const eventLogPath = resolveMemoryHostEventLogPath(workspaceDir);
await expect(fs.readFile(eventLogPath, "utf8")).resolves.toContain(
'"type":"memory.recall.recorded"',
);
const events = await readMemoryHostEvents({ workspaceDir });
const tail = await readMemoryHostEvents({ workspaceDir, limit: 1 });
expect(events).toHaveLength(2);
expect(events[0]?.type).toBe("memory.recall.recorded");
expect(events[1]?.type).toBe("memory.dream.completed");
expect(tail).toHaveLength(1);
expect(tail[0]?.type).toBe("memory.dream.completed");
});
});

View File

@@ -0,0 +1 @@
export * from "../memory-host-sdk/events.js";