UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

668 lines (660 loc) 20.2 kB
import { f as resolveConfigDir, h as resolveUserPath } from "./utils-CKSrBNwq.js"; import { fileURLToPath } from "node:url"; import path from "node:path"; import fs from "node:fs"; //#region src/plugins/slots.ts const SLOT_BY_KIND = { memory: "memory" }; const DEFAULT_SLOT_BY_KEY = { memory: "memory-core" }; function slotKeyForPluginKind(kind) { if (!kind) return null; return SLOT_BY_KIND[kind] ?? null; } function defaultSlotIdForKey(slotKey) { return DEFAULT_SLOT_BY_KEY[slotKey]; } function applyExclusiveSlotSelection(params) { const slotKey = slotKeyForPluginKind(params.selectedKind); if (!slotKey) return { config: params.config, warnings: [], changed: false }; const warnings = []; const pluginsConfig = params.config.plugins ?? {}; const prevSlot = pluginsConfig.slots?.[slotKey]; const slots = { ...pluginsConfig.slots, [slotKey]: params.selectedId }; const inferredPrevSlot = prevSlot ?? defaultSlotIdForKey(slotKey); if (inferredPrevSlot && inferredPrevSlot !== params.selectedId) warnings.push(`Exclusive slot "${slotKey}" switched from "${inferredPrevSlot}" to "${params.selectedId}".`); const entries = { ...pluginsConfig.entries }; const disabledIds = []; if (params.registry) for (const plugin of params.registry.plugins) { if (plugin.id === params.selectedId) continue; if (plugin.kind !== params.selectedKind) continue; const entry = entries[plugin.id]; if (!entry || entry.enabled !== false) { entries[plugin.id] = { ...entry, enabled: false }; disabledIds.push(plugin.id); } } if (disabledIds.length > 0) warnings.push(`Disabled other "${slotKey}" slot plugins: ${disabledIds.toSorted().join(", ")}.`); if (!(prevSlot !== params.selectedId || disabledIds.length > 0)) return { config: params.config, warnings: [], changed: false }; return { config: { ...params.config, plugins: { ...pluginsConfig, slots, entries } }, warnings, changed: true }; } //#endregion //#region src/plugins/config-state.ts const BUNDLED_ENABLED_BY_DEFAULT = /* @__PURE__ */ new Set(); 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")); function isTestDefaultMemorySlotDisabled(cfg, env = process.env) { if (!env.VITEST) return false; const plugins = cfg.plugins; if (hasExplicitMemorySlot(plugins) || hasExplicitMemoryEntry(plugins)) return false; return true; } 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 isRecord(value) { return Boolean(value && typeof value === "object" && !Array.isArray(value)); } 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/discovery.ts const EXTENSION_EXTS = new Set([ ".ts", ".js", ".mts", ".cts", ".mjs", ".cjs" ]); 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; params.seen.add(resolved); const manifest = params.manifest ?? null; params.candidates.push({ idHint: params.idHint, source: resolved, rootDir: path.resolve(params.rootDir), 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 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, seen: params.seen, idHint: path.basename(entry.name, path.extname(entry.name)), source: fullPath, rootDir: path.dirname(fullPath), origin: params.origin, 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 = path.resolve(fullPath, extPath); addCandidate({ candidates: params.candidates, seen: params.seen, idHint: deriveIdHint({ filePath: resolved, packageName: manifest?.name, hasMultipleExtensions: extensions.length > 1 }), source: resolved, rootDir: fullPath, origin: params.origin, 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, seen: params.seen, idHint: entry.name, source: indexFile, rootDir: fullPath, origin: params.origin, 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, seen: params.seen, idHint: path.basename(resolved, path.extname(resolved)), source: resolved, rootDir: path.dirname(resolved), origin: params.origin, 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 = path.resolve(resolved, extPath); addCandidate({ candidates: params.candidates, seen: params.seen, idHint: deriveIdHint({ filePath: source, packageName: manifest?.name, hasMultipleExtensions: extensions.length > 1 }), source, rootDir: resolved, origin: params.origin, 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, seen: params.seen, idHint: path.basename(resolved), source: indexFile, rootDir: resolved, origin: params.origin, workspaceDir: params.workspaceDir, manifest, packageDir: resolved }); return; } discoverInDirectory({ dir: resolved, origin: params.origin, 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", 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", workspaceDir: workspaceRoot, candidates, diagnostics, seen }); } discoverInDirectory({ dir: path.join(resolveConfigDir(), "extensions"), origin: "global", candidates, diagnostics, seen }); const bundledDir = resolveBundledPluginsDir(); if (bundledDir) discoverInDirectory({ dir: bundledDir, origin: "bundled", candidates, diagnostics, seen }); return { candidates, diagnostics }; } //#endregion //#region src/plugins/manifest-registry.ts 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) { return `${params.workspaceDir ? resolveUserPath(params.workspaceDir) : ""}::${JSON.stringify(params.plugins)}`; } 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 Set(); 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}")` }); if (seenIds.has(manifest.id)) diagnostics.push({ level: "warn", pluginId: manifest.id, source: candidate.source, message: `duplicate plugin id detected; later plugin may be overridden (${candidate.source})` }); else seenIds.add(manifest.id); const configSchema = manifest.configSchema; const manifestMtime = safeStatMtimeMs(manifestRes.manifestPath); const schemaCacheKey = manifestMtime ? `${manifestRes.manifestPath}:${manifestMtime}` : manifestRes.manifestPath; 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 { MANIFEST_KEY as a, resolveEnableState as c, LEGACY_MANIFEST_KEYS as i, resolveMemorySlotDecision as l, discoverOpenClawPlugins as n, isTestDefaultMemorySlotDisabled as o, loadPluginManifest as r, normalizePluginsConfig as s, loadPluginManifestRegistry as t, applyExclusiveSlotSelection as u };