mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-16 11:41:08 +00:00
359 lines
9.7 KiB
TypeScript
359 lines
9.7 KiB
TypeScript
import type { OpenClawConfig, OpenClawPluginApi } from "../api.js";
|
|
import { applyMemoryWikiMutation, normalizeMemoryWikiMutationInput } from "./apply.js";
|
|
import { compileMemoryWikiVault } from "./compile.js";
|
|
import {
|
|
WIKI_SEARCH_BACKENDS,
|
|
WIKI_SEARCH_CORPORA,
|
|
type ResolvedMemoryWikiConfig,
|
|
} from "./config.js";
|
|
import { ingestMemoryWikiSource } from "./ingest.js";
|
|
import { lintMemoryWikiVault } from "./lint.js";
|
|
import {
|
|
probeObsidianCli,
|
|
runObsidianCommand,
|
|
runObsidianDaily,
|
|
runObsidianOpen,
|
|
runObsidianSearch,
|
|
} from "./obsidian.js";
|
|
import { getMemoryWikiPage, searchMemoryWiki } from "./query.js";
|
|
import { syncMemoryWikiImportedSources } from "./source-sync.js";
|
|
import { buildMemoryWikiDoctorReport, resolveMemoryWikiStatus } from "./status.js";
|
|
import { initializeMemoryWikiVault } from "./vault.js";
|
|
|
|
const READ_SCOPE = "operator.read" as const;
|
|
const WRITE_SCOPE = "operator.write" as const;
|
|
type GatewayMethodContext = Parameters<
|
|
Parameters<OpenClawPluginApi["registerGatewayMethod"]>[1]
|
|
>[0];
|
|
type GatewayRespond = GatewayMethodContext["respond"];
|
|
|
|
function readStringParam(params: Record<string, unknown>, key: string): string | undefined;
|
|
function readStringParam(
|
|
params: Record<string, unknown>,
|
|
key: string,
|
|
options: { required: true },
|
|
): string;
|
|
function readStringParam(
|
|
params: Record<string, unknown>,
|
|
key: string,
|
|
options?: { required?: boolean },
|
|
): string | undefined {
|
|
const value = params[key];
|
|
if (typeof value === "string" && value.trim()) {
|
|
return value.trim();
|
|
}
|
|
if (options?.required) {
|
|
throw new Error(`${key} is required.`);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function readNumberParam(params: Record<string, unknown>, key: string): number | undefined {
|
|
const value = params[key];
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
return value;
|
|
}
|
|
if (typeof value === "string" && value.trim()) {
|
|
const parsed = Number(value);
|
|
if (Number.isFinite(parsed)) {
|
|
return parsed;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function readEnumParam<T extends string>(
|
|
params: Record<string, unknown>,
|
|
key: string,
|
|
allowed: readonly T[],
|
|
): T | undefined {
|
|
const value = readStringParam(params, key);
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
if ((allowed as readonly string[]).includes(value)) {
|
|
return value as T;
|
|
}
|
|
throw new Error(`${key} must be one of: ${allowed.join(", ")}.`);
|
|
}
|
|
|
|
function respondError(respond: GatewayRespond, error: unknown) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
respond(false, undefined, { code: "internal_error", message });
|
|
}
|
|
|
|
async function syncImportedSourcesIfNeeded(
|
|
config: ResolvedMemoryWikiConfig,
|
|
appConfig?: OpenClawConfig,
|
|
) {
|
|
await syncMemoryWikiImportedSources({ config, appConfig });
|
|
}
|
|
|
|
export function registerMemoryWikiGatewayMethods(params: {
|
|
api: OpenClawPluginApi;
|
|
config: ResolvedMemoryWikiConfig;
|
|
appConfig?: OpenClawConfig;
|
|
}) {
|
|
const { api, config, appConfig } = params;
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.status",
|
|
async ({ respond }) => {
|
|
try {
|
|
await syncImportedSourcesIfNeeded(config, appConfig);
|
|
respond(true, await resolveMemoryWikiStatus(config));
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: READ_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.init",
|
|
async ({ respond }) => {
|
|
try {
|
|
respond(true, await initializeMemoryWikiVault(config));
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: WRITE_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.doctor",
|
|
async ({ respond }) => {
|
|
try {
|
|
await syncImportedSourcesIfNeeded(config, appConfig);
|
|
const status = await resolveMemoryWikiStatus(config);
|
|
respond(true, buildMemoryWikiDoctorReport(status));
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: READ_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.compile",
|
|
async ({ respond }) => {
|
|
try {
|
|
await syncImportedSourcesIfNeeded(config, appConfig);
|
|
respond(true, await compileMemoryWikiVault(config));
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: WRITE_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.ingest",
|
|
async ({ params: requestParams, respond }) => {
|
|
try {
|
|
const inputPath = readStringParam(requestParams, "inputPath", { required: true });
|
|
const title = readStringParam(requestParams, "title");
|
|
respond(
|
|
true,
|
|
await ingestMemoryWikiSource({
|
|
config,
|
|
inputPath,
|
|
...(title ? { title } : {}),
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: WRITE_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.lint",
|
|
async ({ respond }) => {
|
|
try {
|
|
await syncImportedSourcesIfNeeded(config, appConfig);
|
|
respond(true, await lintMemoryWikiVault(config));
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: WRITE_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.bridge.import",
|
|
async ({ respond }) => {
|
|
try {
|
|
respond(
|
|
true,
|
|
await syncMemoryWikiImportedSources({
|
|
config: { ...config, vaultMode: "bridge" },
|
|
appConfig,
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: WRITE_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.unsafeLocal.import",
|
|
async ({ respond }) => {
|
|
try {
|
|
respond(
|
|
true,
|
|
await syncMemoryWikiImportedSources({
|
|
config: { ...config, vaultMode: "unsafe-local" },
|
|
appConfig,
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: WRITE_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.search",
|
|
async ({ params: requestParams, respond }) => {
|
|
try {
|
|
await syncImportedSourcesIfNeeded(config, appConfig);
|
|
const query = readStringParam(requestParams, "query", { required: true });
|
|
const maxResults = readNumberParam(requestParams, "maxResults");
|
|
const searchBackend = readEnumParam(requestParams, "backend", WIKI_SEARCH_BACKENDS);
|
|
const searchCorpus = readEnumParam(requestParams, "corpus", WIKI_SEARCH_CORPORA);
|
|
respond(
|
|
true,
|
|
await searchMemoryWiki({
|
|
config,
|
|
appConfig,
|
|
query,
|
|
maxResults,
|
|
searchBackend,
|
|
searchCorpus,
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: READ_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.apply",
|
|
async ({ params: requestParams, respond }) => {
|
|
try {
|
|
await syncImportedSourcesIfNeeded(config, appConfig);
|
|
respond(
|
|
true,
|
|
await applyMemoryWikiMutation({
|
|
config,
|
|
mutation: normalizeMemoryWikiMutationInput(requestParams),
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: WRITE_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.get",
|
|
async ({ params: requestParams, respond }) => {
|
|
try {
|
|
await syncImportedSourcesIfNeeded(config, appConfig);
|
|
const lookup = readStringParam(requestParams, "lookup", { required: true });
|
|
const fromLine = readNumberParam(requestParams, "fromLine");
|
|
const lineCount = readNumberParam(requestParams, "lineCount");
|
|
const searchBackend = readEnumParam(requestParams, "backend", WIKI_SEARCH_BACKENDS);
|
|
const searchCorpus = readEnumParam(requestParams, "corpus", WIKI_SEARCH_CORPORA);
|
|
respond(
|
|
true,
|
|
await getMemoryWikiPage({
|
|
config,
|
|
appConfig,
|
|
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 query = readStringParam(requestParams, "query", { required: true });
|
|
respond(true, await runObsidianSearch({ config, query }));
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: READ_SCOPE },
|
|
);
|
|
|
|
api.registerGatewayMethod(
|
|
"wiki.obsidian.open",
|
|
async ({ params: requestParams, respond }) => {
|
|
try {
|
|
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 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 ({ respond }) => {
|
|
try {
|
|
respond(true, await runObsidianDaily({ config }));
|
|
} catch (error) {
|
|
respondError(respond, error);
|
|
}
|
|
},
|
|
{ scope: WRITE_SCOPE },
|
|
);
|
|
}
|