openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
157 lines (156 loc) • 6.68 kB
JavaScript
import { n as resolveOpenClawPackageRootSync } from "./openclaw-root-CfYY-fyD.js";
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { r as resolveHomeRelativePath } from "./home-dir-BPhrG-aM.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { i as getGatewayPluginMetadataSnapshot } from "./current-plugin-metadata-state-B1UoAr4G.js";
import { a as pluginCacheRealpathSync, c as readPluginCacheFile, n as parsePluginCacheJson, o as pluginCacheStatSync, r as pluginCacheExistsSync, s as readPluginCacheDirectory } from "./plugin-cache-files-DLPF_Tw2.js";
import { i as resolveBundledPluginsDir } from "./bundled-dir-BEfbEeCF.js";
import { a as resolveDefaultPluginExtensionsDir } from "./install-paths-ChnxikBv.js";
import { o as readPersistedInstalledPluginIndexSync } from "./installed-plugin-index-store-MVV6aa7C.js";
import path from "node:path";
//#region src/plugins/manifest-metadata-scan.ts
const PLUGIN_MANIFEST_METADATA_MAX_BYTES = 262144;
const log = createSubsystemLogger("plugins/manifest-metadata-scan");
const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
function listChildPluginDirs(root, rank, startOrder, origin) {
if (!root || !pluginCacheExistsSync(root)) return [];
const dirs = [];
let order = startOrder;
try {
const entries = readPluginCacheDirectory(root).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
for (const entry of entries) if (entry.isDirectory()) dirs.push({
pluginDir: path.join(root, entry.name),
rank,
order: order++,
origin
});
} catch {
return [];
}
return dirs;
}
function readJsonObject(filePath) {
const file = readPluginCacheFile({
rootDir: path.dirname(filePath),
relativePath: path.basename(filePath),
rejectHardlinks: false,
maxBytes: PLUGIN_MANIFEST_METADATA_MAX_BYTES
});
const warn = (message) => {
if (!file.metadataScanWarningEmitted) {
file.metadataScanWarningEmitted = true;
log.warn(message);
}
};
if (!file.ok) {
if (file.failure.reason === "path") return;
if ((pluginCacheStatSync(filePath)?.size ?? 0) > PLUGIN_MANIFEST_METADATA_MAX_BYTES) warn(`Ignoring oversized plugin manifest at ${filePath}: file exceeds the ${PLUGIN_MANIFEST_METADATA_MAX_BYTES}-byte limit`);
else warn(`Ignoring unreadable plugin manifest at ${filePath}: ${formatErrorMessage(file.failure.error ?? file.failure.reason)}`);
return;
}
const result = parsePluginCacheJson(file, { json5: true });
if (!result.ok) {
warn(`Ignoring invalid plugin manifest at ${filePath}: failed to parse plugin manifest: ${formatErrorMessage(result.error)}`);
return;
}
const parsed = result.value;
if (!isRecord(parsed)) {
warn(`Ignoring invalid plugin manifest at ${filePath}: plugin manifest must be an object`);
return;
}
return parsed;
}
function readManifestObject(pluginDir) {
return readJsonObject(path.join(pluginDir, PLUGIN_MANIFEST_FILENAME));
}
function listPersistedIndexPluginDirs(env, startOrder) {
const index = readPersistedInstalledPluginIndexSync({ env });
if (!index) return [];
const dirs = [];
let order = startOrder;
for (const plugin of index.plugins) {
const rootDir = normalizeOptionalString(plugin.rootDir);
if (!rootDir) continue;
dirs.push({
pluginDir: resolveHomeRelativePath(rootDir, { env }),
rank: plugin.origin === "bundled" ? 3 : 1,
order: order++,
origin: normalizeOptionalString(plugin.origin)
});
}
return dirs;
}
function isSourceCheckoutRoot(packageRoot) {
return pluginCacheExistsSync(path.join(packageRoot, "pnpm-workspace.yaml")) && pluginCacheExistsSync(path.join(packageRoot, "src")) && pluginCacheExistsSync(path.join(packageRoot, "extensions"));
}
function resolvePackageRootsForSourceManifestMetadata() {
const roots = [];
for (const params of [{ argv1: process.argv[1] }, { moduleUrl: import.meta.url }]) {
const root = resolveOpenClawPackageRootSync(params);
if (root && !roots.includes(root)) roots.push(root);
}
return roots;
}
function listSourceCheckoutPluginDirs(startOrder) {
const dirs = [];
let order = startOrder;
for (const packageRoot of resolvePackageRootsForSourceManifestMetadata()) {
if (!isSourceCheckoutRoot(packageRoot)) continue;
dirs.push(...listChildPluginDirs(path.join(packageRoot, "extensions"), 3, order, "source"));
order = startOrder + dirs.length;
}
return dirs;
}
function uniqueCandidateDirs(candidates) {
const byPath = /* @__PURE__ */ new Map();
for (const candidate of candidates) {
const key = pluginCacheRealpathSync(candidate.pluginDir) ?? path.resolve(candidate.pluginDir);
const existing = byPath.get(key);
if (!existing || candidate.rank < existing.rank || candidate.order < existing.order) byPath.set(key, candidate);
}
return [...byPath.values()].toSorted((left, right) => left.rank - right.rank || left.order - right.order);
}
/** Lists plugin manifest metadata from installed, bundled, and global plugin roots. */
function listOpenClawPluginManifestMetadata(env = process.env) {
const snapshot = getGatewayPluginMetadataSnapshot();
if (snapshot) return [...snapshot.plugins, ...(snapshot.bundledManifestRegistry?.plugins ?? []).filter((plugin) => !snapshot.byPluginId.has(plugin.id))].flatMap((plugin) => {
const manifest = readManifestObject(plugin.rootDir);
return manifest ? [{
pluginDir: plugin.rootDir,
manifest,
origin: plugin.origin
}] : [];
});
const candidates = [];
let order = 0;
candidates.push(...listPersistedIndexPluginDirs(env, order));
order = candidates.length;
candidates.push(...listChildPluginDirs(resolveBundledPluginsDir(env), 2, order, "bundled"));
order = candidates.length;
candidates.push(...listSourceCheckoutPluginDirs(order));
order = candidates.length;
candidates.push(...listChildPluginDirs(resolveDefaultPluginExtensionsDir(env), 4, order, "global"));
const uniqueCandidates = uniqueCandidateDirs(candidates);
const byManifestId = /* @__PURE__ */ new Map();
const records = [];
for (const candidate of uniqueCandidates) {
const manifest = readManifestObject(candidate.pluginDir);
if (!manifest) continue;
const manifestId = normalizeOptionalString(manifest.id);
if (manifestId) {
const existing = byManifestId.get(manifestId);
if (existing && existing.rank <= candidate.rank) continue;
byManifestId.set(manifestId, candidate);
}
records.push({
pluginDir: candidate.pluginDir,
manifest,
origin: candidate.origin
});
}
return records.slice();
}
//#endregion
export { listOpenClawPluginManifestMetadata as t };