mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 17:51:22 +00:00
124 lines
3.4 KiB
TypeScript
124 lines
3.4 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
export type MemoryWikiImportedSourceGroup = "bridge" | "unsafe-local";
|
|
|
|
export type MemoryWikiImportedSourceStateEntry = {
|
|
group: MemoryWikiImportedSourceGroup;
|
|
pagePath: string;
|
|
sourcePath: string;
|
|
sourceUpdatedAtMs: number;
|
|
sourceSize: number;
|
|
renderFingerprint: string;
|
|
};
|
|
|
|
type MemoryWikiImportedSourceState = {
|
|
version: 1;
|
|
entries: Record<string, MemoryWikiImportedSourceStateEntry>;
|
|
};
|
|
|
|
const EMPTY_STATE: MemoryWikiImportedSourceState = {
|
|
version: 1,
|
|
entries: {},
|
|
};
|
|
|
|
export function resolveMemoryWikiSourceSyncStatePath(vaultRoot: string): string {
|
|
return path.join(vaultRoot, ".openclaw-wiki", "source-sync.json");
|
|
}
|
|
|
|
export async function readMemoryWikiSourceSyncState(
|
|
vaultRoot: string,
|
|
): Promise<MemoryWikiImportedSourceState> {
|
|
const statePath = resolveMemoryWikiSourceSyncStatePath(vaultRoot);
|
|
const raw = await fs.readFile(statePath, "utf8").catch((err: unknown) => {
|
|
if ((err as NodeJS.ErrnoException)?.code === "ENOENT") {
|
|
return "";
|
|
}
|
|
throw err;
|
|
});
|
|
if (!raw.trim()) {
|
|
return {
|
|
version: EMPTY_STATE.version,
|
|
entries: {},
|
|
};
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(raw) as Partial<MemoryWikiImportedSourceState>;
|
|
return {
|
|
version: 1,
|
|
entries: { ...parsed.entries },
|
|
};
|
|
} catch {
|
|
return {
|
|
version: EMPTY_STATE.version,
|
|
entries: {},
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function writeMemoryWikiSourceSyncState(
|
|
vaultRoot: string,
|
|
state: MemoryWikiImportedSourceState,
|
|
): Promise<void> {
|
|
const statePath = resolveMemoryWikiSourceSyncStatePath(vaultRoot);
|
|
await fs.mkdir(path.dirname(statePath), { recursive: true });
|
|
await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
export async function shouldSkipImportedSourceWrite(params: {
|
|
vaultRoot: string;
|
|
syncKey: string;
|
|
expectedPagePath: string;
|
|
expectedSourcePath: string;
|
|
sourceUpdatedAtMs: number;
|
|
sourceSize: number;
|
|
renderFingerprint: string;
|
|
state: MemoryWikiImportedSourceState;
|
|
}): Promise<boolean> {
|
|
const entry = params.state.entries[params.syncKey];
|
|
if (!entry) {
|
|
return false;
|
|
}
|
|
if (
|
|
entry.pagePath !== params.expectedPagePath ||
|
|
entry.sourcePath !== params.expectedSourcePath ||
|
|
entry.sourceUpdatedAtMs !== params.sourceUpdatedAtMs ||
|
|
entry.sourceSize !== params.sourceSize ||
|
|
entry.renderFingerprint !== params.renderFingerprint
|
|
) {
|
|
return false;
|
|
}
|
|
const pagePath = path.join(params.vaultRoot, params.expectedPagePath);
|
|
return await fs
|
|
.access(pagePath)
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
}
|
|
|
|
export async function pruneImportedSourceEntries(params: {
|
|
vaultRoot: string;
|
|
group: MemoryWikiImportedSourceGroup;
|
|
activeKeys: Set<string>;
|
|
state: MemoryWikiImportedSourceState;
|
|
}): Promise<number> {
|
|
let removedCount = 0;
|
|
for (const [syncKey, entry] of Object.entries(params.state.entries)) {
|
|
if (entry.group !== params.group || params.activeKeys.has(syncKey)) {
|
|
continue;
|
|
}
|
|
const pageAbsPath = path.join(params.vaultRoot, entry.pagePath);
|
|
await fs.rm(pageAbsPath, { force: true }).catch(() => undefined);
|
|
delete params.state.entries[syncKey];
|
|
removedCount += 1;
|
|
}
|
|
return removedCount;
|
|
}
|
|
|
|
export function setImportedSourceEntry(params: {
|
|
syncKey: string;
|
|
entry: MemoryWikiImportedSourceStateEntry;
|
|
state: MemoryWikiImportedSourceState;
|
|
}): void {
|
|
params.state.entries[params.syncKey] = params.entry;
|
|
}
|