UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

868 lines (858 loc) 26.6 kB
import { j as resolveUserPath, k as resolveConfigDir, w as isRecord } from "./registry-C8pj8ctW.js"; import path from "node:path"; import fs from "node:fs"; import { fileURLToPath } from "node:url"; //#region src/security/scan-paths.ts function isPathInside$1(basePath, candidatePath) { const base = path.resolve(basePath); const candidate = path.resolve(candidatePath); const rel = path.relative(base, candidate); return rel === "" || !rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel); } function safeRealpathSync$1(filePath) { try { return fs.realpathSync(filePath); } catch { return null; } } function isPathInsideWithRealpath(basePath, candidatePath, opts) { if (!isPathInside$1(basePath, candidatePath)) return false; const baseReal = safeRealpathSync$1(basePath); const candidateReal = safeRealpathSync$1(candidatePath); if (!baseReal || !candidateReal) return opts?.requireRealpath !== true; return isPathInside$1(baseReal, candidateReal); } //#endregion //#region src/plugins/slots.ts const DEFAULT_SLOT_BY_KEY = { memory: "memory-core" }; function defaultSlotIdForKey(slotKey) { return DEFAULT_SLOT_BY_KEY[slotKey]; } //#endregion //#region src/plugins/config-state.ts const BUNDLED_ENABLED_BY_DEFAULT = new Set([ "device-pair", "phone-control", "talk-voice" ]); const normalizeList = (value) => { if (!Array.isArray(value)) return []; return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean); }; const normalizeSlotValue = (value) => { if (typeof value !== "string") return; const trimmed = value.trim(); if (!trimmed) return; if (trimmed.toLowerCase() === "none") return null; return trimmed; }; const normalizePluginEntries = (entries) => { if (!entries || typeof entries !== "object" || Array.isArray(entries)) return {}; const normalized = {}; for (const [key, value] of Object.entries(entries)) { if (!key.trim()) continue; if (!value || typeof value !== "object" || Array.isArray(value)) { normalized[key] = {}; continue; } const entry = value; normalized[key] = { enabled: typeof entry.enabled === "boolean" ? entry.enabled : void 0, config: "config" in entry ? entry.config : void 0 }; } return normalized; }; const normalizePluginsConfig = (config) => { const memorySlot = normalizeSlotValue(config?.slots?.memory); return { enabled: config?.enabled !== false, allow: normalizeList(config?.allow), deny: normalizeList(config?.deny), loadPaths: normalizeList(config?.load?.paths), slots: { memory: memorySlot === void 0 ? defaultSlotIdForKey("memory") : memorySlot }, entries: normalizePluginEntries(config?.entries) }; }; const hasExplicitMemorySlot = (plugins) => Boolean(plugins?.slots && Object.prototype.hasOwnProperty.call(plugins.slots, "memory")); const hasExplicitMemoryEntry = (plugins) => Boolean(plugins?.entries && Object.prototype.hasOwnProperty.call(plugins.entries, "memory-core")); const hasExplicitPluginConfig = (plugins) => { if (!plugins) return false; if (typeof plugins.enabled === "boolean") return true; if (Array.isArray(plugins.allow) && plugins.allow.length > 0) return true; if (Array.isArray(plugins.deny) && plugins.deny.length > 0) return true; if (plugins.load?.paths && Array.isArray(plugins.load.paths) && plugins.load.paths.length > 0) return true; if (plugins.slots && Object.keys(plugins.slots).length > 0) return true; if (plugins.entries && Object.keys(plugins.entries).length > 0) return true; return false; }; function applyTestPluginDefaults(cfg, env = process.env) { if (!env.VITEST) return cfg; const plugins = cfg.plugins; if (hasExplicitPluginConfig(plugins)) { if (hasExplicitMemorySlot(plugins) || hasExplicitMemoryEntry(plugins)) return cfg; return { ...cfg, plugins: { ...plugins, slots: { ...plugins?.slots, memory: "none" } } }; } return { ...cfg, plugins: { ...plugins, enabled: false, slots: { ...plugins?.slots, memory: "none" } } }; } function resolveEnableState(id, origin, config) { if (!config.enabled) return { enabled: false, reason: "plugins disabled" }; if (config.deny.includes(id)) return { enabled: false, reason: "blocked by denylist" }; if (config.allow.length > 0 && !config.allow.includes(id)) return { enabled: false, reason: "not in allowlist" }; if (config.slots.memory === id) return { enabled: true }; const entry = config.entries[id]; if (entry?.enabled === true) return { enabled: true }; if (entry?.enabled === false) return { enabled: false, reason: "disabled in config" }; if (origin === "bundled" && BUNDLED_ENABLED_BY_DEFAULT.has(id)) return { enabled: true }; if (origin === "bundled") return { enabled: false, reason: "bundled (disabled by default)" }; return { enabled: true }; } function resolveMemorySlotDecision(params) { if (params.kind !== "memory") return { enabled: true }; if (params.slot === null) return { enabled: false, reason: "memory slot disabled" }; if (typeof params.slot === "string") { if (params.slot === params.id) return { enabled: true, selected: true }; return { enabled: false, reason: `memory slot set to "${params.slot}"` }; } if (params.selectedId && params.selectedId !== params.id) return { enabled: false, reason: `memory slot already filled by "${params.selectedId}"` }; return { enabled: true, selected: true }; } //#endregion //#region src/plugins/bundled-dir.ts function resolveBundledPluginsDir() { const override = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR?.trim(); if (override) return override; try { const execDir = path.dirname(process.execPath); const sibling = path.join(execDir, "extensions"); if (fs.existsSync(sibling)) return sibling; } catch {} try { let cursor = path.dirname(fileURLToPath(import.meta.url)); for (let i = 0; i < 6; i += 1) { const candidate = path.join(cursor, "extensions"); if (fs.existsSync(candidate)) return candidate; const parent = path.dirname(cursor); if (parent === cursor) break; cursor = parent; } } catch {} } //#endregion //#region src/compat/legacy-names.ts const PROJECT_NAME = "openclaw"; const LEGACY_PROJECT_NAMES = []; const MANIFEST_KEY = PROJECT_NAME; const LEGACY_MANIFEST_KEYS = LEGACY_PROJECT_NAMES; //#endregion //#region src/plugins/manifest.ts const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json"; const PLUGIN_MANIFEST_FILENAMES = [PLUGIN_MANIFEST_FILENAME]; function normalizeStringList(value) { if (!Array.isArray(value)) return []; return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean); } function resolvePluginManifestPath(rootDir) { for (const filename of PLUGIN_MANIFEST_FILENAMES) { const candidate = path.join(rootDir, filename); if (fs.existsSync(candidate)) return candidate; } return path.join(rootDir, PLUGIN_MANIFEST_FILENAME); } function loadPluginManifest(rootDir) { const manifestPath = resolvePluginManifestPath(rootDir); if (!fs.existsSync(manifestPath)) return { ok: false, error: `plugin manifest not found: ${manifestPath}`, manifestPath }; let raw; try { raw = JSON.parse(fs.readFileSync(manifestPath, "utf-8")); } catch (err) { return { ok: false, error: `failed to parse plugin manifest: ${String(err)}`, manifestPath }; } if (!isRecord(raw)) return { ok: false, error: "plugin manifest must be an object", manifestPath }; const id = typeof raw.id === "string" ? raw.id.trim() : ""; if (!id) return { ok: false, error: "plugin manifest requires id", manifestPath }; const configSchema = isRecord(raw.configSchema) ? raw.configSchema : null; if (!configSchema) return { ok: false, error: "plugin manifest requires configSchema", manifestPath }; const kind = typeof raw.kind === "string" ? raw.kind : void 0; const name = typeof raw.name === "string" ? raw.name.trim() : void 0; const description = typeof raw.description === "string" ? raw.description.trim() : void 0; const version = typeof raw.version === "string" ? raw.version.trim() : void 0; const channels = normalizeStringList(raw.channels); const providers = normalizeStringList(raw.providers); const skills = normalizeStringList(raw.skills); let uiHints; if (isRecord(raw.uiHints)) uiHints = raw.uiHints; return { ok: true, manifest: { id, configSchema, kind, channels, providers, skills, name, description, version, uiHints }, manifestPath }; } function getPackageManifestMetadata(manifest) { if (!manifest) return; return manifest[MANIFEST_KEY]; } //#endregion //#region src/plugins/path-safety.ts function isPathInside(baseDir, targetPath) { const rel = path.relative(baseDir, targetPath); if (!rel) return true; return !rel.startsWith("..") && !path.isAbsolute(rel); } function safeRealpathSync(targetPath, cache) { const cached = cache?.get(targetPath); if (cached) return cached; try { const resolved = fs.realpathSync(targetPath); cache?.set(targetPath, resolved); return resolved; } catch { return null; } } function safeStatSync(targetPath) { try { return fs.statSync(targetPath); } catch { return null; } } function formatPosixMode(mode) { return (mode & 511).toString(8).padStart(3, "0"); } //#endregion //#region src/plugins/discovery.ts const EXTENSION_EXTS = new Set([ ".ts", ".js", ".mts", ".cts", ".mjs", ".cjs" ]); function currentUid(overrideUid) { if (overrideUid !== void 0) return overrideUid; if (process.platform === "win32") return null; if (typeof process.getuid !== "function") return null; return process.getuid(); } function checkSourceEscapesRoot(params) { const sourceRealPath = safeRealpathSync(params.source); const rootRealPath = safeRealpathSync(params.rootDir); if (!sourceRealPath || !rootRealPath) return null; if (isPathInside(rootRealPath, sourceRealPath)) return null; return { reason: "source_escapes_root", sourcePath: params.source, rootPath: params.rootDir, targetPath: params.source, sourceRealPath, rootRealPath }; } function checkPathStatAndPermissions(params) { if (process.platform === "win32") return null; const pathsToCheck = [params.rootDir, params.source]; const seen = /* @__PURE__ */ new Set(); for (const targetPath of pathsToCheck) { const normalized = path.resolve(targetPath); if (seen.has(normalized)) continue; seen.add(normalized); const stat = safeStatSync(targetPath); if (!stat) return { reason: "path_stat_failed", sourcePath: params.source, rootPath: params.rootDir, targetPath }; const modeBits = stat.mode & 511; if ((modeBits & 2) !== 0) return { reason: "path_world_writable", sourcePath: params.source, rootPath: params.rootDir, targetPath, modeBits }; if (params.origin !== "bundled" && params.uid !== null && typeof stat.uid === "number" && stat.uid !== params.uid && stat.uid !== 0) return { reason: "path_suspicious_ownership", sourcePath: params.source, rootPath: params.rootDir, targetPath, foundUid: stat.uid, expectedUid: params.uid }; } return null; } function findCandidateBlockIssue(params) { const escaped = checkSourceEscapesRoot({ source: params.source, rootDir: params.rootDir }); if (escaped) return escaped; return checkPathStatAndPermissions({ source: params.source, rootDir: params.rootDir, origin: params.origin, uid: currentUid(params.ownershipUid) }); } function formatCandidateBlockMessage(issue) { if (issue.reason === "source_escapes_root") return `blocked plugin candidate: source escapes plugin root (${issue.sourcePath} -> ${issue.sourceRealPath}; root=${issue.rootRealPath})`; if (issue.reason === "path_stat_failed") return `blocked plugin candidate: cannot stat path (${issue.targetPath})`; if (issue.reason === "path_world_writable") return `blocked plugin candidate: world-writable path (${issue.targetPath}, mode=${formatPosixMode(issue.modeBits ?? 0)})`; return `blocked plugin candidate: suspicious ownership (${issue.targetPath}, uid=${issue.foundUid}, expected uid=${issue.expectedUid} or root)`; } function isUnsafePluginCandidate(params) { const issue = findCandidateBlockIssue({ source: params.source, rootDir: params.rootDir, origin: params.origin, ownershipUid: params.ownershipUid }); if (!issue) return false; params.diagnostics.push({ level: "warn", source: issue.targetPath, message: formatCandidateBlockMessage(issue) }); return true; } function isExtensionFile(filePath) { const ext = path.extname(filePath); if (!EXTENSION_EXTS.has(ext)) return false; return !filePath.endsWith(".d.ts"); } function readPackageManifest(dir) { const manifestPath = path.join(dir, "package.json"); if (!fs.existsSync(manifestPath)) return null; try { const raw = fs.readFileSync(manifestPath, "utf-8"); return JSON.parse(raw); } catch { return null; } } function resolvePackageExtensions(manifest) { const raw = getPackageManifestMetadata(manifest)?.extensions; if (!Array.isArray(raw)) return []; return raw.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean); } function deriveIdHint(params) { const base = path.basename(params.filePath, path.extname(params.filePath)); const rawPackageName = params.packageName?.trim(); if (!rawPackageName) return base; const unscoped = rawPackageName.includes("/") ? rawPackageName.split("/").pop() ?? rawPackageName : rawPackageName; if (!params.hasMultipleExtensions) return unscoped; return `${unscoped}/${base}`; } function addCandidate(params) { const resolved = path.resolve(params.source); if (params.seen.has(resolved)) return; const resolvedRoot = path.resolve(params.rootDir); if (isUnsafePluginCandidate({ source: resolved, rootDir: resolvedRoot, origin: params.origin, diagnostics: params.diagnostics, ownershipUid: params.ownershipUid })) return; params.seen.add(resolved); const manifest = params.manifest ?? null; params.candidates.push({ idHint: params.idHint, source: resolved, rootDir: resolvedRoot, origin: params.origin, workspaceDir: params.workspaceDir, packageName: manifest?.name?.trim() || void 0, packageVersion: manifest?.version?.trim() || void 0, packageDescription: manifest?.description?.trim() || void 0, packageDir: params.packageDir, packageManifest: getPackageManifestMetadata(manifest ?? void 0) }); } function resolvePackageEntrySource(params) { const source = path.resolve(params.packageDir, params.entryPath); if (!isPathInsideWithRealpath(params.packageDir, source, { requireRealpath: true })) { params.diagnostics.push({ level: "error", message: `extension entry escapes package directory: ${params.entryPath}`, source: params.sourceLabel }); return null; } return source; } function discoverInDirectory(params) { if (!fs.existsSync(params.dir)) return; let entries = []; try { entries = fs.readdirSync(params.dir, { withFileTypes: true }); } catch (err) { params.diagnostics.push({ level: "warn", message: `failed to read extensions dir: ${params.dir} (${String(err)})`, source: params.dir }); return; } for (const entry of entries) { const fullPath = path.join(params.dir, entry.name); if (entry.isFile()) { if (!isExtensionFile(fullPath)) continue; addCandidate({ candidates: params.candidates, diagnostics: params.diagnostics, seen: params.seen, idHint: path.basename(entry.name, path.extname(entry.name)), source: fullPath, rootDir: path.dirname(fullPath), origin: params.origin, ownershipUid: params.ownershipUid, workspaceDir: params.workspaceDir }); } if (!entry.isDirectory()) continue; const manifest = readPackageManifest(fullPath); const extensions = manifest ? resolvePackageExtensions(manifest) : []; if (extensions.length > 0) { for (const extPath of extensions) { const resolved = resolvePackageEntrySource({ packageDir: fullPath, entryPath: extPath, sourceLabel: fullPath, diagnostics: params.diagnostics }); if (!resolved) continue; addCandidate({ candidates: params.candidates, diagnostics: params.diagnostics, seen: params.seen, idHint: deriveIdHint({ filePath: resolved, packageName: manifest?.name, hasMultipleExtensions: extensions.length > 1 }), source: resolved, rootDir: fullPath, origin: params.origin, ownershipUid: params.ownershipUid, workspaceDir: params.workspaceDir, manifest, packageDir: fullPath }); } continue; } const indexFile = [ "index.ts", "index.js", "index.mjs", "index.cjs" ].map((candidate) => path.join(fullPath, candidate)).find((candidate) => fs.existsSync(candidate)); if (indexFile && isExtensionFile(indexFile)) addCandidate({ candidates: params.candidates, diagnostics: params.diagnostics, seen: params.seen, idHint: entry.name, source: indexFile, rootDir: fullPath, origin: params.origin, ownershipUid: params.ownershipUid, workspaceDir: params.workspaceDir, manifest, packageDir: fullPath }); } } function discoverFromPath(params) { const resolved = resolveUserPath(params.rawPath); if (!fs.existsSync(resolved)) { params.diagnostics.push({ level: "error", message: `plugin path not found: ${resolved}`, source: resolved }); return; } const stat = fs.statSync(resolved); if (stat.isFile()) { if (!isExtensionFile(resolved)) { params.diagnostics.push({ level: "error", message: `plugin path is not a supported file: ${resolved}`, source: resolved }); return; } addCandidate({ candidates: params.candidates, diagnostics: params.diagnostics, seen: params.seen, idHint: path.basename(resolved, path.extname(resolved)), source: resolved, rootDir: path.dirname(resolved), origin: params.origin, ownershipUid: params.ownershipUid, workspaceDir: params.workspaceDir }); return; } if (stat.isDirectory()) { const manifest = readPackageManifest(resolved); const extensions = manifest ? resolvePackageExtensions(manifest) : []; if (extensions.length > 0) { for (const extPath of extensions) { const source = resolvePackageEntrySource({ packageDir: resolved, entryPath: extPath, sourceLabel: resolved, diagnostics: params.diagnostics }); if (!source) continue; addCandidate({ candidates: params.candidates, diagnostics: params.diagnostics, seen: params.seen, idHint: deriveIdHint({ filePath: source, packageName: manifest?.name, hasMultipleExtensions: extensions.length > 1 }), source, rootDir: resolved, origin: params.origin, ownershipUid: params.ownershipUid, workspaceDir: params.workspaceDir, manifest, packageDir: resolved }); } return; } const indexFile = [ "index.ts", "index.js", "index.mjs", "index.cjs" ].map((candidate) => path.join(resolved, candidate)).find((candidate) => fs.existsSync(candidate)); if (indexFile && isExtensionFile(indexFile)) { addCandidate({ candidates: params.candidates, diagnostics: params.diagnostics, seen: params.seen, idHint: path.basename(resolved), source: indexFile, rootDir: resolved, origin: params.origin, ownershipUid: params.ownershipUid, workspaceDir: params.workspaceDir, manifest, packageDir: resolved }); return; } discoverInDirectory({ dir: resolved, origin: params.origin, ownershipUid: params.ownershipUid, workspaceDir: params.workspaceDir, candidates: params.candidates, diagnostics: params.diagnostics, seen: params.seen }); return; } } function discoverOpenClawPlugins(params) { const candidates = []; const diagnostics = []; const seen = /* @__PURE__ */ new Set(); const workspaceDir = params.workspaceDir?.trim(); const extra = params.extraPaths ?? []; for (const extraPath of extra) { if (typeof extraPath !== "string") continue; const trimmed = extraPath.trim(); if (!trimmed) continue; discoverFromPath({ rawPath: trimmed, origin: "config", ownershipUid: params.ownershipUid, workspaceDir: workspaceDir?.trim() || void 0, candidates, diagnostics, seen }); } if (workspaceDir) { const workspaceRoot = resolveUserPath(workspaceDir); const workspaceExtDirs = [path.join(workspaceRoot, ".openclaw", "extensions")]; for (const dir of workspaceExtDirs) discoverInDirectory({ dir, origin: "workspace", ownershipUid: params.ownershipUid, workspaceDir: workspaceRoot, candidates, diagnostics, seen }); } discoverInDirectory({ dir: path.join(resolveConfigDir(), "extensions"), origin: "global", ownershipUid: params.ownershipUid, candidates, diagnostics, seen }); const bundledDir = resolveBundledPluginsDir(); if (bundledDir) discoverInDirectory({ dir: bundledDir, origin: "bundled", ownershipUid: params.ownershipUid, candidates, diagnostics, seen }); return { candidates, diagnostics }; } //#endregion //#region src/plugins/manifest-registry.ts const PLUGIN_ORIGIN_RANK = { config: 0, workspace: 1, global: 2, bundled: 3 }; const registryCache = /* @__PURE__ */ new Map(); const DEFAULT_MANIFEST_CACHE_MS = 200; function resolveManifestCacheMs(env) { const raw = env.OPENCLAW_PLUGIN_MANIFEST_CACHE_MS?.trim(); if (raw === "" || raw === "0") return 0; if (!raw) return DEFAULT_MANIFEST_CACHE_MS; const parsed = Number.parseInt(raw, 10); if (!Number.isFinite(parsed)) return DEFAULT_MANIFEST_CACHE_MS; return Math.max(0, parsed); } function shouldUseManifestCache(env) { if (env.OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE?.trim()) return false; return resolveManifestCacheMs(env) > 0; } function buildCacheKey(params) { const workspaceKey = params.workspaceDir ? resolveUserPath(params.workspaceDir) : ""; const loadPaths = params.plugins.loadPaths.map((p) => resolveUserPath(p)).map((p) => p.trim()).filter(Boolean).toSorted(); return `${workspaceKey}::${JSON.stringify(loadPaths)}`; } function safeStatMtimeMs(filePath) { try { return fs.statSync(filePath).mtimeMs; } catch { return null; } } function normalizeManifestLabel(raw) { const trimmed = raw?.trim(); return trimmed ? trimmed : void 0; } function buildRecord(params) { return { id: params.manifest.id, name: normalizeManifestLabel(params.manifest.name) ?? params.candidate.packageName, description: normalizeManifestLabel(params.manifest.description) ?? params.candidate.packageDescription, version: normalizeManifestLabel(params.manifest.version) ?? params.candidate.packageVersion, kind: params.manifest.kind, channels: params.manifest.channels ?? [], providers: params.manifest.providers ?? [], skills: params.manifest.skills ?? [], origin: params.candidate.origin, workspaceDir: params.candidate.workspaceDir, rootDir: params.candidate.rootDir, source: params.candidate.source, manifestPath: params.manifestPath, schemaCacheKey: params.schemaCacheKey, configSchema: params.configSchema, configUiHints: params.manifest.uiHints }; } function loadPluginManifestRegistry(params) { const normalized = normalizePluginsConfig((params.config ?? {}).plugins); const cacheKey = buildCacheKey({ workspaceDir: params.workspaceDir, plugins: normalized }); const env = params.env ?? process.env; const cacheEnabled = params.cache !== false && shouldUseManifestCache(env); if (cacheEnabled) { const cached = registryCache.get(cacheKey); if (cached && cached.expiresAt > Date.now()) return cached.registry; } const discovery = params.candidates ? { candidates: params.candidates, diagnostics: params.diagnostics ?? [] } : discoverOpenClawPlugins({ workspaceDir: params.workspaceDir, extraPaths: normalized.loadPaths }); const diagnostics = [...discovery.diagnostics]; const candidates = discovery.candidates; const records = []; const seenIds = /* @__PURE__ */ new Map(); const realpathCache = /* @__PURE__ */ new Map(); for (const candidate of candidates) { const manifestRes = loadPluginManifest(candidate.rootDir); if (!manifestRes.ok) { diagnostics.push({ level: "error", message: manifestRes.error, source: manifestRes.manifestPath }); continue; } const manifest = manifestRes.manifest; if (candidate.idHint && candidate.idHint !== manifest.id) diagnostics.push({ level: "warn", pluginId: manifest.id, source: candidate.source, message: `plugin id mismatch (manifest uses "${manifest.id}", entry hints "${candidate.idHint}")` }); const configSchema = manifest.configSchema; const manifestMtime = safeStatMtimeMs(manifestRes.manifestPath); const schemaCacheKey = manifestMtime ? `${manifestRes.manifestPath}:${manifestMtime}` : manifestRes.manifestPath; const existing = seenIds.get(manifest.id); if (existing) { const existingReal = safeRealpathSync(existing.candidate.rootDir, realpathCache); const candidateReal = safeRealpathSync(candidate.rootDir, realpathCache); if (Boolean(existingReal && candidateReal && existingReal === candidateReal)) { if (PLUGIN_ORIGIN_RANK[candidate.origin] < PLUGIN_ORIGIN_RANK[existing.candidate.origin]) { records[existing.recordIndex] = buildRecord({ manifest, candidate, manifestPath: manifestRes.manifestPath, schemaCacheKey, configSchema }); seenIds.set(manifest.id, { candidate, recordIndex: existing.recordIndex }); } continue; } diagnostics.push({ level: "warn", pluginId: manifest.id, source: candidate.source, message: `duplicate plugin id detected; later plugin may be overridden (${candidate.source})` }); } else seenIds.set(manifest.id, { candidate, recordIndex: records.length }); records.push(buildRecord({ manifest, candidate, manifestPath: manifestRes.manifestPath, schemaCacheKey, configSchema })); } const registry = { plugins: records, diagnostics }; if (cacheEnabled) { const ttl = resolveManifestCacheMs(env); if (ttl > 0) registryCache.set(cacheKey, { expiresAt: Date.now() + ttl, registry }); } return registry; } //#endregion export { LEGACY_MANIFEST_KEYS as a, normalizePluginsConfig as c, isPathInside$1 as d, isPathInsideWithRealpath as f, safeStatSync as i, resolveEnableState as l, discoverOpenClawPlugins as n, MANIFEST_KEY as o, isPathInside as r, applyTestPluginDefaults as s, loadPluginManifestRegistry as t, resolveMemorySlotDecision as u };