mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-21 21:56:46 +00:00
refactor: drop memory wiki vault metadata files
This commit is contained in:
@@ -304,6 +304,9 @@ The remaining cleanup is mostly consolidation and deletion:
|
||||
`.openclaw-wiki/log.jsonl`. The Memory Wiki migration provider imports old
|
||||
JSONL logs; wiki markdown and user vault content stay file-backed as
|
||||
workspace content.
|
||||
- Memory Wiki no longer creates `.openclaw-wiki/state.json` or the unused
|
||||
`.openclaw-wiki/locks` directory. The migration provider removes those retired
|
||||
plugin metadata files if an older vault still has them.
|
||||
- Crestodian audit entries now use core SQLite plugin state instead of
|
||||
`audit/crestodian.jsonl`. Doctor imports the legacy JSONL audit log and
|
||||
removes it after successful import.
|
||||
@@ -1082,6 +1085,8 @@ Add a repo check that fails new runtime writes to legacy state paths:
|
||||
- `crestodian/rescue-pending/*.json`
|
||||
- `plugins/phone-control/armed.json`
|
||||
- Memory Wiki `.openclaw-wiki/log.jsonl`
|
||||
- Memory Wiki `.openclaw-wiki/state.json`
|
||||
- Memory Wiki `.openclaw-wiki/locks/`
|
||||
- Memory Wiki `.openclaw-wiki/source-sync.json`
|
||||
- Memory Wiki `.openclaw-wiki/import-runs/*.json`
|
||||
- Memory Wiki `.openclaw-wiki/cache/agent-digest.json`
|
||||
|
||||
72
extensions/memory-wiki/src/source-sync-migration.test.ts
Normal file
72
extensions/memory-wiki/src/source-sync-migration.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { MigrationProviderContext } from "openclaw/plugin-sdk/migration";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { ResolvedMemoryWikiConfig } from "./config.js";
|
||||
import { createMemoryWikiSourceSyncMigrationProvider } from "./source-sync-migration.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
async function createVaultRoot(): Promise<string> {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "memory-wiki-migration-"));
|
||||
tempDirs.push(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
function createConfig(vaultRoot: string): ResolvedMemoryWikiConfig {
|
||||
return {
|
||||
vaultMode: "isolated",
|
||||
vault: { path: vaultRoot, renderMode: "native" },
|
||||
obsidian: { enabled: false, useOfficialCli: false, openAfterWrites: false },
|
||||
bridge: {
|
||||
enabled: false,
|
||||
readMemoryArtifacts: false,
|
||||
indexDreamReports: false,
|
||||
indexDailyNotes: false,
|
||||
indexMemoryRoot: false,
|
||||
followMemoryEvents: false,
|
||||
},
|
||||
unsafeLocal: { allowPrivateMemoryCoreAccess: false, paths: [] },
|
||||
ingest: { autoCompile: false, maxConcurrentJobs: 1, allowUrlIngest: false },
|
||||
search: { backend: "shared", corpus: "wiki" },
|
||||
context: { includeCompiledDigestPrompt: false },
|
||||
render: { preserveHumanBlocks: true, createBacklinks: true, createDashboards: true },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
describe("memory wiki source sync migration", () => {
|
||||
it("removes retired vault metadata files during doctor migration", async () => {
|
||||
const vaultRoot = await createVaultRoot();
|
||||
const metadataDir = path.join(vaultRoot, ".openclaw-wiki");
|
||||
const locksDir = path.join(metadataDir, "locks");
|
||||
await fs.mkdir(locksDir, { recursive: true });
|
||||
await fs.writeFile(path.join(metadataDir, "state.json"), '{"version":1}\n', "utf8");
|
||||
await fs.writeFile(path.join(locksDir, "stale.lock"), "stale", "utf8");
|
||||
|
||||
const provider = createMemoryWikiSourceSyncMigrationProvider(createConfig(vaultRoot));
|
||||
await expect(provider.detect()).resolves.toMatchObject({
|
||||
found: true,
|
||||
confidence: "high",
|
||||
});
|
||||
const plan = await provider.plan({} as MigrationProviderContext);
|
||||
|
||||
expect(plan.items.map((item) => item.id)).toContain("memory-wiki-vault-metadata-json");
|
||||
|
||||
const result = await provider.apply({} as MigrationProviderContext, plan);
|
||||
const item = result.items.find((item) => item.id === "memory-wiki-vault-metadata-json");
|
||||
|
||||
expect(item).toMatchObject({
|
||||
status: "migrated",
|
||||
details: { removedStateFile: true, removedLocksDir: true },
|
||||
});
|
||||
await expect(fs.stat(path.join(metadataDir, "state.json"))).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
await expect(fs.stat(locksDir)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,14 @@ import {
|
||||
|
||||
const PROVIDER_ID = "memory-wiki-source-sync";
|
||||
|
||||
function resolveLegacyVaultStatePath(vaultRoot: string): string {
|
||||
return path.join(vaultRoot, ".openclaw-wiki", "state.json");
|
||||
}
|
||||
|
||||
function resolveLegacyVaultLocksDir(vaultRoot: string): string {
|
||||
return path.join(vaultRoot, ".openclaw-wiki", "locks");
|
||||
}
|
||||
|
||||
async function legacySourceExists(vaultRoot: string): Promise<boolean> {
|
||||
const sourcePath = resolveMemoryWikiLegacySourceSyncStatePath(vaultRoot);
|
||||
return await fs
|
||||
@@ -31,6 +39,45 @@ async function legacyLogExists(vaultRoot: string): Promise<boolean> {
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
async function legacyVaultMetadataExists(vaultRoot: string): Promise<boolean> {
|
||||
const [hasStateFile, hasLocksDir] = await Promise.all([
|
||||
fs
|
||||
.stat(resolveLegacyVaultStatePath(vaultRoot))
|
||||
.then((stat) => stat.isFile())
|
||||
.catch(() => false),
|
||||
fs
|
||||
.stat(resolveLegacyVaultLocksDir(vaultRoot))
|
||||
.then((stat) => stat.isDirectory())
|
||||
.catch(() => false),
|
||||
]);
|
||||
return hasStateFile || hasLocksDir;
|
||||
}
|
||||
|
||||
async function removeLegacyVaultMetadata(vaultRoot: string): Promise<{
|
||||
removedStateFile: boolean;
|
||||
removedLocksDir: boolean;
|
||||
}> {
|
||||
const statePath = resolveLegacyVaultStatePath(vaultRoot);
|
||||
const locksDir = resolveLegacyVaultLocksDir(vaultRoot);
|
||||
const [hadStateFile, hadLocksDir] = await Promise.all([
|
||||
fs
|
||||
.stat(statePath)
|
||||
.then((stat) => stat.isFile())
|
||||
.catch(() => false),
|
||||
fs
|
||||
.stat(locksDir)
|
||||
.then((stat) => stat.isDirectory())
|
||||
.catch(() => false),
|
||||
]);
|
||||
if (hadStateFile) {
|
||||
await fs.rm(statePath, { force: true });
|
||||
}
|
||||
if (hadLocksDir) {
|
||||
await fs.rm(locksDir, { recursive: true, force: true });
|
||||
}
|
||||
return { removedStateFile: hadStateFile, removedLocksDir: hadLocksDir };
|
||||
}
|
||||
|
||||
function resolveLegacyImportRunsDir(vaultRoot: string): string {
|
||||
return path.join(vaultRoot, ".openclaw-wiki", "import-runs");
|
||||
}
|
||||
@@ -88,8 +135,21 @@ export function createMemoryWikiSourceSyncMigrationProvider(
|
||||
const hasSourceSync = await legacySourceExists(config.vault.path);
|
||||
const hasLegacyLog = await legacyLogExists(config.vault.path);
|
||||
const hasLegacyDigests = await legacyMemoryWikiDigestFilesExist(config.vault.path);
|
||||
const hasLegacyVaultMetadata = await legacyVaultMetadataExists(config.vault.path);
|
||||
const importRunFiles = await listLegacyImportRunJsonFiles(config.vault.path);
|
||||
const items = [
|
||||
...(hasLegacyVaultMetadata
|
||||
? [
|
||||
createMigrationItem({
|
||||
id: "memory-wiki-vault-metadata-json",
|
||||
kind: "state",
|
||||
action: "archive",
|
||||
source: path.join(config.vault.path, ".openclaw-wiki"),
|
||||
target: "none; Memory Wiki vault metadata is derived from config and SQLite state",
|
||||
message: "Remove retired Memory Wiki vault state.json and locks directory.",
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(hasSourceSync
|
||||
? [
|
||||
createMigrationItem({
|
||||
@@ -157,6 +217,7 @@ export function createMemoryWikiSourceSyncMigrationProvider(
|
||||
const found =
|
||||
(await legacySourceExists(config.vault.path)) ||
|
||||
(await legacyLogExists(config.vault.path)) ||
|
||||
(await legacyVaultMetadataExists(config.vault.path)) ||
|
||||
(await legacyMemoryWikiDigestFilesExist(config.vault.path)) ||
|
||||
(await listLegacyImportRunJsonFiles(config.vault.path)).length > 0;
|
||||
return {
|
||||
@@ -180,7 +241,14 @@ export function createMemoryWikiSourceSyncMigrationProvider(
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (item.id === "memory-wiki-source-sync-json") {
|
||||
if (item.id === "memory-wiki-vault-metadata-json") {
|
||||
const result = await removeLegacyVaultMetadata(config.vault.path);
|
||||
items[itemIndex] = {
|
||||
...item,
|
||||
status: "migrated",
|
||||
details: result,
|
||||
};
|
||||
} else if (item.id === "memory-wiki-source-sync-json") {
|
||||
const result = await importMemoryWikiLegacySourceSyncState({
|
||||
vaultRoot: config.vault.path,
|
||||
});
|
||||
|
||||
@@ -34,9 +34,12 @@ describe("initializeMemoryWikiVault", () => {
|
||||
await expect(fs.readFile(path.join(rootDir, "WIKI.md"), "utf8")).resolves.toContain(
|
||||
"Render mode: `obsidian`",
|
||||
);
|
||||
await expect(
|
||||
fs.readFile(path.join(rootDir, ".openclaw-wiki", "state.json"), "utf8"),
|
||||
).resolves.toContain('"renderMode": "obsidian"');
|
||||
await expect(fs.stat(path.join(rootDir, ".openclaw-wiki", "state.json"))).rejects.toMatchObject(
|
||||
{ code: "ENOENT" },
|
||||
);
|
||||
await expect(fs.stat(path.join(rootDir, ".openclaw-wiki", "locks"))).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
});
|
||||
|
||||
it("is idempotent when the vault already exists", async () => {
|
||||
|
||||
@@ -17,7 +17,6 @@ export const WIKI_VAULT_DIRECTORIES = [
|
||||
"_attachments",
|
||||
"_views",
|
||||
".openclaw-wiki",
|
||||
".openclaw-wiki/locks",
|
||||
] as const;
|
||||
|
||||
type InitializeMemoryWikiVaultResult = {
|
||||
@@ -120,22 +119,6 @@ export async function initializeMemoryWikiVault(
|
||||
withTrailingNewline("# Inbox\n\nDrop raw ideas, questions, and source links here.\n"),
|
||||
createdFiles,
|
||||
);
|
||||
await writeFileIfMissing(
|
||||
rootDir,
|
||||
".openclaw-wiki/state.json",
|
||||
withTrailingNewline(
|
||||
JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
createdAt: new Date(options?.nowMs ?? Date.now()).toISOString(),
|
||||
renderMode: config.vault.renderMode,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
),
|
||||
createdFiles,
|
||||
);
|
||||
if (createdDirectories.length > 0 || createdFiles.length > 0) {
|
||||
await appendMemoryWikiLog(rootDir, {
|
||||
type: "init",
|
||||
|
||||
@@ -73,6 +73,8 @@ const legacyStoreMarkers = [
|
||||
},
|
||||
{ label: "Memory Wiki source sync JSON", pattern: /\bsource-sync\.json\b/u },
|
||||
{ label: "Memory Wiki activity JSONL", pattern: /\b\.openclaw-wiki[/\\]log\.jsonl\b/u },
|
||||
{ label: "Memory Wiki vault metadata JSON", pattern: /\b\.openclaw-wiki[/\\]state\.json\b/u },
|
||||
{ label: "Memory Wiki vault lock directory", pattern: /\b\.openclaw-wiki[/\\]locks\b/u },
|
||||
{
|
||||
label: "Memory Wiki import run JSON",
|
||||
pattern: /\bimport-runs[/\\][A-Za-z0-9._-]+\.json\b/u,
|
||||
|
||||
Reference in New Issue
Block a user