Skip to content

Commit 5716d83

Browse files
committed
feat(memory-wiki): restore llm wiki stack
1 parent 9fc2a9f commit 5716d83

84 files changed

Lines changed: 8740 additions & 144 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/labeler.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@
233233
- changed-files:
234234
- any-glob-to-any-file:
235235
- "extensions/memory-lancedb/**"
236+
"extensions: memory-wiki":
237+
- changed-files:
238+
- any-glob-to-any-file:
239+
- "extensions/memory-wiki/**"
236240
"extensions: open-prose":
237241
- changed-files:
238242
- any-glob-to-any-file:
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
f22ced69dd64597a651d826a3b40d96a9a0d3613fb4a9b8a6960ab6b4d546bf9 plugin-sdk-api-baseline.json
2-
47b675735c395daedf360a30bc7cf85b9e598e05a9e89378859cf21674334c52 plugin-sdk-api-baseline.jsonl
1+
7770bd71ffc20bd65c90f0eb1aa33f46784809f87f014004f8e7a0a5acd2506b plugin-sdk-api-baseline.json
2+
ebe0d3f30710a7a977530d7d15b390b0b30bbaecb1a586dd56292dea667cb06e plugin-sdk-api-baseline.jsonl

docs/plugins/sdk-migration.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,17 @@ Current bundled provider examples:
290290
| `plugin-sdk/memory-core-host-multimodal` | Memory host multimodal helpers | Memory host multimodal helpers |
291291
| `plugin-sdk/memory-core-host-query` | Memory host query helpers | Memory host query helpers |
292292
| `plugin-sdk/memory-core-host-secret` | Memory host secret helpers | Memory host secret helpers |
293+
| `plugin-sdk/memory-core-host-events` | Memory host event journal helpers | Memory host event journal helpers |
293294
| `plugin-sdk/memory-core-host-status` | Memory host status helpers | Memory host status helpers |
294295
| `plugin-sdk/memory-core-host-runtime-cli` | Memory host CLI runtime | Memory host CLI runtime helpers |
295296
| `plugin-sdk/memory-core-host-runtime-core` | Memory host core runtime | Memory host core runtime helpers |
296297
| `plugin-sdk/memory-core-host-runtime-files` | Memory host file/runtime helpers | Memory host file/runtime helpers |
298+
| `plugin-sdk/memory-host-core` | Memory host core runtime alias | Vendor-neutral alias for memory host core runtime helpers |
299+
| `plugin-sdk/memory-host-events` | Memory host event journal alias | Vendor-neutral alias for memory host event journal helpers |
300+
| `plugin-sdk/memory-host-files` | Memory host file/runtime alias | Vendor-neutral alias for memory host file/runtime helpers |
301+
| `plugin-sdk/memory-host-markdown` | Managed markdown helpers | Shared managed-markdown helpers for memory-adjacent plugins |
302+
| `plugin-sdk/memory-host-search` | Active memory search facade | Lazy active-memory search-manager runtime facade |
303+
| `plugin-sdk/memory-host-status` | Memory host status alias | Vendor-neutral alias for memory host status helpers |
297304
| `plugin-sdk/memory-lancedb` | Bundled memory-lancedb helpers | Memory-lancedb helper surface |
298305
| `plugin-sdk/testing` | Test utilities | Test helpers and mocks |
299306
</Accordion>

docs/plugins/sdk-overview.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,17 @@ explicitly promotes one as public.
256256
| `plugin-sdk/memory-core-host-multimodal` | Memory host multimodal helpers |
257257
| `plugin-sdk/memory-core-host-query` | Memory host query helpers |
258258
| `plugin-sdk/memory-core-host-secret` | Memory host secret helpers |
259+
| `plugin-sdk/memory-core-host-events` | Memory host event journal helpers |
259260
| `plugin-sdk/memory-core-host-status` | Memory host status helpers |
260261
| `plugin-sdk/memory-core-host-runtime-cli` | Memory host CLI runtime helpers |
261262
| `plugin-sdk/memory-core-host-runtime-core` | Memory host core runtime helpers |
262263
| `plugin-sdk/memory-core-host-runtime-files` | Memory host file/runtime helpers |
264+
| `plugin-sdk/memory-host-core` | Vendor-neutral alias for memory host core runtime helpers |
265+
| `plugin-sdk/memory-host-events` | Vendor-neutral alias for memory host event journal helpers |
266+
| `plugin-sdk/memory-host-files` | Vendor-neutral alias for memory host file/runtime helpers |
267+
| `plugin-sdk/memory-host-markdown` | Shared managed-markdown helpers for memory-adjacent plugins |
268+
| `plugin-sdk/memory-host-search` | Active memory runtime facade for search-manager access |
269+
| `plugin-sdk/memory-host-status` | Vendor-neutral alias for memory host status helpers |
263270
| `plugin-sdk/memory-lancedb` | Bundled memory-lancedb helper surface |
264271
</Accordion>
265272

@@ -305,14 +312,16 @@ methods:
305312

306313
### Infrastructure
307314

308-
| Method | What it registers |
309-
| ---------------------------------------------- | --------------------- |
310-
| `api.registerHook(events, handler, opts?)` | Event hook |
311-
| `api.registerHttpRoute(params)` | Gateway HTTP endpoint |
312-
| `api.registerGatewayMethod(name, handler)` | Gateway RPC method |
313-
| `api.registerCli(registrar, opts?)` | CLI subcommand |
314-
| `api.registerService(service)` | Background service |
315-
| `api.registerInteractiveHandler(registration)` | Interactive handler |
315+
| Method | What it registers |
316+
| ---------------------------------------------- | --------------------------------------- |
317+
| `api.registerHook(events, handler, opts?)` | Event hook |
318+
| `api.registerHttpRoute(params)` | Gateway HTTP endpoint |
319+
| `api.registerGatewayMethod(name, handler)` | Gateway RPC method |
320+
| `api.registerCli(registrar, opts?)` | CLI subcommand |
321+
| `api.registerService(service)` | Background service |
322+
| `api.registerInteractiveHandler(registration)` | Interactive handler |
323+
| `api.registerMemoryPromptSupplement(builder)` | Additive memory-adjacent prompt section |
324+
| `api.registerMemoryCorpusSupplement(adapter)` | Additive memory search/read corpus |
316325

317326
Reserved core admin namespaces (`config.*`, `exec.approvals.*`, `wizard.*`,
318327
`update.*`) always stay `operator.admin`, even if a plugin tries to assign a

extensions/memory-core/src/dreaming-markdown.ts

Lines changed: 30 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,22 @@ import {
55
type MemoryDreamingPhaseName,
66
type MemoryDreamingStorageConfig,
77
} from "openclaw/plugin-sdk/memory-core-host-status";
8+
import { appendMemoryHostEvent } from "openclaw/plugin-sdk/memory-host-events";
9+
import {
10+
replaceManagedMarkdownBlock,
11+
withTrailingNewline,
12+
} from "openclaw/plugin-sdk/memory-host-markdown";
813

914
const DAILY_PHASE_HEADINGS: Record<Exclude<MemoryDreamingPhaseName, "deep">, string> = {
1015
light: "## Light Sleep",
1116
rem: "## REM Sleep",
1217
};
13-
const DEEP_PHASE_HEADING = "## Deep Sleep";
1418

1519
const DAILY_PHASE_LABELS: Record<Exclude<MemoryDreamingPhaseName, "deep">, string> = {
1620
light: "light",
1721
rem: "rem",
1822
};
1923

20-
const PRIMARY_DREAMS_FILENAME = "DREAMS.md";
21-
const DREAMS_FILENAME_ALIASES = [PRIMARY_DREAMS_FILENAME, "dreams.md"] as const;
22-
2324
function resolvePhaseMarkers(phase: Exclude<MemoryDreamingPhaseName, "deep">): {
2425
start: string;
2526
end: string;
@@ -31,72 +32,9 @@ function resolvePhaseMarkers(phase: Exclude<MemoryDreamingPhaseName, "deep">): {
3132
};
3233
}
3334

34-
function withTrailingNewline(content: string): string {
35-
return content.endsWith("\n") ? content : `${content}\n`;
36-
}
37-
38-
function replaceManagedBlock(params: {
39-
original: string;
40-
heading: string;
41-
startMarker: string;
42-
endMarker: string;
43-
body: string;
44-
}): string {
45-
const managedBlock = `${params.heading}\n${params.startMarker}\n${params.body}\n${params.endMarker}`;
46-
const existingPattern = new RegExp(
47-
`${escapeRegex(params.heading)}\\n${escapeRegex(params.startMarker)}[\\s\\S]*?${escapeRegex(params.endMarker)}`,
48-
"m",
49-
);
50-
if (existingPattern.test(params.original)) {
51-
return params.original.replace(existingPattern, managedBlock);
52-
}
53-
const trimmed = params.original.trimEnd();
54-
if (trimmed.length === 0) {
55-
return `${managedBlock}\n`;
56-
}
57-
return `${trimmed}\n\n${managedBlock}\n`;
58-
}
59-
60-
function escapeRegex(value: string): string {
61-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62-
}
63-
64-
async function resolveDreamsPath(workspaceDir: string): Promise<string> {
65-
for (const candidate of DREAMS_FILENAME_ALIASES) {
66-
const target = path.join(workspaceDir, candidate);
67-
try {
68-
await fs.access(target);
69-
return target;
70-
} catch (err) {
71-
if ((err as NodeJS.ErrnoException)?.code !== "ENOENT") {
72-
throw err;
73-
}
74-
}
75-
}
76-
return path.join(workspaceDir, PRIMARY_DREAMS_FILENAME);
77-
}
78-
79-
async function writeInlineDeepDreamingBlock(params: {
80-
workspaceDir: string;
81-
body: string;
82-
}): Promise<string> {
83-
const inlinePath = await resolveDreamsPath(params.workspaceDir);
84-
await fs.mkdir(path.dirname(inlinePath), { recursive: true });
85-
const original = await fs.readFile(inlinePath, "utf-8").catch((err: unknown) => {
86-
if ((err as NodeJS.ErrnoException)?.code === "ENOENT") {
87-
return "";
88-
}
89-
throw err;
90-
});
91-
const updated = replaceManagedBlock({
92-
original,
93-
heading: DEEP_PHASE_HEADING,
94-
startMarker: "<!-- openclaw:dreaming:deep:start -->",
95-
endMarker: "<!-- openclaw:dreaming:deep:end -->",
96-
body: params.body,
97-
});
98-
await fs.writeFile(inlinePath, withTrailingNewline(updated), "utf-8");
99-
return inlinePath;
35+
function resolveDailyMemoryPath(workspaceDir: string, epochMs: number, timezone?: string): string {
36+
const isoDay = formatMemoryDreamingDay(epochMs, timezone);
37+
return path.join(workspaceDir, "memory", `${isoDay}.md`);
10038
}
10139

10240
function resolveSeparateReportPath(
@@ -131,7 +69,7 @@ export async function writeDailyDreamingPhaseBlock(params: {
13169
let reportPath: string | undefined;
13270

13371
if (shouldWriteInline(params.storage)) {
134-
inlinePath = await resolveDreamsPath(params.workspaceDir);
72+
inlinePath = resolveDailyMemoryPath(params.workspaceDir, nowMs, params.timezone);
13573
await fs.mkdir(path.dirname(inlinePath), { recursive: true });
13674
const original = await fs.readFile(inlinePath, "utf-8").catch((err: unknown) => {
13775
if ((err as NodeJS.ErrnoException)?.code === "ENOENT") {
@@ -140,7 +78,7 @@ export async function writeDailyDreamingPhaseBlock(params: {
14078
throw err;
14179
});
14280
const markers = resolvePhaseMarkers(params.phase);
143-
const updated = replaceManagedBlock({
81+
const updated = replaceManagedMarkdownBlock({
14482
original,
14583
heading: DAILY_PHASE_HEADINGS[params.phase],
14684
startMarker: markers.start,
@@ -167,6 +105,16 @@ export async function writeDailyDreamingPhaseBlock(params: {
167105
await fs.writeFile(reportPath, report, "utf-8");
168106
}
169107

108+
await appendMemoryHostEvent(params.workspaceDir, {
109+
type: "memory.dream.completed",
110+
timestamp: new Date(nowMs).toISOString(),
111+
phase: params.phase,
112+
...(inlinePath ? { inlinePath } : {}),
113+
...(reportPath ? { reportPath } : {}),
114+
lineCount: params.bodyLines.length,
115+
storageMode: params.storage.mode,
116+
});
117+
170118
return {
171119
...(inlinePath ? { inlinePath } : {}),
172120
...(reportPath ? { reportPath } : {}),
@@ -180,19 +128,21 @@ export async function writeDeepDreamingReport(params: {
180128
timezone?: string;
181129
storage: MemoryDreamingStorageConfig;
182130
}): Promise<string | undefined> {
183-
const nowMs = Number.isFinite(params.nowMs) ? (params.nowMs as number) : Date.now();
184-
const body = params.bodyLines.length > 0 ? params.bodyLines.join("\n") : "- No durable changes.";
185-
await writeInlineDeepDreamingBlock({
186-
workspaceDir: params.workspaceDir,
187-
body,
188-
});
189-
190131
if (!shouldWriteSeparate(params.storage)) {
191132
return undefined;
192133
}
193-
134+
const nowMs = Number.isFinite(params.nowMs) ? (params.nowMs as number) : Date.now();
194135
const reportPath = resolveSeparateReportPath(params.workspaceDir, "deep", nowMs, params.timezone);
195136
await fs.mkdir(path.dirname(reportPath), { recursive: true });
137+
const body = params.bodyLines.length > 0 ? params.bodyLines.join("\n") : "- No durable changes.";
196138
await fs.writeFile(reportPath, `# Deep Sleep\n\n${body}\n`, "utf-8");
139+
await appendMemoryHostEvent(params.workspaceDir, {
140+
type: "memory.dream.completed",
141+
timestamp: new Date(nowMs).toISOString(),
142+
phase: "deep",
143+
reportPath,
144+
lineCount: params.bodyLines.length,
145+
storageMode: params.storage.mode,
146+
});
197147
return reportPath;
198148
}

extensions/memory-core/src/dreaming.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ describe("short-term dreaming cron reconciliation", () => {
544544
logger,
545545
});
546546

547-
expect(result.status).toBe("noop");
547+
expect(result.status).toBe("updated");
548548
expect(result.removed).toBe(2);
549549
expect(harness.removeCalls).toEqual(["job-light", "job-rem"]);
550550
});
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import fs from "node:fs/promises";
2+
import os from "node:os";
3+
import path from "node:path";
4+
import { readMemoryHostEvents } from "openclaw/plugin-sdk/memory-host-events";
5+
import { afterEach, describe, expect, it } from "vitest";
6+
import { writeDailyDreamingPhaseBlock } from "./dreaming-markdown.js";
7+
import {
8+
applyShortTermPromotions,
9+
rankShortTermPromotionCandidates,
10+
recordShortTermRecalls,
11+
} from "./short-term-promotion.js";
12+
13+
const tempDirs: string[] = [];
14+
15+
afterEach(async () => {
16+
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
17+
});
18+
19+
describe("memory host event journal integration", () => {
20+
it("records recall and promotion events from short-term promotion flows", async () => {
21+
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-core-events-"));
22+
tempDirs.push(workspaceDir);
23+
await fs.mkdir(path.join(workspaceDir, "memory"), { recursive: true });
24+
await fs.writeFile(
25+
path.join(workspaceDir, "memory", "2026-04-05.md"),
26+
"# Daily\n\nalpha\nbeta\ngamma\n",
27+
"utf8",
28+
);
29+
30+
await recordShortTermRecalls({
31+
workspaceDir,
32+
query: "alpha memory",
33+
results: [
34+
{
35+
path: "memory/2026-04-05.md",
36+
startLine: 3,
37+
endLine: 4,
38+
score: 0.92,
39+
snippet: "alpha beta",
40+
source: "memory",
41+
},
42+
],
43+
nowMs: Date.UTC(2026, 3, 5, 12, 0, 0),
44+
});
45+
46+
const candidates = await rankShortTermPromotionCandidates({
47+
workspaceDir,
48+
minScore: 0,
49+
minRecallCount: 0,
50+
minUniqueQueries: 0,
51+
nowMs: Date.UTC(2026, 3, 5, 12, 5, 0),
52+
});
53+
const applied = await applyShortTermPromotions({
54+
workspaceDir,
55+
candidates,
56+
minScore: 0,
57+
minRecallCount: 0,
58+
minUniqueQueries: 0,
59+
nowMs: Date.UTC(2026, 3, 5, 12, 10, 0),
60+
});
61+
62+
expect(applied.applied).toBe(1);
63+
64+
const events = await readMemoryHostEvents({ workspaceDir });
65+
66+
expect(events.map((event) => event.type)).toEqual([
67+
"memory.recall.recorded",
68+
"memory.promotion.applied",
69+
]);
70+
expect(events[0]).toMatchObject({
71+
type: "memory.recall.recorded",
72+
resultCount: 1,
73+
query: "alpha memory",
74+
});
75+
expect(events[1]).toMatchObject({
76+
type: "memory.promotion.applied",
77+
applied: 1,
78+
});
79+
});
80+
81+
it("records dreaming completion events when phase artifacts are written", async () => {
82+
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-core-dream-events-"));
83+
tempDirs.push(workspaceDir);
84+
85+
const written = await writeDailyDreamingPhaseBlock({
86+
workspaceDir,
87+
phase: "light",
88+
bodyLines: ["- staged note", "- second note"],
89+
nowMs: Date.UTC(2026, 3, 5, 13, 0, 0),
90+
storage: { mode: "both", separateReports: true },
91+
});
92+
93+
const events = await readMemoryHostEvents({ workspaceDir });
94+
95+
expect(written.inlinePath).toBeTruthy();
96+
expect(written.reportPath).toBeTruthy();
97+
expect(events).toHaveLength(1);
98+
expect(events[0]).toMatchObject({
99+
type: "memory.dream.completed",
100+
phase: "light",
101+
lineCount: 2,
102+
storageMode: "both",
103+
});
104+
});
105+
});

extensions/memory-core/src/short-term-promotion.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,7 @@ describe("short-term promotion", () => {
10861086
source: "memory",
10871087
snippet,
10881088
recallCount: 2,
1089+
dailyCount: 0,
10891090
totalScore: 1.8,
10901091
maxScore: 0.95,
10911092
firstRecalledAt: "2026-04-01T00:00:00.000Z",

0 commit comments

Comments
 (0)