openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
155 lines (154 loc) • 5.5 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { o as isRecord } from "./record-coerce-DHZ4bFlT.js";
import { t as parseJsonWithJson5Fallback } from "./parse-json-compat-DvZKmwhP.js";
import { i as readPersistedInstalledPluginIndexSync } from "./installed-plugin-index-store-DyTBte5L.js";
import { fileURLToPath } from "node:url";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
//#region src/plugins/manifest-metadata-scan.ts
const OPENCLAW_PACKAGE_ROOT = fileURLToPath(new URL("../..", import.meta.url));
const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
let manifestMetadataCache;
function resolveUserPath(value, env) {
if (value === "~" || value.startsWith("~/")) {
const home = env.OPENCLAW_HOME ?? env.HOME ?? env.USERPROFILE ?? os.homedir();
return path.join(home, value.slice(2));
}
return path.resolve(value);
}
function resolveStateDir(env) {
const override = normalizeOptionalString(env.OPENCLAW_STATE_DIR);
if (override) return resolveUserPath(override, env);
const home = env.OPENCLAW_HOME ?? env.HOME ?? env.USERPROFILE ?? os.homedir();
return path.join(home, ".openclaw");
}
function areBundledPluginsDisabled(env) {
const value = normalizeOptionalString(env.OPENCLAW_DISABLE_BUNDLED_PLUGINS)?.toLowerCase();
return value === "1" || value === "true";
}
function hasManifestDir(root) {
return Boolean(root && fs.existsSync(root));
}
function resolveBundledPluginRoot(env) {
if (areBundledPluginsDisabled(env)) return;
const override = normalizeOptionalString(env.OPENCLAW_BUNDLED_PLUGINS_DIR);
if (override) return resolveUserPath(override, env);
return [
path.join(OPENCLAW_PACKAGE_ROOT, "extensions"),
path.join(OPENCLAW_PACKAGE_ROOT, "dist-runtime", "extensions"),
path.join(OPENCLAW_PACKAGE_ROOT, "dist", "extensions")
].find(hasManifestDir);
}
function listChildPluginDirs(root, rank, startOrder, origin) {
if (!root || !fs.existsSync(root)) return [];
const dirs = [];
let order = startOrder;
try {
for (const entry of fs.readdirSync(root, { withFileTypes: true })) if (entry.isDirectory()) dirs.push({
pluginDir: path.join(root, entry.name),
rank,
order: order++,
origin
});
} catch {
return [];
}
return dirs;
}
function readJsonObject(filePath) {
try {
const parsed = parseJsonWithJson5Fallback(fs.readFileSync(filePath, "utf8"));
return isRecord(parsed) ? parsed : void 0;
} catch {
return;
}
}
function readManifestObject(pluginDir) {
return readJsonObject(path.join(pluginDir, PLUGIN_MANIFEST_FILENAME));
}
function manifestFileFingerprint(pluginDir) {
const manifestPath = path.join(pluginDir, PLUGIN_MANIFEST_FILENAME);
try {
const stat = fs.statSync(manifestPath);
return `${manifestPath}:${stat.mtimeMs}:${stat.size}`;
} catch {
return `${manifestPath}:missing`;
}
}
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: resolveUserPath(rootDir, env),
rank: plugin.origin === "bundled" ? 3 : 1,
order: order++,
origin: normalizeOptionalString(plugin.origin)
});
}
return dirs;
}
function resolveComparablePath(filePath) {
try {
return fs.realpathSync(filePath);
} catch {
return path.resolve(filePath);
}
}
function uniqueCandidateDirs(candidates) {
const byPath = /* @__PURE__ */ new Map();
for (const candidate of candidates) {
const key = resolveComparablePath(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 candidates = [];
let order = 0;
candidates.push(...listPersistedIndexPluginDirs(env, order));
order = candidates.length;
candidates.push(...listChildPluginDirs(resolveBundledPluginRoot(env), 2, order, "bundled"));
order = candidates.length;
candidates.push(...listChildPluginDirs(path.join(resolveStateDir(env), "extensions"), 4, order, "global"));
const uniqueCandidates = uniqueCandidateDirs(candidates);
const cacheKey = JSON.stringify(uniqueCandidates.map((candidate) => [
candidate.pluginDir,
candidate.rank,
candidate.order,
candidate.origin ?? "",
manifestFileFingerprint(candidate.pluginDir)
]));
if (manifestMetadataCache?.key === cacheKey) return manifestMetadataCache.records.slice();
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
});
}
manifestMetadataCache = {
key: cacheKey,
records
};
return records;
}
//#endregion
export { listOpenClawPluginManifestMetadata as t };