UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

262 lines (257 loc) 9.69 kB
import { U as CONFIG_DIR, ot as resolveUserPath } from "./entry.js"; import { a as resolveOpenClawManifestBlock, c as resolveOpenClawManifestRequires, d as hasBinary, f as isConfigPathTruthyWithDefaults, i as parseOpenClawManifestInstallBase, l as parseFrontmatterBlock, n as normalizeStringList, o as resolveOpenClawManifestInstall, p as resolveRuntimePlatform, r as parseFrontmatterBool, s as resolveOpenClawManifestOs, t as getFrontmatterString, u as evaluateRuntimeRequires } from "./frontmatter-D-YR-Ghi.js"; import { a as MANIFEST_KEY, r as isPathInsideWithRealpath } from "./scan-paths-B22E7-Cg.js"; import path from "node:path"; import fs from "node:fs"; import { fileURLToPath } from "node:url"; //#region src/hooks/frontmatter.ts function parseFrontmatter(content) { return parseFrontmatterBlock(content); } function parseInstallSpec(input) { const parsed = parseOpenClawManifestInstallBase(input, [ "bundled", "npm", "git" ]); if (!parsed) return; const { raw } = parsed; const spec = { kind: parsed.kind }; if (parsed.id) spec.id = parsed.id; if (parsed.label) spec.label = parsed.label; if (parsed.bins) spec.bins = parsed.bins; if (typeof raw.package === "string") spec.package = raw.package; if (typeof raw.repository === "string") spec.repository = raw.repository; return spec; } function resolveOpenClawMetadata(frontmatter) { const metadataObj = resolveOpenClawManifestBlock({ frontmatter }); if (!metadataObj) return; const requires = resolveOpenClawManifestRequires(metadataObj); const install = resolveOpenClawManifestInstall(metadataObj, parseInstallSpec); const osRaw = resolveOpenClawManifestOs(metadataObj); const eventsRaw = normalizeStringList(metadataObj.events); return { always: typeof metadataObj.always === "boolean" ? metadataObj.always : void 0, emoji: typeof metadataObj.emoji === "string" ? metadataObj.emoji : void 0, homepage: typeof metadataObj.homepage === "string" ? metadataObj.homepage : void 0, hookKey: typeof metadataObj.hookKey === "string" ? metadataObj.hookKey : void 0, export: typeof metadataObj.export === "string" ? metadataObj.export : void 0, os: osRaw.length > 0 ? osRaw : void 0, events: eventsRaw.length > 0 ? eventsRaw : [], requires, install: install.length > 0 ? install : void 0 }; } function resolveHookInvocationPolicy(frontmatter) { return { enabled: parseFrontmatterBool(getFrontmatterString(frontmatter, "enabled"), true) }; } function resolveHookKey(hookName, entry) { return entry?.metadata?.hookKey ?? hookName; } //#endregion //#region src/hooks/config.ts const DEFAULT_CONFIG_VALUES = { "browser.enabled": true, "browser.evaluateEnabled": true, "workspace.dir": true }; function isConfigPathTruthy(config, pathStr) { return isConfigPathTruthyWithDefaults(config, pathStr, DEFAULT_CONFIG_VALUES); } function resolveHookConfig(config, hookKey) { const hooks = config?.hooks?.internal?.entries; if (!hooks || typeof hooks !== "object") return; const entry = hooks[hookKey]; if (!entry || typeof entry !== "object") return; return entry; } function shouldIncludeHook(params) { const { entry, config, eligibility } = params; const hookConfig = resolveHookConfig(config, resolveHookKey(entry.hook.name, entry)); const pluginManaged = entry.hook.source === "openclaw-plugin"; const osList = entry.metadata?.os ?? []; const remotePlatforms = eligibility?.remote?.platforms ?? []; if (!pluginManaged && hookConfig?.enabled === false) return false; if (osList.length > 0 && !osList.includes(resolveRuntimePlatform()) && !remotePlatforms.some((platform) => osList.includes(platform))) return false; if (entry.metadata?.always === true) return true; return evaluateRuntimeRequires({ requires: entry.metadata?.requires, hasBin: hasBinary, hasRemoteBin: eligibility?.remote?.hasBin, hasAnyRemoteBin: eligibility?.remote?.hasAnyBin, hasEnv: (envName) => Boolean(process.env[envName] || hookConfig?.env?.[envName]), isConfigPathTruthy: (configPath) => isConfigPathTruthy(config, configPath) }); } //#endregion //#region src/hooks/bundled-dir.ts function resolveBundledHooksDir() { const override = process.env.OPENCLAW_BUNDLED_HOOKS_DIR?.trim(); if (override) return override; try { const execDir = path.dirname(process.execPath); const sibling = path.join(execDir, "hooks", "bundled"); if (fs.existsSync(sibling)) return sibling; } catch {} try { const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const distBundled = path.join(moduleDir, "bundled"); if (fs.existsSync(distBundled)) return distBundled; } catch {} try { const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(moduleDir, "..", ".."); const srcBundled = path.join(root, "src", "hooks", "bundled"); if (fs.existsSync(srcBundled)) return srcBundled; } catch {} } //#endregion //#region src/hooks/workspace.ts function readHookPackageManifest(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 resolvePackageHooks(manifest) { const raw = manifest[MANIFEST_KEY]?.hooks; if (!Array.isArray(raw)) return []; return raw.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean); } function resolveContainedDir(baseDir, targetDir) { const base = path.resolve(baseDir); const resolved = path.resolve(baseDir, targetDir); if (!isPathInsideWithRealpath(base, resolved, { requireRealpath: true })) return null; return resolved; } function loadHookFromDir(params) { const hookMdPath = path.join(params.hookDir, "HOOK.md"); if (!fs.existsSync(hookMdPath)) return null; try { const frontmatter = parseFrontmatter(fs.readFileSync(hookMdPath, "utf-8")); const name = frontmatter.name || params.nameHint || path.basename(params.hookDir); const description = frontmatter.description || ""; const handlerCandidates = [ "handler.ts", "handler.js", "index.ts", "index.js" ]; let handlerPath; for (const candidate of handlerCandidates) { const candidatePath = path.join(params.hookDir, candidate); if (fs.existsSync(candidatePath)) { handlerPath = candidatePath; break; } } if (!handlerPath) { console.warn(`[hooks] Hook "${name}" has HOOK.md but no handler file in ${params.hookDir}`); return null; } return { name, description, source: params.source, pluginId: params.pluginId, filePath: hookMdPath, baseDir: params.hookDir, handlerPath }; } catch (err) { console.warn(`[hooks] Failed to load hook from ${params.hookDir}:`, err); return null; } } /** * Scan a directory for hooks (subdirectories containing HOOK.md) */ function loadHooksFromDir(params) { const { dir, source, pluginId } = params; if (!fs.existsSync(dir)) return []; if (!fs.statSync(dir).isDirectory()) return []; const hooks = []; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory()) continue; const hookDir = path.join(dir, entry.name); const manifest = readHookPackageManifest(hookDir); const packageHooks = manifest ? resolvePackageHooks(manifest) : []; if (packageHooks.length > 0) { for (const hookPath of packageHooks) { const resolvedHookDir = resolveContainedDir(hookDir, hookPath); if (!resolvedHookDir) { console.warn(`[hooks] Ignoring out-of-package hook path "${hookPath}" in ${hookDir} (must be within package directory)`); continue; } const hook = loadHookFromDir({ hookDir: resolvedHookDir, source, pluginId, nameHint: path.basename(resolvedHookDir) }); if (hook) hooks.push(hook); } continue; } const hook = loadHookFromDir({ hookDir, source, pluginId, nameHint: entry.name }); if (hook) hooks.push(hook); } return hooks; } function loadHookEntries(workspaceDir, opts) { const managedHooksDir = opts?.managedHooksDir ?? path.join(CONFIG_DIR, "hooks"); const workspaceHooksDir = path.join(workspaceDir, "hooks"); const bundledHooksDir = opts?.bundledHooksDir ?? resolveBundledHooksDir(); const extraDirs = (opts?.config?.hooks?.internal?.load?.extraDirs ?? []).map((d) => typeof d === "string" ? d.trim() : "").filter(Boolean); const bundledHooks = bundledHooksDir ? loadHooksFromDir({ dir: bundledHooksDir, source: "openclaw-bundled" }) : []; const extraHooks = extraDirs.flatMap((dir) => { return loadHooksFromDir({ dir: resolveUserPath(dir), source: "openclaw-workspace" }); }); const managedHooks = loadHooksFromDir({ dir: managedHooksDir, source: "openclaw-managed" }); const workspaceHooks = loadHooksFromDir({ dir: workspaceHooksDir, source: "openclaw-workspace" }); const merged = /* @__PURE__ */ new Map(); for (const hook of extraHooks) merged.set(hook.name, hook); for (const hook of bundledHooks) merged.set(hook.name, hook); for (const hook of managedHooks) merged.set(hook.name, hook); for (const hook of workspaceHooks) merged.set(hook.name, hook); return Array.from(merged.values()).map((hook) => { let frontmatter = {}; try { frontmatter = parseFrontmatter(fs.readFileSync(hook.filePath, "utf-8")); } catch {} return { hook, frontmatter, metadata: resolveOpenClawMetadata(frontmatter), invocation: resolveHookInvocationPolicy(frontmatter) }; }); } function loadWorkspaceHookEntries(workspaceDir, opts) { return loadHookEntries(workspaceDir, opts); } //#endregion export { parseFrontmatter as a, shouldIncludeHook as i, isConfigPathTruthy as n, resolveHookConfig as r, loadWorkspaceHookEntries as t };