Skip to content

Commit efd9aae

Browse files
committed
refactor: dedupe memory wiki source page writes
1 parent 79a84f0 commit efd9aae

3 files changed

Lines changed: 141 additions & 122 deletions

File tree

extensions/memory-wiki/src/bridge.ts

Lines changed: 43 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@ import type { OpenClawConfig } from "../api.js";
1010
import type { ResolvedMemoryWikiConfig } from "./config.js";
1111
import { appendMemoryWikiLog } from "./log.js";
1212
import { renderMarkdownFence, renderWikiMarkdown, slugifyWikiSegment } from "./markdown.js";
13+
import { writeImportedSourcePage } from "./source-page-shared.js";
1314
import { pathExists, resolveArtifactKey } from "./source-path-shared.js";
1415
import {
1516
pruneImportedSourceEntries,
1617
readMemoryWikiSourceSyncState,
17-
setImportedSourceEntry,
18-
shouldSkipImportedSourceWrite,
1918
writeMemoryWikiSourceSyncState,
2019
} from "./source-sync-state.js";
2120
import { initializeMemoryWikiVault } from "./vault.js";
@@ -184,9 +183,6 @@ async function writeBridgeSourcePage(params: {
184183
relativePath: params.artifact.relativePath,
185184
});
186185
const title = resolveBridgeTitle(params.artifact, params.agentIds);
187-
const pageAbsPath = path.join(params.config.vault.path, pagePath);
188-
const created = !(await pathExists(pageAbsPath));
189-
const sourceUpdatedAt = new Date(params.sourceUpdatedAtMs).toISOString();
190186
const renderFingerprint = createHash("sha1")
191187
.update(
192188
JSON.stringify({
@@ -197,71 +193,56 @@ async function writeBridgeSourcePage(params: {
197193
}),
198194
)
199195
.digest("hex");
200-
const shouldSkip = await shouldSkipImportedSourceWrite({
196+
return writeImportedSourcePage({
201197
vaultRoot: params.config.vault.path,
202198
syncKey: params.artifact.syncKey,
203-
expectedPagePath: pagePath,
204-
expectedSourcePath: params.artifact.absolutePath,
199+
sourcePath: params.artifact.absolutePath,
205200
sourceUpdatedAtMs: params.sourceUpdatedAtMs,
206201
sourceSize: params.sourceSize,
207202
renderFingerprint,
203+
pagePath,
204+
group: "bridge",
208205
state: params.state,
209-
});
210-
if (shouldSkip) {
211-
return { pagePath, changed: false, created };
212-
}
213-
const raw = await fs.readFile(params.artifact.absolutePath, "utf8");
214-
const contentLanguage = params.artifact.artifactType === "memory-events" ? "json" : "markdown";
215-
const rendered = renderWikiMarkdown({
216-
frontmatter: {
217-
pageType: "source",
218-
id: pageId,
219-
title,
220-
sourceType:
221-
params.artifact.artifactType === "memory-events" ? "memory-bridge-events" : "memory-bridge",
222-
sourcePath: params.artifact.absolutePath,
223-
bridgeRelativePath: params.artifact.relativePath,
224-
bridgeWorkspaceDir: params.artifact.workspaceDir,
225-
bridgeAgentIds: params.agentIds,
226-
status: "active",
227-
updatedAt: sourceUpdatedAt,
228-
},
229-
body: [
230-
`# ${title}`,
231-
"",
232-
"## Bridge Source",
233-
`- Workspace: \`${params.artifact.workspaceDir}\``,
234-
`- Relative path: \`${params.artifact.relativePath}\``,
235-
`- Kind: \`${params.artifact.artifactType}\``,
236-
`- Agents: ${params.agentIds.length > 0 ? params.agentIds.join(", ") : "unknown"}`,
237-
`- Updated: ${sourceUpdatedAt}`,
238-
"",
239-
"## Content",
240-
renderMarkdownFence(raw, contentLanguage),
241-
"",
242-
"## Notes",
243-
"<!-- openclaw:human:start -->",
244-
"<!-- openclaw:human:end -->",
245-
"",
246-
].join("\n"),
247-
});
248-
const existing = await fs.readFile(pageAbsPath, "utf8").catch(() => "");
249-
if (existing !== rendered) {
250-
await fs.writeFile(pageAbsPath, rendered, "utf8");
251-
}
252-
setImportedSourceEntry({
253-
syncKey: params.artifact.syncKey,
254-
state: params.state,
255-
entry: {
256-
group: "bridge",
257-
pagePath,
258-
sourcePath: params.artifact.absolutePath,
259-
sourceUpdatedAtMs: params.sourceUpdatedAtMs,
260-
sourceSize: params.sourceSize,
261-
renderFingerprint,
206+
buildRendered: (raw, updatedAt) => {
207+
const contentLanguage =
208+
params.artifact.artifactType === "memory-events" ? "json" : "markdown";
209+
return renderWikiMarkdown({
210+
frontmatter: {
211+
pageType: "source",
212+
id: pageId,
213+
title,
214+
sourceType:
215+
params.artifact.artifactType === "memory-events"
216+
? "memory-bridge-events"
217+
: "memory-bridge",
218+
sourcePath: params.artifact.absolutePath,
219+
bridgeRelativePath: params.artifact.relativePath,
220+
bridgeWorkspaceDir: params.artifact.workspaceDir,
221+
bridgeAgentIds: params.agentIds,
222+
status: "active",
223+
updatedAt,
224+
},
225+
body: [
226+
`# ${title}`,
227+
"",
228+
"## Bridge Source",
229+
`- Workspace: \`${params.artifact.workspaceDir}\``,
230+
`- Relative path: \`${params.artifact.relativePath}\``,
231+
`- Kind: \`${params.artifact.artifactType}\``,
232+
`- Agents: ${params.agentIds.length > 0 ? params.agentIds.join(", ") : "unknown"}`,
233+
`- Updated: ${updatedAt}`,
234+
"",
235+
"## Content",
236+
renderMarkdownFence(raw, contentLanguage),
237+
"",
238+
"## Notes",
239+
"<!-- openclaw:human:start -->",
240+
"<!-- openclaw:human:end -->",
241+
"",
242+
].join("\n"),
243+
});
262244
},
263245
});
264-
return { pagePath, changed: existing !== rendered, created };
265246
}
266247

267248
export async function syncMemoryWikiBridgeSources(params: {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import fs from "node:fs/promises";
2+
import path from "node:path";
3+
import { pathExists } from "./source-path-shared.js";
4+
import {
5+
setImportedSourceEntry,
6+
shouldSkipImportedSourceWrite,
7+
type MemoryWikiImportedSourceGroup,
8+
} from "./source-sync-state.js";
9+
10+
type ImportedSourceState = Parameters<typeof shouldSkipImportedSourceWrite>[0]["state"];
11+
12+
export async function writeImportedSourcePage(params: {
13+
vaultRoot: string;
14+
syncKey: string;
15+
sourcePath: string;
16+
sourceUpdatedAtMs: number;
17+
sourceSize: number;
18+
renderFingerprint: string;
19+
pagePath: string;
20+
group: MemoryWikiImportedSourceGroup;
21+
state: ImportedSourceState;
22+
buildRendered: (raw: string, updatedAt: string) => string;
23+
}): Promise<{ pagePath: string; changed: boolean; created: boolean }> {
24+
const pageAbsPath = path.join(params.vaultRoot, params.pagePath);
25+
const created = !(await pathExists(pageAbsPath));
26+
const updatedAt = new Date(params.sourceUpdatedAtMs).toISOString();
27+
const shouldSkip = await shouldSkipImportedSourceWrite({
28+
vaultRoot: params.vaultRoot,
29+
syncKey: params.syncKey,
30+
expectedPagePath: params.pagePath,
31+
expectedSourcePath: params.sourcePath,
32+
sourceUpdatedAtMs: params.sourceUpdatedAtMs,
33+
sourceSize: params.sourceSize,
34+
renderFingerprint: params.renderFingerprint,
35+
state: params.state,
36+
});
37+
if (shouldSkip) {
38+
return { pagePath: params.pagePath, changed: false, created };
39+
}
40+
41+
const raw = await fs.readFile(params.sourcePath, "utf8");
42+
const rendered = params.buildRendered(raw, updatedAt);
43+
const existing = await fs.readFile(pageAbsPath, "utf8").catch(() => "");
44+
if (existing !== rendered) {
45+
await fs.writeFile(pageAbsPath, rendered, "utf8");
46+
}
47+
48+
setImportedSourceEntry({
49+
syncKey: params.syncKey,
50+
state: params.state,
51+
entry: {
52+
group: params.group,
53+
pagePath: params.pagePath,
54+
sourcePath: params.sourcePath,
55+
sourceUpdatedAtMs: params.sourceUpdatedAtMs,
56+
sourceSize: params.sourceSize,
57+
renderFingerprint: params.renderFingerprint,
58+
},
59+
});
60+
return { pagePath: params.pagePath, changed: existing !== rendered, created };
61+
}

extensions/memory-wiki/src/unsafe-local.ts

Lines changed: 37 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ import type { BridgeMemoryWikiResult } from "./bridge.js";
55
import type { ResolvedMemoryWikiConfig } from "./config.js";
66
import { appendMemoryWikiLog } from "./log.js";
77
import { renderMarkdownFence, renderWikiMarkdown, slugifyWikiSegment } from "./markdown.js";
8-
import { pathExists, resolveArtifactKey } from "./source-path-shared.js";
8+
import { writeImportedSourcePage } from "./source-page-shared.js";
9+
import { resolveArtifactKey } from "./source-path-shared.js";
910
import {
1011
pruneImportedSourceEntries,
1112
readMemoryWikiSourceSyncState,
12-
setImportedSourceEntry,
13-
shouldSkipImportedSourceWrite,
1413
writeMemoryWikiSourceSyncState,
1514
} from "./source-sync-state.js";
1615
import { initializeMemoryWikiVault } from "./vault.js";
@@ -129,9 +128,6 @@ async function writeUnsafeLocalSourcePage(params: {
129128
configuredPath: params.artifact.configuredPath,
130129
absolutePath: params.artifact.absolutePath,
131130
});
132-
const pageAbsPath = path.join(params.config.vault.path, pagePath);
133-
const created = !(await pathExists(pageAbsPath));
134-
const updatedAt = new Date(params.sourceUpdatedAtMs).toISOString();
135131
const title = resolveUnsafeLocalTitle(params.artifact);
136132
const renderFingerprint = createHash("sha1")
137133
.update(
@@ -141,67 +137,48 @@ async function writeUnsafeLocalSourcePage(params: {
141137
}),
142138
)
143139
.digest("hex");
144-
const shouldSkip = await shouldSkipImportedSourceWrite({
140+
return writeImportedSourcePage({
145141
vaultRoot: params.config.vault.path,
146142
syncKey: params.artifact.syncKey,
147-
expectedPagePath: pagePath,
148-
expectedSourcePath: params.artifact.absolutePath,
143+
sourcePath: params.artifact.absolutePath,
149144
sourceUpdatedAtMs: params.sourceUpdatedAtMs,
150145
sourceSize: params.sourceSize,
151146
renderFingerprint,
147+
pagePath,
148+
group: "unsafe-local",
152149
state: params.state,
150+
buildRendered: (raw, updatedAt) =>
151+
renderWikiMarkdown({
152+
frontmatter: {
153+
pageType: "source",
154+
id: pageId,
155+
title,
156+
sourceType: "memory-unsafe-local",
157+
provenanceMode: "unsafe-local",
158+
sourcePath: params.artifact.absolutePath,
159+
unsafeLocalConfiguredPath: params.artifact.configuredPath,
160+
unsafeLocalRelativePath: params.artifact.relativePath,
161+
status: "active",
162+
updatedAt,
163+
},
164+
body: [
165+
`# ${title}`,
166+
"",
167+
"## Unsafe Local Source",
168+
`- Configured path: \`${params.artifact.configuredPath}\``,
169+
`- Relative path: \`${params.artifact.relativePath}\``,
170+
`- Updated: ${updatedAt}`,
171+
"",
172+
"## Content",
173+
renderMarkdownFence(raw, detectFenceLanguage(params.artifact.absolutePath)),
174+
"",
175+
"## Notes",
176+
"<!-- openclaw:human:start -->",
177+
"<!-- openclaw:human:end -->",
178+
"",
179+
].join("\n"),
180+
}),
153181
});
154-
if (shouldSkip) {
155-
return { pagePath, changed: false, created };
156-
}
157-
const raw = await fs.readFile(params.artifact.absolutePath, "utf8");
158-
const rendered = renderWikiMarkdown({
159-
frontmatter: {
160-
pageType: "source",
161-
id: pageId,
162-
title,
163-
sourceType: "memory-unsafe-local",
164-
provenanceMode: "unsafe-local",
165-
sourcePath: params.artifact.absolutePath,
166-
unsafeLocalConfiguredPath: params.artifact.configuredPath,
167-
unsafeLocalRelativePath: params.artifact.relativePath,
168-
status: "active",
169-
updatedAt,
170-
},
171-
body: [
172-
`# ${title}`,
173-
"",
174-
"## Unsafe Local Source",
175-
`- Configured path: \`${params.artifact.configuredPath}\``,
176-
`- Relative path: \`${params.artifact.relativePath}\``,
177-
`- Updated: ${updatedAt}`,
178-
"",
179-
"## Content",
180-
renderMarkdownFence(raw, detectFenceLanguage(params.artifact.absolutePath)),
181-
"",
182-
"## Notes",
183-
"<!-- openclaw:human:start -->",
184-
"<!-- openclaw:human:end -->",
185-
"",
186-
].join("\n"),
187-
});
188-
const existing = await fs.readFile(pageAbsPath, "utf8").catch(() => "");
189-
if (existing !== rendered) {
190-
await fs.writeFile(pageAbsPath, rendered, "utf8");
191-
}
192-
setImportedSourceEntry({
193-
syncKey: params.artifact.syncKey,
194-
state: params.state,
195-
entry: {
196-
group: "unsafe-local",
197-
pagePath,
198-
sourcePath: params.artifact.absolutePath,
199-
sourceUpdatedAtMs: params.sourceUpdatedAtMs,
200-
sourceSize: params.sourceSize,
201-
renderFingerprint,
202-
},
203-
});
204-
return { pagePath, changed: existing !== rendered, created };
205182
}
206183

207184
export async function syncMemoryWikiUnsafeLocalSources(

0 commit comments

Comments
 (0)