openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
897 lines (896 loc) • 39.5 kB
JavaScript
import { t as asNonArrayRecord } from "../../record-coerce-DItp3I4t.js";
import { y as uniqueStrings } from "../../string-normalization-DsCfAx8q.js";
import { r as truncateUtf16Safe } from "../../utf16-slice-D_ngcYKd.js";
import "../../api-BRlGb14C.js";
import { y as resolveDefaultAgentId } from "../../agent-scope-config-DcbEhP0R.js";
import { t as formatErrorMessage } from "../../errors-Db3Ymjlb.js";
import { t as ErrorCodes } from "../../gateway-error-details-w0nAGBBp.js";
import { d as errorShape } from "../../error-codes-Bo8q2D1o.js";
import { i as optionalFiniteNumberSchema } from "../../typebox-BdE5GwKs.js";
import { n as textResult } from "../../tool-results-BCM3fdVS.js";
import { d as readPositiveIntegerParam } from "../../common-Bm6UTDDA.js";
import { n as buildPluginConfigSchema } from "../../config-schema-ChMzSV8V.js";
import "../../error-runtime-Bz9Tw57Z.js";
import "../../string-coerce-runtime-GQa0ehRA.js";
import { t as definePluginEntry } from "../../plugin-entry-zfBGJaNO.js";
import "../../gateway-runtime-BkyVf3uU.js";
import { s as mapPluginConfigIssues } from "../../extension-shared-BAZUCAhf.js";
import "../../channel-actions-CrPVkBaw.js";
import "../../memory-host-core-BuVChYR2.js";
import "../../param-readers-BElxWJzG.js";
import "../../text-utility-runtime-BjzvUG99.js";
import { A as MemoryWikiDashboardUnavailableError, C as getMemoryWikiPage, F as loadMemoryWikiCompiledCache, I as reconcileMemoryWikiCompiledCacheOwner, L as resolveMemoryWikiCompiledCacheOwnerId, M as configureMemoryWikiCompiledCacheStore, N as createMemoryWikiCompiledCacheStore, O as ensureMemoryWikiVaultGeneration, P as deactivateMemoryWikiCompiledCacheOwnersExcept, R as setMemoryWikiDashboardState, S as WIKI_SEARCH_MODES, T as initializeMemoryWikiVault, _ as normalizeMemoryWikiMutationInput, a as syncMemoryWikiImportedSources, d as runObsidianDaily, f as runObsidianOpen, g as applyMemoryWikiMutation, h as ingestMemoryWikiSource, i as resolveMemoryWikiStatus, j as activateMemoryWikiCompiledCacheOwner, k as loadMemoryWikiValidatedVaultIdentity, l as probeObsidianCli, m as lintMemoryWikiVault, o as waitForMemoryWikiImportedSourceSyncs, p as runObsidianSearch, r as renderMemoryWikiStatus, t as buildMemoryWikiDoctorReport, u as runObsidianCommand, v as compileMemoryWikiVault, w as searchMemoryWiki, x as listMemoryWikiImportInsights, y as listMemoryWikiOverview } from "../../status-mk9AERH5.js";
import { a as resolveMemoryWikiConfig, i as resolveMemoryWikiAgentConfig, n as WIKI_SEARCH_BACKENDS, o as resolveMemoryWikiConfiguredAgentIds, r as WIKI_SEARCH_CORPORA, t as MemoryWikiConfigSource } from "../../config-CpK-zpn0.js";
import { a as createMemoryWikiImportRunStateStore, h as createMemoryWikiSourceSyncStateStore, m as configureMemoryWikiSourceSyncStateStore, o as listMemoryWikiImportRunRecords, r as configureMemoryWikiImportRunStateStore } from "../../import-runs-state-COPJ30nf.js";
import path from "node:path";
import fs from "node:fs/promises";
import { Type } from "typebox";
//#region extensions/memory-wiki/src/config-schema.ts
const memoryWikiConfigSchema = buildPluginConfigSchema(MemoryWikiConfigSource, { safeParse(value) {
if (value === void 0) return {
success: true,
data: resolveMemoryWikiConfig(void 0)
};
const result = MemoryWikiConfigSource.safeParse(value);
if (result.success) return {
success: true,
data: resolveMemoryWikiConfig(result.data)
};
return {
success: false,
error: { issues: mapPluginConfigIssues(result.error.issues) }
};
} });
//#endregion
//#region extensions/memory-wiki/src/corpus-supplement.ts
function createWikiCorpusSupplement(params) {
return {
search: async (input) => {
const appConfig = params.getAppConfig();
const config = params.resolveConfig(input.agentId, appConfig);
return await searchMemoryWiki({
config,
appConfig,
agentId: config.agentId ?? input.agentId,
agentSessionKey: input.agentSessionKey,
sandboxed: input.sandboxed,
query: input.query,
maxResults: input.maxResults,
searchBackend: "local",
searchCorpus: "wiki"
});
},
get: async (input) => {
const appConfig = params.getAppConfig();
const config = params.resolveConfig(input.agentId, appConfig);
return await getMemoryWikiPage({
config,
appConfig,
agentId: config.agentId ?? input.agentId,
agentSessionKey: input.agentSessionKey,
sandboxed: input.sandboxed,
lookup: input.lookup,
fromLine: input.fromLine,
lineCount: input.lineCount,
searchBackend: "local",
searchCorpus: "wiki"
});
}
};
}
//#endregion
//#region extensions/memory-wiki/src/import-runs.ts
function toImportRunSummary(record) {
const createdPaths = record.createdPaths.map((entry) => entry.path);
const updatedPaths = record.updatedPaths.map((entry) => entry.path);
const pagePaths = uniqueStrings([...createdPaths, ...updatedPaths]);
const rollingBack = Boolean(record.rollbackStartedAt || record.rollbackTargetsFinalizedAt);
return {
runId: record.runId,
importType: record.importType,
appliedAt: record.appliedAt,
exportPath: record.exportPath,
sourcePath: record.sourcePath,
conversationCount: record.conversationCount,
createdCount: record.createdCount,
updatedCount: record.updatedCount,
skippedCount: record.skippedCount,
status: record.rolledBackAt ? "rolled_back" : rollingBack ? "rolling_back" : "applied",
...record.rollbackStartedAt ? { rollbackStartedAt: record.rollbackStartedAt } : {},
...record.rollbackTargetsFinalizedAt ? { rollbackTargetsFinalizedAt: record.rollbackTargetsFinalizedAt } : {},
...record.rolledBackAt ? { rolledBackAt: record.rolledBackAt } : {},
pagePaths,
samplePaths: pagePaths.slice(0, 5)
};
}
async function listMemoryWikiImportRuns(config, options) {
const limit = Math.max(1, Math.floor(options?.limit ?? 10));
const runs = (await listMemoryWikiImportRunRecords(config.vault.path)).map(toImportRunSummary).toSorted((left, right) => right.appliedAt.localeCompare(left.appliedAt));
return {
runs: runs.slice(0, limit),
totalRuns: runs.length,
activeRuns: runs.filter((entry) => entry.status !== "rolled_back").length,
rolledBackRuns: runs.filter((entry) => entry.status === "rolled_back").length
};
}
//#endregion
//#region extensions/memory-wiki/src/gateway.ts
const READ_SCOPE = "operator.read";
const WRITE_SCOPE = "operator.write";
const LOCAL_FILE_INGEST_SCOPE = "operator.admin";
function readStringParam(params, key, options) {
const value = params[key];
if (typeof value === "string" && value.trim()) return value.trim();
if (options?.required) throw new Error(`${key} is required.`);
}
function readEnumParam(params, key, allowed) {
const value = readStringParam(params, key);
if (!value) return;
if (allowed.includes(value)) return value;
throw new Error(`${key} must be one of: ${allowed.join(", ")}.`);
}
function respondError(respond, error) {
if (error instanceof MemoryWikiDashboardUnavailableError) {
const retryable = error.state === "rebuilding";
respond(false, void 0, errorShape(error.state === "compile-required" ? ErrorCodes.INVALID_REQUEST : ErrorCodes.UNAVAILABLE, error.message, {
details: { state: error.state },
...retryable ? {
retryable: true,
retryAfterMs: 500
} : {}
}));
return;
}
respond(false, void 0, {
code: "internal_error",
message: formatErrorMessage(error)
});
}
function registerMemoryWikiGatewayMethods(params) {
const { api, config: baseConfig } = params;
const syncImportedSourcesInBackground = (config, appConfig) => {
const signal = params.resolveSourceSyncSignal?.();
if (params.resolveSourceSyncSignal && !signal) return;
syncMemoryWikiImportedSources({
config,
appConfig,
...signal ? { signal } : {}
}).catch((error) => {
if (signal?.aborted) return;
setMemoryWikiDashboardState(config, { state: "failed" });
api.logger.warn(`memory-wiki: background source sync failed: ${formatErrorMessage(error)}`);
});
};
const getAppConfig = () => {
if (params.getAppConfig) return params.getAppConfig();
if (typeof api.runtime.config?.current === "function") return api.runtime.config.current();
return params.appConfig;
};
const resolveSourceSyncSignal = () => {
const signal = params.resolveSourceSyncSignal?.();
if (params.resolveSourceSyncSignal && !signal) throw new Error("Memory Wiki service is not active.");
return signal;
};
const resolveRequestContext = (requestParams) => {
const signal = resolveSourceSyncSignal();
const appConfig = getAppConfig();
const requestedAgentId = readStringParam(requestParams, "agentId");
const config = params.resolveConfig ? params.resolveConfig(requestedAgentId, appConfig) : resolveMemoryWikiAgentConfig({
config: baseConfig,
appConfig,
...requestedAgentId ? { agentId: requestedAgentId } : {}
});
return {
agentId: config.agentId ?? requestedAgentId ?? (appConfig ? resolveDefaultAgentId(appConfig) : void 0),
appConfig,
config,
signal
};
};
const assertOfficialObsidianCliSupported = (config) => {
if (config.vault.scope === "agent") throw new Error("Official Obsidian CLI actions do not support memory-wiki vault.scope=agent.");
};
api.registerGatewayMethod("wiki.status", async ({ params: requestParams, respond }) => {
try {
const { appConfig, config, signal } = resolveRequestContext(requestParams);
await syncMemoryWikiImportedSources({
config,
appConfig,
...signal ? { signal } : {}
});
respond(true, await resolveMemoryWikiStatus(config, { appConfig }));
} catch (error) {
respondError(respond, error);
}
}, { scope: READ_SCOPE });
api.registerGatewayMethod("wiki.importRuns", async ({ params: requestParams, respond }) => {
try {
const { config } = resolveRequestContext(requestParams);
const limit = readPositiveIntegerParam(requestParams, "limit");
respond(true, await listMemoryWikiImportRuns(config, limit !== void 0 ? { limit } : {}));
} catch (error) {
respondError(respond, error);
}
}, { scope: READ_SCOPE });
api.registerGatewayMethod("wiki.importInsights", async ({ params: requestParams, respond }) => {
try {
const { appConfig, config } = resolveRequestContext(requestParams);
syncImportedSourcesInBackground(config, appConfig);
respond(true, await listMemoryWikiImportInsights(config));
} catch (error) {
respondError(respond, error);
}
}, { scope: READ_SCOPE });
api.registerGatewayMethod("wiki.overview", async ({ params: requestParams, respond }) => {
try {
const { appConfig, config } = resolveRequestContext(requestParams);
syncImportedSourcesInBackground(config, appConfig);
respond(true, await listMemoryWikiOverview(config));
} catch (error) {
respondError(respond, error);
}
}, { scope: READ_SCOPE });
api.registerGatewayMethod("wiki.init", async ({ params: requestParams, respond }) => {
try {
const { config, signal } = resolveRequestContext(requestParams);
respond(true, await initializeMemoryWikiVault(config, signal ? { signal } : void 0));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
api.registerGatewayMethod("wiki.doctor", async ({ params: requestParams, respond }) => {
try {
const { appConfig, config, signal } = resolveRequestContext(requestParams);
await syncMemoryWikiImportedSources({
config,
appConfig,
...signal ? { signal } : {}
});
const status = await resolveMemoryWikiStatus(config, { appConfig });
respond(true, buildMemoryWikiDoctorReport(status));
} catch (error) {
respondError(respond, error);
}
}, { scope: READ_SCOPE });
api.registerGatewayMethod("wiki.compile", async ({ params: requestParams, respond }) => {
try {
const { appConfig, config, signal } = resolveRequestContext(requestParams);
await syncMemoryWikiImportedSources({
config,
appConfig,
...signal ? { signal } : {}
});
respond(true, await compileMemoryWikiVault(config, signal ? { signal } : void 0));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
api.registerGatewayMethod("wiki.ingest", async ({ params: requestParams, respond }) => {
try {
const { config, signal } = resolveRequestContext(requestParams);
const inputPath = readStringParam(requestParams, "inputPath", { required: true });
const title = readStringParam(requestParams, "title");
respond(true, await ingestMemoryWikiSource({
config,
inputPath,
...title ? { title } : {},
...signal ? { signal } : {}
}));
} catch (error) {
respondError(respond, error);
}
}, { scope: LOCAL_FILE_INGEST_SCOPE });
api.registerGatewayMethod("wiki.lint", async ({ params: requestParams, respond }) => {
try {
const { appConfig, config, signal } = resolveRequestContext(requestParams);
await syncMemoryWikiImportedSources({
config,
appConfig,
...signal ? { signal } : {}
});
respond(true, await lintMemoryWikiVault(config, signal ? { signal } : void 0));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
api.registerGatewayMethod("wiki.bridge.import", async ({ params: requestParams, respond }) => {
try {
const { appConfig, config, signal } = resolveRequestContext(requestParams);
respond(true, await syncMemoryWikiImportedSources({
config: {
...config,
vaultMode: "bridge"
},
appConfig,
...signal ? { signal } : {}
}));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
api.registerGatewayMethod("wiki.unsafeLocal.import", async ({ params: requestParams, respond }) => {
try {
const { appConfig, config, signal } = resolveRequestContext(requestParams);
if (config.vault.scope === "agent") throw new Error("Unsafe-local import does not support memory-wiki vault.scope=agent.");
respond(true, await syncMemoryWikiImportedSources({
config: {
...config,
vaultMode: "unsafe-local"
},
appConfig,
...signal ? { signal } : {}
}));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
api.registerGatewayMethod("wiki.search", async ({ params: requestParams, respond }) => {
try {
const { agentId, appConfig, config, signal } = resolveRequestContext(requestParams);
await syncMemoryWikiImportedSources({
config,
appConfig,
...signal ? { signal } : {}
});
const query = readStringParam(requestParams, "query", { required: true });
const maxResults = readPositiveIntegerParam(requestParams, "maxResults");
const searchBackend = readEnumParam(requestParams, "backend", WIKI_SEARCH_BACKENDS);
const searchCorpus = readEnumParam(requestParams, "corpus", WIKI_SEARCH_CORPORA);
const mode = readEnumParam(requestParams, "mode", WIKI_SEARCH_MODES);
respond(true, await searchMemoryWiki({
config,
appConfig,
...agentId ? { agentId } : {},
query,
maxResults,
searchBackend,
searchCorpus,
mode
}));
} catch (error) {
respondError(respond, error);
}
}, { scope: READ_SCOPE });
api.registerGatewayMethod("wiki.apply", async ({ params: requestParams, respond }) => {
try {
const { appConfig, config, signal } = resolveRequestContext(requestParams);
const mutation = normalizeMemoryWikiMutationInput(requestParams);
await syncMemoryWikiImportedSources({
config,
appConfig,
...signal ? { signal } : {}
});
respond(true, await applyMemoryWikiMutation({
config,
mutation,
...signal ? { signal } : {}
}));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
api.registerGatewayMethod("wiki.get", async ({ params: requestParams, respond }) => {
try {
const { agentId, appConfig, config, signal } = resolveRequestContext(requestParams);
await syncMemoryWikiImportedSources({
config,
appConfig,
...signal ? { signal } : {}
});
const lookup = readStringParam(requestParams, "lookup", { required: true });
const fromLine = readPositiveIntegerParam(requestParams, "fromLine");
const lineCount = readPositiveIntegerParam(requestParams, "lineCount");
const searchBackend = readEnumParam(requestParams, "backend", WIKI_SEARCH_BACKENDS);
const searchCorpus = readEnumParam(requestParams, "corpus", WIKI_SEARCH_CORPORA);
respond(true, await getMemoryWikiPage({
config,
appConfig,
...agentId ? { agentId } : {},
lookup,
fromLine,
lineCount,
searchBackend,
searchCorpus
}));
} catch (error) {
respondError(respond, error);
}
}, { scope: READ_SCOPE });
api.registerGatewayMethod("wiki.obsidian.status", async ({ respond }) => {
try {
respond(true, await probeObsidianCli());
} catch (error) {
respondError(respond, error);
}
}, { scope: READ_SCOPE });
api.registerGatewayMethod("wiki.obsidian.search", async ({ params: requestParams, respond }) => {
try {
const { config } = resolveRequestContext(requestParams);
assertOfficialObsidianCliSupported(config);
const query = readStringParam(requestParams, "query", { required: true });
respond(true, await runObsidianSearch({
config,
query
}));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
api.registerGatewayMethod("wiki.obsidian.open", async ({ params: requestParams, respond }) => {
try {
const { config } = resolveRequestContext(requestParams);
assertOfficialObsidianCliSupported(config);
const vaultPath = readStringParam(requestParams, "path", { required: true });
respond(true, await runObsidianOpen({
config,
vaultPath
}));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
api.registerGatewayMethod("wiki.obsidian.command", async ({ params: requestParams, respond }) => {
try {
const { config } = resolveRequestContext(requestParams);
assertOfficialObsidianCliSupported(config);
const id = readStringParam(requestParams, "id", { required: true });
respond(true, await runObsidianCommand({
config,
id
}));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
api.registerGatewayMethod("wiki.obsidian.daily", async ({ params: requestParams, respond }) => {
try {
const { config } = resolveRequestContext(requestParams);
assertOfficialObsidianCliSupported(config);
respond(true, await runObsidianDaily({ config }));
} catch (error) {
respondError(respond, error);
}
}, { scope: WRITE_SCOPE });
}
//#endregion
//#region extensions/memory-wiki/src/prompt-section.ts
const DIGEST_MAX_PAGES = 4;
const DIGEST_MAX_CLAIMS_PER_PAGE = 2;
const DIGEST_MAX_PAGE_TITLE_CHARS = 160;
const DIGEST_MAX_CLAIM_CHARS = 700;
const DIGEST_MAX_PROMPT_CHARS = 2800;
function rankPromptDigestPage(page) {
return (page.contradictions?.length ?? 0) * 6 + (page.questions?.length ?? 0) * 4 + Math.min(page.claimCount ?? 0, 6) * 2 + Math.min(page.topClaims?.length ?? 0, 3);
}
function rankPromptClaimFreshness(level) {
switch (level) {
case "fresh": return 3;
case "aging": return 2;
case "stale": return 1;
default: return 0;
}
}
function sortPromptClaims(claims) {
return [...claims].toSorted((left, right) => {
const leftConfidence = typeof left.confidence === "number" ? left.confidence : -1;
const rightConfidence = typeof right.confidence === "number" ? right.confidence : -1;
if (leftConfidence !== rightConfidence) return rightConfidence - leftConfidence;
const leftFreshness = rankPromptClaimFreshness(left.freshnessLevel);
const rightFreshness = rankPromptClaimFreshness(right.freshnessLevel);
if (leftFreshness !== rightFreshness) return rightFreshness - leftFreshness;
return left.text.localeCompare(right.text);
});
}
function formatPromptClaim(claim) {
const qualifiers = [
claim.status?.trim() ? `status ${claim.status.trim()}` : null,
typeof claim.confidence === "number" ? `confidence ${claim.confidence.toFixed(2)}` : null,
claim.freshnessLevel?.trim() ? `freshness ${claim.freshnessLevel.trim()}` : null
].filter(Boolean);
if (qualifiers.length === 0) return claim.text;
return `${claim.text} (${qualifiers.join(", ")})`;
}
function buildDigestPromptSection(digest) {
if (!digest?.pages?.length) return [];
const selectedPages = [...digest.pages].filter((page) => (page.claimCount ?? 0) > 0 || (page.questions?.length ?? 0) > 0 || (page.contradictions?.length ?? 0) > 0).toSorted((left, right) => {
const leftScore = rankPromptDigestPage(left);
const rightScore = rankPromptDigestPage(right);
if (leftScore !== rightScore) return rightScore - leftScore;
return left.title.localeCompare(right.title);
}).slice(0, DIGEST_MAX_PAGES);
if (selectedPages.length === 0) return [];
const lines = ["## Compiled Wiki Snapshot", `Compiled wiki currently tracks ${digest.claimCount ?? 0} claims across ${selectedPages.length} high-signal pages.`];
lines.push(`Contradiction clusters: ${digest.contradictionCount}.`);
for (const page of selectedPages) {
const details = [
page.kind,
`${page.claimCount} claims`,
(page.questions?.length ?? 0) > 0 ? `${page.questions?.length} open questions` : null,
(page.contradictions?.length ?? 0) > 0 ? `${page.contradictions?.length} contradiction notes` : null
].filter(Boolean);
lines.push(`- ${truncateUtf16Safe(page.title, DIGEST_MAX_PAGE_TITLE_CHARS)}: ${details.join(", ")}`);
for (const claim of sortPromptClaims(page.topClaims ?? []).slice(0, DIGEST_MAX_CLAIMS_PER_PAGE)) lines.push(` - ${truncateUtf16Safe(formatPromptClaim(claim), DIGEST_MAX_CLAIM_CHARS)}`);
}
lines.push("");
return truncateUtf16Safe(lines.join("\n"), DIGEST_MAX_PROMPT_CHARS).split("\n");
}
function buildWikiToolGuidance(availableTools) {
const hasMemorySearch = availableTools.has("memory_search");
const hasMemoryGet = availableTools.has("memory_get");
const hasWikiSearch = availableTools.has("wiki_search");
const hasWikiGet = availableTools.has("wiki_get");
const hasWikiApply = availableTools.has("wiki_apply");
const hasWikiLint = availableTools.has("wiki_lint");
if (!hasMemorySearch && !hasMemoryGet && !hasWikiSearch && !hasWikiGet && !hasWikiApply && !hasWikiLint) return [];
const lines = ["## Compiled Wiki", "Use the wiki when the answer depends on accumulated project knowledge, prior syntheses, entity pages, or source-backed notes that should survive beyond one conversation."];
if (hasMemorySearch) lines.push("Prefer `memory_search` with `corpus=all` for one recall pass across durable memory and the compiled wiki when both are relevant.");
if (hasMemoryGet) lines.push("Use `memory_get` with `corpus=wiki` or `corpus=all` when you already know the page path and want a small excerpt without leaving the shared memory tool flow.");
if (hasWikiSearch && hasWikiGet) lines.push("Workflow: `wiki_search` first, then `wiki_get` for the exact page or imported memory file you need. Use this when you want wiki-specific ranking or provenance details instead of the broader shared memory flow.");
else if (hasWikiSearch) lines.push("Use `wiki_search` before answering from stored knowledge when you want wiki-specific ranking or provenance details.");
else if (hasWikiGet) lines.push("Use `wiki_get` to inspect specific wiki pages or imported memory files by path/id.");
if (hasWikiApply) lines.push("Use `wiki_apply` for narrow synthesis filing and metadata repair instead of rewriting managed markdown blocks by hand.");
if (hasWikiLint) lines.push("After meaningful wiki updates, run `wiki_lint` before trusting the vault.");
lines.push("");
return lines;
}
function createWikiPromptSectionBuilder() {
return ({ availableTools }) => buildWikiToolGuidance(availableTools);
}
function createWikiPromptSectionPreparer(params) {
return async ({ agentId }) => {
if (params.config.vault.scope === "agent" && !agentId) return [];
const config = params.resolveConfig(agentId);
if (!config.context.includeCompiledDigestPrompt) return [];
return buildDigestPromptSection((await loadMemoryWikiCompiledCache(config))?.digest);
};
}
//#endregion
//#region extensions/memory-wiki/src/tool.ts
function formatWikiToolReportPath(config, reportPath) {
const vaultRoot = path.resolve(config.vault.path);
const resolvedReportPath = path.resolve(reportPath);
const relativeReportPath = path.relative(vaultRoot, resolvedReportPath);
if (!relativeReportPath || relativeReportPath.startsWith("..") || path.isAbsolute(relativeReportPath)) return reportPath;
return relativeReportPath.replace(/\\/g, "/");
}
const WikiStatusSchema = Type.Object({}, { additionalProperties: false });
const WikiLintSchema = Type.Object({}, { additionalProperties: false });
const WikiSearchBackendSchema = Type.Union(WIKI_SEARCH_BACKENDS.map((value) => Type.Literal(value)));
const WikiSearchCorpusSchema = Type.Union(WIKI_SEARCH_CORPORA.map((value) => Type.Literal(value)));
const WikiSearchModeSchema = Type.Union(WIKI_SEARCH_MODES.map((value) => Type.Literal(value)));
const WikiSearchSchema = Type.Object({
query: Type.String({ minLength: 1 }),
maxResults: Type.Optional(Type.Integer({ minimum: 1 })),
backend: Type.Optional(WikiSearchBackendSchema),
corpus: Type.Optional(WikiSearchCorpusSchema),
mode: Type.Optional(WikiSearchModeSchema)
}, { additionalProperties: false });
const WikiGetSchema = Type.Object({
lookup: Type.String({ minLength: 1 }),
fromLine: Type.Optional(Type.Integer({ minimum: 1 })),
lineCount: Type.Optional(Type.Integer({ minimum: 1 })),
backend: Type.Optional(WikiSearchBackendSchema),
corpus: Type.Optional(WikiSearchCorpusSchema)
}, { additionalProperties: false });
const WikiClaimEvidenceSchema = Type.Object({
kind: Type.Optional(Type.String({ minLength: 1 })),
sourceId: Type.Optional(Type.String({ minLength: 1 })),
path: Type.Optional(Type.String({ minLength: 1 })),
lines: Type.Optional(Type.String({ minLength: 1 })),
weight: optionalFiniteNumberSchema({ minimum: 0 }),
note: Type.Optional(Type.String({ minLength: 1 })),
confidence: optionalFiniteNumberSchema({
minimum: 0,
maximum: 1
}),
privacyTier: Type.Optional(Type.String({ minLength: 1 })),
updatedAt: Type.Optional(Type.String({ minLength: 1 }))
}, { additionalProperties: false });
const WikiClaimSchema = Type.Object({
id: Type.Optional(Type.String({ minLength: 1 })),
text: Type.String({ minLength: 1 }),
status: Type.Optional(Type.String({ minLength: 1 })),
confidence: optionalFiniteNumberSchema({
minimum: 0,
maximum: 1
}),
evidence: Type.Optional(Type.Array(WikiClaimEvidenceSchema)),
updatedAt: Type.Optional(Type.String({ minLength: 1 }))
}, { additionalProperties: false });
const WikiApplySchema = Type.Object({
op: Type.Union([
Type.Literal("create_synthesis"),
Type.Literal("update_metadata"),
Type.Literal("synthesis"),
Type.Literal("metadata")
]),
title: Type.Optional(Type.String({ minLength: 1 })),
body: Type.Optional(Type.String({ minLength: 1 })),
lookup: Type.Optional(Type.String({ minLength: 1 })),
sourceIds: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
claims: Type.Optional(Type.Array(WikiClaimSchema)),
contradictions: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
questions: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
confidence: Type.Optional(Type.Union([Type.Number({
minimum: 0,
maximum: 1
}), Type.Null()])),
status: Type.Optional(Type.String({ minLength: 1 }))
}, { additionalProperties: false });
async function syncImportedSourcesIfNeeded(config, appConfig, signal) {
await syncMemoryWikiImportedSources({
config,
appConfig,
...signal ? { signal } : {}
});
}
function createWikiStatusTool(config, appConfig, memoryContext = {}) {
return {
name: "wiki_status",
label: "Wiki Status",
description: "Inspect the current memory wiki vault mode, health, and Obsidian CLI availability.",
parameters: WikiStatusSchema,
execute: async () => {
await syncImportedSourcesIfNeeded(config, appConfig, memoryContext.signal);
const status = await resolveMemoryWikiStatus(config, {
appConfig,
callerAgentId: memoryContext.agentId
});
return textResult(renderMemoryWikiStatus(status), status);
}
};
}
function createWikiSearchTool(config, appConfig, memoryContext = {}) {
return {
name: "wiki_search",
label: "Wiki Search",
description: "Search wiki pages and, when shared search is enabled, the active memory corpus by title, path, id, or body text.",
parameters: WikiSearchSchema,
execute: async (_toolCallId, rawParams) => {
const params = rawParams;
await syncImportedSourcesIfNeeded(config, appConfig, memoryContext.signal);
const results = await searchMemoryWiki({
config,
appConfig,
agentId: memoryContext.agentId,
agentSessionKey: memoryContext.agentSessionKey,
sandboxed: memoryContext.sandboxed,
conversationRecall: memoryContext.conversationRecall,
query: params.query,
maxResults: params.maxResults,
...params.backend ? { searchBackend: params.backend } : {},
...params.corpus ? { searchCorpus: params.corpus } : {},
...params.mode ? { mode: params.mode } : {}
});
const text = results.length === 0 ? "No wiki or memory results." : results.map((result, index) => `${index + 1}. ${result.title} (${result.corpus}/${result.kind})\nPath: ${result.path}${typeof result.startLine === "number" && typeof result.endLine === "number" ? `\nLines: ${result.startLine}-${result.endLine}` : ""}${result.provenanceLabel ? `\nProvenance: ${result.provenanceLabel}` : ""}${result.matchedClaimId ? `\nClaim: ${result.matchedClaimId}` : ""}${result.evidenceKinds && result.evidenceKinds.length > 0 ? `\nEvidence: ${result.evidenceKinds.join(", ")}` : ""}\nSnippet: ${result.snippet}`).join("\n\n");
return textResult(text, { results });
}
};
}
function createWikiLintTool(config, appConfig, signal) {
return {
name: "wiki_lint",
label: "Wiki Lint",
description: "Lint the wiki vault and surface structural issues, provenance gaps, contradictions, and open questions.",
parameters: WikiLintSchema,
execute: async () => {
await syncImportedSourcesIfNeeded(config, appConfig, signal);
const result = await lintMemoryWikiVault(config, signal ? { signal } : void 0);
const contradictions = result.issuesByCategory.contradictions.length;
const openQuestions = result.issuesByCategory["open-questions"].length;
const provenance = result.issuesByCategory.provenance.length;
const errors = result.issues.filter((issue) => issue.severity === "error").length;
const warnings = result.issues.filter((issue) => issue.severity === "warning").length;
const reportPath = formatWikiToolReportPath(config, result.reportPath);
const summary = result.issueCount === 0 ? "No wiki lint issues." : [
`Issues: ${result.issueCount} total (${errors} errors, ${warnings} warnings)`,
`Contradictions: ${contradictions}`,
`Open questions: ${openQuestions}`,
`Provenance gaps: ${provenance}`,
`Report: ${reportPath}`
].join("\n");
return textResult(summary, {
issueCount: result.issueCount,
issues: result.issues,
issuesByCategory: result.issuesByCategory,
reportPath
});
}
};
}
function createWikiApplyTool(config, appConfig, signal) {
return {
name: "wiki_apply",
label: "Wiki Apply",
description: "Apply narrow wiki mutations for syntheses and page metadata without freeform markdown surgery.",
parameters: WikiApplySchema,
execute: async (_toolCallId, rawParams) => {
const mutation = normalizeMemoryWikiMutationInput(rawParams);
await syncImportedSourcesIfNeeded(config, appConfig, signal);
const result = await applyMemoryWikiMutation({
config,
mutation,
...signal ? { signal } : {}
});
const action = result.changed ? "Updated" : "No changes for";
const compileSummary = result.compile.updatedFiles.length > 0 ? `Refreshed ${result.compile.updatedFiles.length} index file${result.compile.updatedFiles.length === 1 ? "" : "s"}.` : "Indexes unchanged.";
return textResult(`${action} ${result.pagePath} via ${result.operation}. ${compileSummary}`, result);
}
};
}
function createWikiGetTool(config, appConfig, memoryContext = {}) {
return {
name: "wiki_get",
label: "Wiki Get",
description: "Read a wiki page by id or relative path, or fall back to the active memory corpus when shared search is enabled.",
parameters: WikiGetSchema,
execute: async (_toolCallId, rawParams) => {
const params = asNonArrayRecord(rawParams);
const lookup = typeof params.lookup === "string" ? params.lookup.trim() : "";
if (!lookup) return textResult("wiki_get requires a non-empty `lookup` path or id.", { found: false });
await syncImportedSourcesIfNeeded(config, appConfig, memoryContext.signal);
const result = await getMemoryWikiPage({
config,
appConfig,
agentId: memoryContext.agentId,
agentSessionKey: memoryContext.agentSessionKey,
sandboxed: memoryContext.sandboxed,
conversationRecall: memoryContext.conversationRecall,
lookup,
fromLine: params.fromLine,
lineCount: params.lineCount,
...params.backend ? { searchBackend: params.backend } : {},
...params.corpus ? { searchCorpus: params.corpus } : {}
});
if (!result) return textResult(`Wiki page not found: ${lookup}`, { found: false });
return textResult(result.content, {
found: true,
...result
});
}
};
}
//#endregion
//#region extensions/memory-wiki/index.ts
async function loadConfiguredVaultIdentity(vaultRoot) {
const identity = await loadMemoryWikiValidatedVaultIdentity(vaultRoot);
if (identity.vaultGeneration) return {
vaultGeneration: identity.vaultGeneration,
compiledCachePublicationId: identity.compiledCachePublicationId
};
try {
if (!(await fs.stat(path.join(vaultRoot, ".openclaw-wiki", "log.jsonl"))).isFile()) return null;
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
throw error;
}
return {
vaultGeneration: await ensureMemoryWikiVaultGeneration(vaultRoot),
compiledCachePublicationId: null
};
}
var memory_wiki_default = definePluginEntry({
id: "memory-wiki",
name: "Memory Wiki",
description: "Persistent wiki compiler and Obsidian-friendly knowledge vault for OpenClaw.",
configSchema: memoryWikiConfigSchema,
register(api) {
const config = resolveMemoryWikiConfig(api.pluginConfig);
const getAppConfig = () => api.runtime.config?.current?.() ?? api.config;
const resolveConfig = (agentId, appConfig = getAppConfig()) => resolveMemoryWikiAgentConfig({
config,
appConfig,
agentId
});
const resolveToolContext = (agentId) => {
const appConfig = getAppConfig();
if (config.vault.scope === "agent" && !agentId && resolveMemoryWikiConfiguredAgentIds(appConfig).length > 1) return null;
return {
appConfig,
config: resolveConfig(agentId, appConfig),
...sourceSyncAbortController ? { signal: sourceSyncAbortController.signal } : {}
};
};
configureMemoryWikiSourceSyncStateStore(createMemoryWikiSourceSyncStateStore(api.runtime.state.openKeyedStore));
configureMemoryWikiImportRunStateStore(createMemoryWikiImportRunStateStore(api.runtime.state.openKeyedStore));
const compiledCacheStore = createMemoryWikiCompiledCacheStore(api.runtime.state.openBlobStore, { onReadError(error) {
api.logger.warn(`memory-wiki: compiled cache unavailable: ${String(error)}`);
} });
configureMemoryWikiCompiledCacheStore(compiledCacheStore);
let sourceSyncAbortController;
api.registerService({
id: "memory-wiki-compiled-cache-owner-cleanup",
async start() {
sourceSyncAbortController?.abort();
const abortController = new AbortController();
sourceSyncAbortController = abortController;
try {
const appConfig = getAppConfig();
const activeConfigs = config.vault.scope === "global" ? [resolveConfig(void 0, appConfig)] : resolveMemoryWikiConfiguredAgentIds(appConfig).map((agentId) => resolveConfig(agentId, appConfig));
deactivateMemoryWikiCompiledCacheOwnersExcept(/* @__PURE__ */ new Set());
const preparedOwners = [];
for (const activeConfig of activeConfigs) {
const identity = await loadConfiguredVaultIdentity(activeConfig.vault.path);
if (identity) preparedOwners.push({
config: activeConfig,
identity
});
}
const activeOwnerIds = /* @__PURE__ */ new Set();
for (const { config: activeConfig, identity } of preparedOwners) {
activateMemoryWikiCompiledCacheOwner(activeConfig, identity.vaultGeneration, identity.compiledCachePublicationId);
await reconcileMemoryWikiCompiledCacheOwner(activeConfig, () => loadMemoryWikiValidatedVaultIdentity(activeConfig.vault.path));
activeOwnerIds.add(resolveMemoryWikiCompiledCacheOwnerId(activeConfig));
}
deactivateMemoryWikiCompiledCacheOwnersExcept(activeOwnerIds);
await compiledCacheStore.deleteOwnersExcept(activeOwnerIds);
} catch (error) {
abortController.abort();
if (sourceSyncAbortController === abortController) sourceSyncAbortController = void 0;
deactivateMemoryWikiCompiledCacheOwnersExcept(/* @__PURE__ */ new Set());
throw error;
}
},
async stop() {
sourceSyncAbortController?.abort();
sourceSyncAbortController = void 0;
deactivateMemoryWikiCompiledCacheOwnersExcept(/* @__PURE__ */ new Set());
await waitForMemoryWikiImportedSourceSyncs();
deactivateMemoryWikiCompiledCacheOwnersExcept(/* @__PURE__ */ new Set());
}
});
api.registerMemoryPromptSupplement(createWikiPromptSectionBuilder());
api.registerMemoryPromptPreparation(createWikiPromptSectionPreparer({
config,
resolveConfig
}));
api.registerMemoryCorpusSupplement(createWikiCorpusSupplement({
resolveConfig,
getAppConfig
}));
registerMemoryWikiGatewayMethods({
api,
config,
appConfig: api.config,
getAppConfig,
resolveConfig,
resolveSourceSyncSignal: () => sourceSyncAbortController?.signal
});
api.registerTool((ctx) => {
const resolved = resolveToolContext(ctx.agentId);
return resolved ? createWikiStatusTool(resolved.config, resolved.appConfig, {
agentId: resolved.config.agentId ?? ctx.agentId,
...resolved.signal ? { signal: resolved.signal } : {}
}) : null;
}, { name: "wiki_status" });
for (const [name, createTool] of [["wiki_lint", createWikiLintTool], ["wiki_apply", createWikiApplyTool]]) api.registerTool((ctx) => {
const resolved = resolveToolContext(ctx.agentId);
return resolved ? createTool(resolved.config, resolved.appConfig, resolved.signal) : null;
}, { name });
for (const [name, createTool] of [["wiki_search", createWikiSearchTool], ["wiki_get", createWikiGetTool]]) api.registerTool((ctx) => {
const resolved = resolveToolContext(ctx.agentId);
if (!resolved) return null;
return createTool(resolved.config, resolved.appConfig, {
agentId: resolved.config.agentId ?? ctx.agentId,
agentSessionKey: ctx.sessionKey,
sandboxed: ctx.sandboxed,
conversationRecall: ctx.conversationRecall,
...resolved.signal ? { signal: resolved.signal } : {}
});
}, { name });
api.registerCli(async ({ program }) => {
const { registerWikiCli } = await import("../../cli-CT3jZHeo.js");
registerWikiCli(program, {
config,
resolveConfig,
getAppConfig
});
}, { descriptors: [{
name: "wiki",
description: "Inspect and initialize the memory wiki vault",
hasSubcommands: true
}] });
}
});
//#endregion
export { memory_wiki_default as default };