mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-13 01:00:41 +00:00
* refactor: extract filesystem safety primitives * refactor: use fs-safe for file access helpers * refactor: reuse fs-safe for media reads * refactor: use fs-safe for image reads * refactor: reuse fs-safe in qqbot media opener * refactor: reuse fs-safe for local media checks * refactor: consume cleaner fs-safe api * refactor: align fs-safe json option names * fix: preserve fs-safe migration contracts * refactor: use fs-safe primitive subpaths * refactor: use grouped fs-safe subpaths * refactor: align fs-safe api usage * refactor: adapt private state store api * chore: refresh proof gate * refactor: follow fs-safe json api split * refactor: follow reduced fs-safe surface * build: default fs-safe python helper off * fix: preserve fs-safe plugin sdk aliases * refactor: consolidate fs-safe usage * refactor: unify fs-safe store usage * refactor: trim fs-safe temp workspace usage * refactor: hide low-level fs-safe primitives * build: use published fs-safe package * fix: preserve outbound recovery durability after rebase * chore: refresh pr checks
160 lines
4.6 KiB
TypeScript
160 lines
4.6 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import {
|
|
replaceManagedMarkdownBlock,
|
|
withTrailingNewline,
|
|
} from "openclaw/plugin-sdk/memory-host-markdown";
|
|
import { FsSafeError, pathExists, root as fsRoot } from "openclaw/plugin-sdk/security-runtime";
|
|
import type { ResolvedMemoryWikiConfig } from "./config.js";
|
|
import { appendMemoryWikiLog } from "./log.js";
|
|
|
|
export const WIKI_VAULT_DIRECTORIES = [
|
|
"entities",
|
|
"concepts",
|
|
"syntheses",
|
|
"sources",
|
|
"reports",
|
|
"_attachments",
|
|
"_views",
|
|
".openclaw-wiki",
|
|
".openclaw-wiki/locks",
|
|
".openclaw-wiki/cache",
|
|
] as const;
|
|
|
|
type InitializeMemoryWikiVaultResult = {
|
|
rootDir: string;
|
|
created: boolean;
|
|
createdDirectories: string[];
|
|
createdFiles: string[];
|
|
};
|
|
|
|
function buildIndexMarkdown(): string {
|
|
return withTrailingNewline(
|
|
replaceManagedMarkdownBlock({
|
|
original: "# Wiki Index\n",
|
|
heading: "## Generated",
|
|
startMarker: "<!-- openclaw:wiki:index:start -->",
|
|
endMarker: "<!-- openclaw:wiki:index:end -->",
|
|
body: "- No compiled pages yet.",
|
|
}),
|
|
);
|
|
}
|
|
|
|
function buildAgentsMarkdown(): string {
|
|
return withTrailingNewline(`\
|
|
# Memory Wiki Agent Guide
|
|
|
|
- Treat generated blocks as plugin-owned.
|
|
- Preserve human notes outside managed markers.
|
|
- Prefer source-backed claims over wiki-to-wiki citation loops.
|
|
- Prefer structured \`claims\` with evidence over burying key beliefs only in prose.
|
|
- Use \`.openclaw-wiki/cache/agent-digest.json\` and \`claims.jsonl\` for machine reads; markdown pages are the human view.
|
|
`);
|
|
}
|
|
|
|
function buildWikiOverviewMarkdown(config: ResolvedMemoryWikiConfig): string {
|
|
return withTrailingNewline(`\
|
|
# Memory Wiki
|
|
|
|
This vault is maintained by the OpenClaw memory-wiki plugin.
|
|
|
|
- Vault mode: \`${config.vaultMode}\`
|
|
- Render mode: \`${config.vault.renderMode}\`
|
|
- Search corpus default: \`${config.search.corpus}\`
|
|
|
|
## Architecture
|
|
- Raw sources remain the evidence layer.
|
|
- Wiki pages are the human-readable synthesis layer.
|
|
- \`.openclaw-wiki/cache/agent-digest.json\` is the agent-facing compiled digest.
|
|
|
|
## Notes
|
|
<!-- openclaw:human:start -->
|
|
<!-- openclaw:human:end -->
|
|
`);
|
|
}
|
|
|
|
async function writeFileIfMissing(
|
|
rootDir: string,
|
|
relativePath: string,
|
|
content: string,
|
|
createdFiles: string[],
|
|
): Promise<void> {
|
|
const root = await fsRoot(rootDir);
|
|
try {
|
|
await root.create(relativePath, content);
|
|
} catch (err) {
|
|
if (err instanceof FsSafeError && err.code === "already-exists") {
|
|
return;
|
|
}
|
|
throw err;
|
|
}
|
|
createdFiles.push(path.join(rootDir, relativePath));
|
|
}
|
|
|
|
export async function initializeMemoryWikiVault(
|
|
config: ResolvedMemoryWikiConfig,
|
|
options?: { nowMs?: number },
|
|
): Promise<InitializeMemoryWikiVaultResult> {
|
|
const rootDir = config.vault.path;
|
|
const createdDirectories: string[] = [];
|
|
const createdFiles: string[] = [];
|
|
|
|
if (!(await pathExists(rootDir))) {
|
|
createdDirectories.push(rootDir);
|
|
}
|
|
await fs.mkdir(rootDir, { recursive: true });
|
|
|
|
for (const relativeDir of WIKI_VAULT_DIRECTORIES) {
|
|
const fullPath = path.join(rootDir, relativeDir);
|
|
if (!(await pathExists(fullPath))) {
|
|
createdDirectories.push(fullPath);
|
|
}
|
|
await fs.mkdir(fullPath, { recursive: true });
|
|
}
|
|
|
|
await writeFileIfMissing(rootDir, "AGENTS.md", buildAgentsMarkdown(), createdFiles);
|
|
await writeFileIfMissing(rootDir, "WIKI.md", buildWikiOverviewMarkdown(config), createdFiles);
|
|
await writeFileIfMissing(rootDir, "index.md", buildIndexMarkdown(), createdFiles);
|
|
await writeFileIfMissing(
|
|
rootDir,
|
|
"inbox.md",
|
|
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,
|
|
);
|
|
await writeFileIfMissing(rootDir, ".openclaw-wiki/log.jsonl", "", createdFiles);
|
|
|
|
if (createdDirectories.length > 0 || createdFiles.length > 0) {
|
|
await appendMemoryWikiLog(rootDir, {
|
|
type: "init",
|
|
timestamp: new Date(options?.nowMs ?? Date.now()).toISOString(),
|
|
details: {
|
|
createdDirectories: createdDirectories.map((dir) => path.relative(rootDir, dir) || "."),
|
|
createdFiles: createdFiles.map((file) => path.relative(rootDir, file)),
|
|
},
|
|
});
|
|
}
|
|
|
|
return {
|
|
rootDir,
|
|
created: createdDirectories.length > 0 || createdFiles.length > 0,
|
|
createdDirectories,
|
|
createdFiles,
|
|
};
|
|
}
|