"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.a = syncSkillsToWorkspace;exports.c = assertSandboxPath;exports.d = resolveSandboxedMediaSource;exports.f = applySkillEnvOverrides;exports.h = safeEqualSecret;exports.i = resolveSkillsPromptForRun;exports.l = resolveSandboxInputPath;exports.m = sanitizeEnvVars;exports.n = buildWorkspaceSkillSnapshot;exports.o = resolvePluginSkillDirs;exports.p = applySkillEnvOverridesFromSnapshot;exports.r = loadWorkspaceSkillEntries;exports.s = assertMediaNotDataUrl;exports.t = buildWorkspaceSkillCommandSpecs;exports.u = resolveSandboxPath;var _runWithConcurrency2ga3CMk = require("./run-with-concurrency-2ga3-CMk.js"); var _configDiiPndBn = require("./config-DiiPndBn.js"); var _loggerU3s76KST = require("./logger-U3s76KST.js"); var _pathAliasGuardsDFv45kR = require("./path-alias-guards-DFv45kR8.js"); var _nodeFs = _interopRequireDefault(require("node:fs")); var _nodePath = _interopRequireDefault(require("node:path")); var _nodeOs = _interopRequireDefault(require("node:os")); var _nodeCrypto = require("node:crypto"); var _json = _interopRequireDefault(require("json5")); var _nodeUrl = require("node:url"); var _piCodingAgent = require("@mariozechner/pi-coding-agent"); var _yaml = _interopRequireDefault(require("yaml"));function _interopRequireDefault(e) {return e && e.__esModule ? e : { default: e };} //#region src/security/secret-equal.ts function safeEqualSecret(provided, expected) { if (typeof provided !== "string" || typeof expected !== "string") return false; const hash = (s) => (0, _nodeCrypto.createHash)("sha256").update(s).digest(); return (0, _nodeCrypto.timingSafeEqual)(hash(provided), hash(expected)); } //#endregion //#region src/shared/config-eval.ts function isTruthy(value) { if (value === void 0 || value === null) return false; if (typeof value === "boolean") return value; if (typeof value === "number") return value !== 0; if (typeof value === "string") return value.trim().length > 0; return true; } function resolveConfigPath(config, pathStr) { const parts = pathStr.split(".").filter(Boolean); let current = config; for (const part of parts) { if (typeof current !== "object" || current === null) return; current = current[part]; } return current; } function isConfigPathTruthyWithDefaults(config, pathStr, defaults) { const value = resolveConfigPath(config, pathStr); if (value === void 0 && pathStr in defaults) return defaults[pathStr] ?? false; return isTruthy(value); } function evaluateRuntimeRequires(params) { const requires = params.requires; if (!requires) return true; const requiredBins = requires.bins ?? []; if (requiredBins.length > 0) for (const bin of requiredBins) { if (params.hasBin(bin)) continue; if (params.hasRemoteBin?.(bin)) continue; return false; } const requiredAnyBins = requires.anyBins ?? []; if (requiredAnyBins.length > 0) { if (!requiredAnyBins.some((bin) => params.hasBin(bin)) && !params.hasAnyRemoteBin?.(requiredAnyBins)) return false; } const requiredEnv = requires.env ?? []; if (requiredEnv.length > 0) { for (const envName of requiredEnv) if (!params.hasEnv(envName)) return false; } const requiredConfig = requires.config ?? []; if (requiredConfig.length > 0) { for (const configPath of requiredConfig) if (!params.isConfigPathTruthy(configPath)) return false; } return true; } function evaluateRuntimeEligibility(params) { const osList = params.os ?? []; const remotePlatforms = params.remotePlatforms ?? []; if (osList.length > 0 && !osList.includes(resolveRuntimePlatform()) && !remotePlatforms.some((platform) => osList.includes(platform))) return false; if (params.always === true) return true; return evaluateRuntimeRequires({ requires: params.requires, hasBin: params.hasBin, hasRemoteBin: params.hasRemoteBin, hasAnyRemoteBin: params.hasAnyRemoteBin, hasEnv: params.hasEnv, isConfigPathTruthy: params.isConfigPathTruthy }); } function resolveRuntimePlatform() { return process.platform; } function windowsPathExtensions() { const raw = process.env.PATHEXT; return ["", ...(raw !== void 0 ? raw.split(";").map((v) => v.trim()) : [ ".EXE", ".CMD", ".BAT", ".COM"]). filter(Boolean)]; } let cachedHasBinaryPath; let cachedHasBinaryPathExt; const hasBinaryCache = /* @__PURE__ */new Map(); function hasBinary(bin) { const pathEnv = process.env.PATH ?? ""; const pathExt = process.platform === "win32" ? process.env.PATHEXT ?? "" : ""; if (cachedHasBinaryPath !== pathEnv || cachedHasBinaryPathExt !== pathExt) { cachedHasBinaryPath = pathEnv; cachedHasBinaryPathExt = pathExt; hasBinaryCache.clear(); } if (hasBinaryCache.has(bin)) return hasBinaryCache.get(bin); const parts = pathEnv.split(_nodePath.default.delimiter).filter(Boolean); const extensions = process.platform === "win32" ? windowsPathExtensions() : [""]; for (const part of parts) for (const ext of extensions) { const candidate = _nodePath.default.join(part, bin + ext); try { _nodeFs.default.accessSync(candidate, _nodeFs.default.constants.X_OK); hasBinaryCache.set(bin, true); return true; } catch {} } hasBinaryCache.set(bin, false); return false; } //#endregion //#region src/infra/npm-registry-spec.ts const EXACT_SEMVER_VERSION_RE = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/; const DIST_TAG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/; function parseRegistryNpmSpecInternal(rawSpec) { const spec = rawSpec.trim(); if (!spec) return { ok: false, error: "missing npm spec" }; if (/\s/.test(spec)) return { ok: false, error: "unsupported npm spec: whitespace is not allowed" }; if (spec.includes("://")) return { ok: false, error: "unsupported npm spec: URLs are not allowed" }; if (spec.includes("#")) return { ok: false, error: "unsupported npm spec: git refs are not allowed" }; if (spec.includes(":")) return { ok: false, error: "unsupported npm spec: protocol specs are not allowed" }; const at = spec.lastIndexOf("@"); const hasSelector = at > 0; const name = hasSelector ? spec.slice(0, at) : spec; const selector = hasSelector ? spec.slice(at + 1) : ""; if (!(name.startsWith("@") ? /^@[a-z0-9][a-z0-9-._~]*\/[a-z0-9][a-z0-9-._~]*$/.test(name) : /^[a-z0-9][a-z0-9-._~]*$/.test(name))) return { ok: false, error: "unsupported npm spec: expected or @ from the npm registry" }; if (!hasSelector) return { ok: true, parsed: { name, raw: spec, selectorKind: "none", selectorIsPrerelease: false } }; if (!selector) return { ok: false, error: "unsupported npm spec: missing version/tag after @" }; if (/[\\/]/.test(selector)) return { ok: false, error: "unsupported npm spec: invalid version/tag" }; const exactVersionMatch = EXACT_SEMVER_VERSION_RE.exec(selector); if (exactVersionMatch) return { ok: true, parsed: { name, raw: spec, selector, selectorKind: "exact-version", selectorIsPrerelease: Boolean(exactVersionMatch[4]) } }; if (!DIST_TAG_RE.test(selector)) return { ok: false, error: "unsupported npm spec: use an exact version or dist-tag (ranges are not allowed)" }; return { ok: true, parsed: { name, raw: spec, selector, selectorKind: "tag", selectorIsPrerelease: false } }; } function validateRegistryNpmSpec(rawSpec) { const parsed = parseRegistryNpmSpecInternal(rawSpec); return parsed.ok ? null : parsed.error; } //#endregion //#region src/markdown/frontmatter.ts function stripQuotes(value) { if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1); return value; } function coerceYamlFrontmatterValue(value) { if (value === null || value === void 0) return; if (typeof value === "string") return { value: value.trim(), kind: "scalar" }; if (typeof value === "number" || typeof value === "boolean") return { value: String(value), kind: "scalar" }; if (typeof value === "object") try { return { value: JSON.stringify(value), kind: "structured" }; } catch { return; } } function parseYamlFrontmatter(block) { try { const parsed = _yaml.default.parse(block, { schema: "core" }); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null; const result = {}; for (const [rawKey, value] of Object.entries(parsed)) { const key = rawKey.trim(); if (!key) continue; const coerced = coerceYamlFrontmatterValue(value); if (!coerced) continue; result[key] = coerced; } return result; } catch { return null; } } function extractMultiLineValue(lines, startIndex) { const valueLines = []; let i = startIndex + 1; while (i < lines.length) { const line = lines[i]; if (line.length > 0 && !line.startsWith(" ") && !line.startsWith(" ")) break; valueLines.push(line); i += 1; } return { value: valueLines.join("\n").trim(), linesConsumed: i - startIndex }; } function parseLineFrontmatter(block) { const result = {}; const lines = block.split("\n"); let i = 0; while (i < lines.length) { const match = lines[i].match(/^([\w-]+):\s*(.*)$/); if (!match) { i += 1; continue; } const key = match[1]; const inlineValue = match[2].trim(); if (!key) { i += 1; continue; } if (!inlineValue && i + 1 < lines.length) { const nextLine = lines[i + 1]; if (nextLine.startsWith(" ") || nextLine.startsWith(" ")) { const { value, linesConsumed } = extractMultiLineValue(lines, i); if (value) result[key] = { value, kind: "multiline", rawInline: inlineValue }; i += linesConsumed; continue; } } const value = stripQuotes(inlineValue); if (value) result[key] = { value, kind: "inline", rawInline: inlineValue }; i += 1; } return result; } function lineFrontmatterToPlain(parsed) { const result = {}; for (const [key, entry] of Object.entries(parsed)) result[key] = entry.value; return result; } function isYamlBlockScalarIndicator(value) { return /^[|>][+-]?(\d+)?[+-]?$/.test(value); } function shouldPreferInlineLineValue(params) { const { lineEntry, yamlValue } = params; if (yamlValue.kind !== "structured") return false; if (lineEntry.kind !== "inline") return false; if (isYamlBlockScalarIndicator(lineEntry.rawInline)) return false; return lineEntry.value.includes(":"); } function extractFrontmatterBlock(content) { const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); if (!normalized.startsWith("---")) return; const endIndex = normalized.indexOf("\n---", 3); if (endIndex === -1) return; return normalized.slice(4, endIndex); } function parseFrontmatterBlock(content) { const block = extractFrontmatterBlock(content); if (!block) return {}; const lineParsed = parseLineFrontmatter(block); const yamlParsed = parseYamlFrontmatter(block); if (yamlParsed === null) return lineFrontmatterToPlain(lineParsed); const merged = {}; for (const [key, yamlValue] of Object.entries(yamlParsed)) { merged[key] = yamlValue.value; const lineEntry = lineParsed[key]; if (!lineEntry) continue; if (shouldPreferInlineLineValue({ lineEntry, yamlValue })) merged[key] = lineEntry.value; } for (const [key, lineEntry] of Object.entries(lineParsed)) if (!(key in merged)) merged[key] = lineEntry.value; return merged; } //#endregion //#region src/shared/frontmatter.ts function normalizeStringList(input) { if (!input) return []; if (Array.isArray(input)) return input.map((value) => String(value).trim()).filter(Boolean); if (typeof input === "string") return input.split(",").map((value) => value.trim()).filter(Boolean); return []; } function getFrontmatterString(frontmatter, key) { const raw = frontmatter[key]; return typeof raw === "string" ? raw : void 0; } function parseFrontmatterBool(value, fallback) { const parsed = (0, _configDiiPndBn.ci)(value); return parsed === void 0 ? fallback : parsed; } function resolveOpenClawManifestBlock(params) { const raw = getFrontmatterString(params.frontmatter, params.key ?? "metadata"); if (!raw) return; try { const parsed = _json.default.parse(raw); if (!parsed || typeof parsed !== "object") return; const manifestKeys = [_configDiiPndBn.ht, ..._configDiiPndBn.mt]; for (const key of manifestKeys) { const candidate = parsed[key]; if (candidate && typeof candidate === "object") return candidate; } return; } catch { return; } } function resolveOpenClawManifestRequires(metadataObj) { const requiresRaw = typeof metadataObj.requires === "object" && metadataObj.requires !== null ? metadataObj.requires : void 0; if (!requiresRaw) return; return { bins: normalizeStringList(requiresRaw.bins), anyBins: normalizeStringList(requiresRaw.anyBins), env: normalizeStringList(requiresRaw.env), config: normalizeStringList(requiresRaw.config) }; } function resolveOpenClawManifestInstall(metadataObj, parseInstallSpec) { return (Array.isArray(metadataObj.install) ? metadataObj.install : []).map((entry) => parseInstallSpec(entry)).filter((entry) => Boolean(entry)); } function resolveOpenClawManifestOs(metadataObj) { return normalizeStringList(metadataObj.os); } function parseOpenClawManifestInstallBase(input, allowedKinds) { if (!input || typeof input !== "object") return; const raw = input; const kind = (typeof raw.kind === "string" ? raw.kind : typeof raw.type === "string" ? raw.type : "").trim().toLowerCase(); if (!allowedKinds.includes(kind)) return; const spec = { raw, kind }; if (typeof raw.id === "string") spec.id = raw.id; if (typeof raw.label === "string") spec.label = raw.label; const bins = normalizeStringList(raw.bins); if (bins.length > 0) spec.bins = bins; return spec; } function applyOpenClawManifestInstallCommonFields(spec, parsed) { if (parsed.id) spec.id = parsed.id; if (parsed.label) spec.label = parsed.label; if (parsed.bins) spec.bins = parsed.bins; return spec; } //#endregion //#region src/agents/skills/frontmatter.ts function parseFrontmatter(content) { return parseFrontmatterBlock(content); } const BREW_FORMULA_PATTERN = /^[A-Za-z0-9][A-Za-z0-9@+._/-]*$/; const GO_MODULE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~+\-/]*(?:@[A-Za-z0-9][A-Za-z0-9._~+\-/]*)?$/; const UV_PACKAGE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._\-[\]=<>!~+,]*$/; function normalizeSafeBrewFormula(raw) { if (typeof raw !== "string") return; const formula = raw.trim(); if (!formula || formula.startsWith("-") || formula.includes("\\") || formula.includes("..")) return; if (!BREW_FORMULA_PATTERN.test(formula)) return; return formula; } function normalizeSafeNpmSpec(raw) { if (typeof raw !== "string") return; const spec = raw.trim(); if (!spec || spec.startsWith("-")) return; if (validateRegistryNpmSpec(spec) !== null) return; return spec; } function normalizeSafeGoModule(raw) { if (typeof raw !== "string") return; const moduleSpec = raw.trim(); if (!moduleSpec || moduleSpec.startsWith("-") || moduleSpec.includes("\\") || moduleSpec.includes("://")) return; if (!GO_MODULE_PATTERN.test(moduleSpec)) return; return moduleSpec; } function normalizeSafeUvPackage(raw) { if (typeof raw !== "string") return; const pkg = raw.trim(); if (!pkg || pkg.startsWith("-") || pkg.includes("\\") || pkg.includes("://")) return; if (!UV_PACKAGE_PATTERN.test(pkg)) return; return pkg; } function normalizeSafeDownloadUrl(raw) { if (typeof raw !== "string") return; const value = raw.trim(); if (!value || /\s/.test(value)) return; try { const parsed = new URL(value); if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return; return parsed.toString(); } catch { return; } } function parseInstallSpec(input) { const parsed = parseOpenClawManifestInstallBase(input, [ "brew", "node", "go", "uv", "download"] ); if (!parsed) return; const { raw } = parsed; const spec = applyOpenClawManifestInstallCommonFields({ kind: parsed.kind }, parsed); const osList = normalizeStringList(raw.os); if (osList.length > 0) spec.os = osList; const formula = normalizeSafeBrewFormula(raw.formula); if (formula) spec.formula = formula; const cask = normalizeSafeBrewFormula(raw.cask); if (!spec.formula && cask) spec.formula = cask; if (spec.kind === "node") { const pkg = normalizeSafeNpmSpec(raw.package); if (pkg) spec.package = pkg; } else if (spec.kind === "uv") { const pkg = normalizeSafeUvPackage(raw.package); if (pkg) spec.package = pkg; } const moduleSpec = normalizeSafeGoModule(raw.module); if (moduleSpec) spec.module = moduleSpec; const downloadUrl = normalizeSafeDownloadUrl(raw.url); if (downloadUrl) spec.url = downloadUrl; if (typeof raw.archive === "string") spec.archive = raw.archive; if (typeof raw.extract === "boolean") spec.extract = raw.extract; if (typeof raw.stripComponents === "number") spec.stripComponents = raw.stripComponents; if (typeof raw.targetDir === "string") spec.targetDir = raw.targetDir; if (spec.kind === "brew" && !spec.formula) return; if (spec.kind === "node" && !spec.package) return; if (spec.kind === "go" && !spec.module) return; if (spec.kind === "uv" && !spec.package) return; if (spec.kind === "download" && !spec.url) return; 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); 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, skillKey: typeof metadataObj.skillKey === "string" ? metadataObj.skillKey : void 0, primaryEnv: typeof metadataObj.primaryEnv === "string" ? metadataObj.primaryEnv : void 0, os: osRaw.length > 0 ? osRaw : void 0, requires, install: install.length > 0 ? install : void 0 }; } function resolveSkillInvocationPolicy(frontmatter) { return { userInvocable: parseFrontmatterBool(getFrontmatterString(frontmatter, "user-invocable"), true), disableModelInvocation: parseFrontmatterBool(getFrontmatterString(frontmatter, "disable-model-invocation"), false) }; } function resolveSkillKey(skill, entry) { return entry?.metadata?.skillKey ?? skill.name; } //#endregion //#region src/agents/skills/config.ts const DEFAULT_CONFIG_VALUES = { "browser.enabled": true, "browser.evaluateEnabled": true }; function isConfigPathTruthy(config, pathStr) { return isConfigPathTruthyWithDefaults(config, pathStr, DEFAULT_CONFIG_VALUES); } function resolveSkillConfig(config, skillKey) { const skills = config?.skills?.entries; if (!skills || typeof skills !== "object") return; const entry = skills[skillKey]; if (!entry || typeof entry !== "object") return; return entry; } function normalizeAllowlist(input) { if (!input) return; if (!Array.isArray(input)) return; const normalized = (0, _runWithConcurrency2ga3CMk.G)(input); return normalized.length > 0 ? normalized : void 0; } const BUNDLED_SOURCES = new Set(["openclaw-bundled"]); function isBundledSkill(entry) { return BUNDLED_SOURCES.has(entry.skill.source); } function isBundledSkillAllowed(entry, allowlist) { if (!allowlist || allowlist.length === 0) return true; if (!isBundledSkill(entry)) return true; const key = resolveSkillKey(entry.skill, entry); return allowlist.includes(key) || allowlist.includes(entry.skill.name); } function shouldIncludeSkill(params) { const { entry, config, eligibility } = params; const skillConfig = resolveSkillConfig(config, resolveSkillKey(entry.skill, entry)); const allowBundled = normalizeAllowlist(config?.skills?.allowBundled); if (skillConfig?.enabled === false) return false; if (!isBundledSkillAllowed(entry, allowBundled)) return false; return evaluateRuntimeEligibility({ os: entry.metadata?.os, remotePlatforms: eligibility?.remote?.platforms, always: entry.metadata?.always, requires: entry.metadata?.requires, hasBin: hasBinary, hasRemoteBin: eligibility?.remote?.hasBin, hasAnyRemoteBin: eligibility?.remote?.hasAnyBin, hasEnv: (envName) => Boolean(process.env[envName] || skillConfig?.env?.[envName] || skillConfig?.apiKey && entry.metadata?.primaryEnv === envName), isConfigPathTruthy: (configPath) => isConfigPathTruthy(config, configPath) }); } //#endregion //#region src/agents/sandbox/sanitize-env-vars.ts const BLOCKED_ENV_VAR_PATTERNS = [ /^ANTHROPIC_API_KEY$/i, /^OPENAI_API_KEY$/i, /^GEMINI_API_KEY$/i, /^OPENROUTER_API_KEY$/i, /^MINIMAX_API_KEY$/i, /^ELEVENLABS_API_KEY$/i, /^SYNTHETIC_API_KEY$/i, /^TELEGRAM_BOT_TOKEN$/i, /^DISCORD_BOT_TOKEN$/i, /^SLACK_(BOT|APP)_TOKEN$/i, /^LINE_CHANNEL_SECRET$/i, /^LINE_CHANNEL_ACCESS_TOKEN$/i, /^OPENCLAW_GATEWAY_(TOKEN|PASSWORD)$/i, /^AWS_(SECRET_ACCESS_KEY|SECRET_KEY|SESSION_TOKEN)$/i, /^(GH|GITHUB)_TOKEN$/i, /^(AZURE|AZURE_OPENAI|COHERE|AI_GATEWAY|OPENROUTER)_API_KEY$/i, /_?(API_KEY|TOKEN|PASSWORD|PRIVATE_KEY|SECRET)$/i]; const ALLOWED_ENV_VAR_PATTERNS = [ /^LANG$/, /^LC_.*$/i, /^PATH$/i, /^HOME$/i, /^USER$/i, /^SHELL$/i, /^TERM$/i, /^TZ$/i, /^NODE_ENV$/i]; function validateEnvVarValue(value) { if (value.includes("\0")) return "Contains null bytes"; if (value.length > 32768) return "Value exceeds maximum length"; if (/^[A-Za-z0-9+/=]{80,}$/.test(value)) return "Value looks like base64-encoded credential data"; } function matchesAnyPattern$1(value, patterns) { return patterns.some((pattern) => pattern.test(value)); } function sanitizeEnvVars(envVars, options = {}) { const allowed = {}; const blocked = []; const warnings = []; const blockedPatterns = [...BLOCKED_ENV_VAR_PATTERNS, ...(options.customBlockedPatterns ?? [])]; const allowedPatterns = [...ALLOWED_ENV_VAR_PATTERNS, ...(options.customAllowedPatterns ?? [])]; for (const [rawKey, value] of Object.entries(envVars)) { const key = rawKey.trim(); if (!key) continue; if (matchesAnyPattern$1(key, blockedPatterns)) { blocked.push(key); continue; } if (options.strictMode && !matchesAnyPattern$1(key, allowedPatterns)) { blocked.push(key); continue; } const warning = validateEnvVarValue(value); if (warning) { if (warning === "Contains null bytes") { blocked.push(key); continue; } warnings.push(`${key}: ${warning}`); } allowed[key] = value; } return { allowed, blocked, warnings }; } //#endregion //#region src/agents/skills/env-overrides.ts const log$1 = (0, _loggerU3s76KST.a)("env-overrides"); /** * Tracks env var keys that are currently injected by skill overrides. * Used by ACP harness spawn to strip skill-injected keys so they don't * leak to child processes (e.g., OPENAI_API_KEY leaking to Codex CLI). * @see https://github.com/openclaw/openclaw/issues/36280 */ const activeSkillEnvEntries = /* @__PURE__ */new Map(); function acquireActiveSkillEnvKey(key, value) { const active = activeSkillEnvEntries.get(key); if (active) { active.count += 1; if (process.env[key] === void 0) process.env[key] = active.value; return true; } if (process.env[key] !== void 0) return false; activeSkillEnvEntries.set(key, { baseline: process.env[key], value, count: 1 }); return true; } function releaseActiveSkillEnvKey(key) { const active = activeSkillEnvEntries.get(key); if (!active) return; active.count -= 1; if (active.count > 0) { if (process.env[key] === void 0) process.env[key] = active.value; return; } activeSkillEnvEntries.delete(key); if (active.baseline === void 0) delete process.env[key];else process.env[key] = active.baseline; } const SKILL_ALWAYS_BLOCKED_ENV_PATTERNS = [/^OPENSSL_CONF$/i]; function matchesAnyPattern(value, patterns) { return patterns.some((pattern) => pattern.test(value)); } function isAlwaysBlockedSkillEnvKey(key) { return (0, _configDiiPndBn.oi)(key) || matchesAnyPattern(key, SKILL_ALWAYS_BLOCKED_ENV_PATTERNS); } function sanitizeSkillEnvOverrides(params) { if (Object.keys(params.overrides).length === 0) return { allowed: {}, blocked: [], warnings: [] }; const result = sanitizeEnvVars(params.overrides); const allowed = {}; const blocked = /* @__PURE__ */new Set(); const warnings = [...result.warnings]; for (const [key, value] of Object.entries(result.allowed)) { if (isAlwaysBlockedSkillEnvKey(key)) { blocked.add(key); continue; } allowed[key] = value; } for (const key of result.blocked) { if (isAlwaysBlockedSkillEnvKey(key) || !params.allowedSensitiveKeys.has(key)) { blocked.add(key); continue; } const value = params.overrides[key]; if (!value) continue; const warning = validateEnvVarValue(value); if (warning) { if (warning === "Contains null bytes") { blocked.add(key); continue; } warnings.push(`${key}: ${warning}`); } allowed[key] = value; } return { allowed, blocked: [...blocked], warnings }; } function applySkillConfigEnvOverrides(params) { const { updates, skillConfig, primaryEnv, requiredEnv, skillKey } = params; const allowedSensitiveKeys = /* @__PURE__ */new Set(); const normalizedPrimaryEnv = primaryEnv?.trim(); if (normalizedPrimaryEnv) allowedSensitiveKeys.add(normalizedPrimaryEnv); for (const envName of requiredEnv ?? []) { const trimmedEnv = envName.trim(); if (trimmedEnv) allowedSensitiveKeys.add(trimmedEnv); } const pendingOverrides = {}; if (skillConfig.env) for (const [rawKey, envValue] of Object.entries(skillConfig.env)) { const envKey = rawKey.trim(); const hasExternallyManagedValue = process.env[envKey] !== void 0 && !activeSkillEnvEntries.has(envKey); if (!envKey || !envValue || hasExternallyManagedValue) continue; pendingOverrides[envKey] = envValue; } const resolvedApiKey = (0, _configDiiPndBn.Xr)({ value: skillConfig.apiKey, path: `skills.entries.${skillKey}.apiKey` }) ?? ""; if (normalizedPrimaryEnv && (process.env[normalizedPrimaryEnv] === void 0 || activeSkillEnvEntries.has(normalizedPrimaryEnv)) && resolvedApiKey) { if (!pendingOverrides[normalizedPrimaryEnv]) pendingOverrides[normalizedPrimaryEnv] = resolvedApiKey; } const sanitized = sanitizeSkillEnvOverrides({ overrides: pendingOverrides, allowedSensitiveKeys }); if (sanitized.blocked.length > 0) log$1.warn(`Blocked skill env overrides for ${skillKey}: ${sanitized.blocked.join(", ")}`); if (sanitized.warnings.length > 0) log$1.warn(`Suspicious skill env overrides for ${skillKey}: ${sanitized.warnings.join(", ")}`); for (const [envKey, envValue] of Object.entries(sanitized.allowed)) { if (!acquireActiveSkillEnvKey(envKey, envValue)) continue; updates.push({ key: envKey }); process.env[envKey] = activeSkillEnvEntries.get(envKey)?.value ?? envValue; } } function createEnvReverter(updates) { return () => { for (const update of updates) releaseActiveSkillEnvKey(update.key); }; } function applySkillEnvOverrides(params) { const { skills, config } = params; const updates = []; for (const entry of skills) { const skillKey = resolveSkillKey(entry.skill, entry); const skillConfig = resolveSkillConfig(config, skillKey); if (!skillConfig) continue; applySkillConfigEnvOverrides({ updates, skillConfig, primaryEnv: entry.metadata?.primaryEnv, requiredEnv: entry.metadata?.requires?.env, skillKey }); } return createEnvReverter(updates); } function applySkillEnvOverridesFromSnapshot(params) { const { snapshot, config } = params; if (!snapshot) return () => {}; const updates = []; for (const skill of snapshot.skills) { const skillConfig = resolveSkillConfig(config, skill.name); if (!skillConfig) continue; applySkillConfigEnvOverrides({ updates, skillConfig, primaryEnv: skill.primaryEnv, requiredEnv: skill.requiredEnv, skillKey: skill.name }); } return createEnvReverter(updates); } //#endregion //#region src/agents/sandbox-paths.ts const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g; const HTTP_URL_RE = /^https?:\/\//i; const DATA_URL_RE = /^data:/i; const SANDBOX_CONTAINER_WORKDIR = "/workspace"; function normalizeUnicodeSpaces(str) { return str.replace(UNICODE_SPACES, " "); } function normalizeAtPrefix(filePath) { return filePath.startsWith("@") ? filePath.slice(1) : filePath; } function expandPath(filePath) { const normalized = normalizeUnicodeSpaces(normalizeAtPrefix(filePath)); if (normalized === "~") return _nodeOs.default.homedir(); if (normalized.startsWith("~/")) return _nodeOs.default.homedir() + normalized.slice(1); return normalized; } function resolveToCwd(filePath, cwd) { const expanded = expandPath(filePath); if (_nodePath.default.isAbsolute(expanded)) return expanded; return _nodePath.default.resolve(cwd, expanded); } function resolveSandboxInputPath(filePath, cwd) { return resolveToCwd(filePath, cwd); } function resolveSandboxPath(params) { const resolved = resolveSandboxInputPath(params.filePath, params.cwd); const rootResolved = _nodePath.default.resolve(params.root); const relative = _nodePath.default.relative(rootResolved, resolved); if (!relative || relative === "") return { resolved, relative: "" }; if (relative.startsWith("..") || _nodePath.default.isAbsolute(relative)) throw new Error(`Path escapes sandbox root (${shortPath(rootResolved)}): ${params.filePath}`); return { resolved, relative }; } async function assertSandboxPath(params) { const resolved = resolveSandboxPath(params); const policy = { allowFinalSymlinkForUnlink: params.allowFinalSymlinkForUnlink, allowFinalHardlinkForUnlink: params.allowFinalHardlinkForUnlink }; await (0, _pathAliasGuardsDFv45kR.n)({ absolutePath: resolved.resolved, rootPath: params.root, boundaryLabel: "sandbox root", policy }); return resolved; } function assertMediaNotDataUrl(media) { const raw = media.trim(); if (DATA_URL_RE.test(raw)) throw new Error("data: URLs are not supported for media. Use buffer instead."); } async function resolveSandboxedMediaSource(params) { const raw = params.media.trim(); if (!raw) return raw; if (HTTP_URL_RE.test(raw)) return raw; let candidate = raw; if (/^file:\/\//i.test(candidate)) { const workspaceMappedFromUrl = mapContainerWorkspaceFileUrl({ fileUrl: candidate, sandboxRoot: params.sandboxRoot }); if (workspaceMappedFromUrl) candidate = workspaceMappedFromUrl;else try { candidate = (0, _nodeUrl.fileURLToPath)(candidate); } catch { throw new Error(`Invalid file:// URL for sandboxed media: ${raw}`); } } const containerWorkspaceMapped = mapContainerWorkspacePath({ candidate, sandboxRoot: params.sandboxRoot }); if (containerWorkspaceMapped) candidate = containerWorkspaceMapped; const tmpMediaPath = await resolveAllowedTmpMediaPath({ candidate, sandboxRoot: params.sandboxRoot }); if (tmpMediaPath) return tmpMediaPath; return (await assertSandboxPath({ filePath: candidate, cwd: params.sandboxRoot, root: params.sandboxRoot })).resolved; } function mapContainerWorkspaceFileUrl(params) { let parsed; try { parsed = new _nodeUrl.URL(params.fileUrl); } catch { return; } if (parsed.protocol !== "file:") return; const normalizedPathname = decodeURIComponent(parsed.pathname).replace(/\\/g, "/"); if (normalizedPathname !== SANDBOX_CONTAINER_WORKDIR && !normalizedPathname.startsWith(`${SANDBOX_CONTAINER_WORKDIR}/`)) return; return mapContainerWorkspacePath({ candidate: normalizedPathname, sandboxRoot: params.sandboxRoot }); } function mapContainerWorkspacePath(params) { const normalized = params.candidate.replace(/\\/g, "/"); if (normalized === SANDBOX_CONTAINER_WORKDIR) return _nodePath.default.resolve(params.sandboxRoot); const prefix = `${SANDBOX_CONTAINER_WORKDIR}/`; if (!normalized.startsWith(prefix)) return; const rel = normalized.slice(prefix.length); if (!rel) return _nodePath.default.resolve(params.sandboxRoot); return _nodePath.default.resolve(params.sandboxRoot, ...rel.split("/").filter(Boolean)); } async function resolveAllowedTmpMediaPath(params) { if (!_nodePath.default.isAbsolute(expandPath(params.candidate))) return; const resolved = _nodePath.default.resolve(resolveSandboxInputPath(params.candidate, params.sandboxRoot)); const openClawTmpDir = _nodePath.default.resolve((0, _loggerU3s76KST.Z)()); if (!(0, _runWithConcurrency2ga3CMk.z)(openClawTmpDir, resolved)) return; await assertNoTmpAliasEscape({ filePath: resolved, tmpRoot: openClawTmpDir }); return resolved; } async function assertNoTmpAliasEscape(params) { await (0, _pathAliasGuardsDFv45kR.n)({ absolutePath: params.filePath, rootPath: params.tmpRoot, boundaryLabel: "tmp root" }); } function shortPath(value) { if (value.startsWith(_nodeOs.default.homedir())) return `~${value.slice(_nodeOs.default.homedir().length)}`; return value; } //#endregion //#region src/agents/skills/bundled-dir.ts function looksLikeSkillsDir(dir) { try { const entries = _nodeFs.default.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.name.startsWith(".")) continue; const fullPath = _nodePath.default.join(dir, entry.name); if (entry.isFile() && entry.name.endsWith(".md")) return true; if (entry.isDirectory()) { if (_nodeFs.default.existsSync(_nodePath.default.join(fullPath, "SKILL.md"))) return true; } } } catch { return false; } return false; } function resolveBundledSkillsDir(opts = {}) { const override = process.env.OPENCLAW_BUNDLED_SKILLS_DIR?.trim(); if (override) return override; try { const execPath = opts.execPath ?? process.execPath; const execDir = _nodePath.default.dirname(execPath); const sibling = _nodePath.default.join(execDir, "skills"); if (_nodeFs.default.existsSync(sibling)) return sibling; } catch {} try { const moduleUrl = opts.moduleUrl ?? "file:///root/.local/share/pnpm/global/5/.pnpm/openclaw@2026.3.8_@napi-rs+canvas@0.1.96_@types+express@5.0.6_hono@4.12.7_node-llama-cpp@3.16.2/node_modules/openclaw/dist/plugin-sdk/skills-BC9BA6b0.js"; const moduleDir = _nodePath.default.dirname((0, _nodeUrl.fileURLToPath)(moduleUrl)); const packageRoot = (0, _runWithConcurrency2ga3CMk.E)({ argv1: opts.argv1 ?? process.argv[1], moduleUrl, cwd: opts.cwd ?? process.cwd() }); if (packageRoot) { const candidate = _nodePath.default.join(packageRoot, "skills"); if (looksLikeSkillsDir(candidate)) return candidate; } let current = moduleDir; for (let depth = 0; depth < 6; depth += 1) { const candidate = _nodePath.default.join(current, "skills"); if (looksLikeSkillsDir(candidate)) return candidate; const next = _nodePath.default.dirname(current); if (next === current) break; current = next; } } catch {} } //#endregion //#region src/agents/skills/plugin-skills.ts const log = (0, _loggerU3s76KST.a)("skills"); function resolvePluginSkillDirs(params) { const workspaceDir = (params.workspaceDir ?? "").trim(); if (!workspaceDir) return []; const registry = (0, _configDiiPndBn.ut)({ workspaceDir, config: params.config }); if (registry.plugins.length === 0) return []; const normalizedPlugins = (0, _configDiiPndBn._t)(params.config?.plugins); const acpEnabled = params.config?.acp?.enabled !== false; const memorySlot = normalizedPlugins.slots.memory; let selectedMemoryPluginId = null; const seen = /* @__PURE__ */new Set(); const resolved = []; for (const record of registry.plugins) { if (!record.skills || record.skills.length === 0) continue; if (!(0, _configDiiPndBn.vt)({ id: record.id, origin: record.origin, config: normalizedPlugins, rootConfig: params.config }).enabled) continue; if (!acpEnabled && record.id === "acpx") continue; const memoryDecision = (0, _configDiiPndBn.yt)({ id: record.id, kind: record.kind, slot: memorySlot, selectedId: selectedMemoryPluginId }); if (!memoryDecision.enabled) continue; if (memoryDecision.selected && record.kind === "memory") selectedMemoryPluginId = record.id; for (const raw of record.skills) { const trimmed = raw.trim(); if (!trimmed) continue; const candidate = _nodePath.default.resolve(record.rootDir, trimmed); if (!_nodeFs.default.existsSync(candidate)) { log.warn(`plugin skill path not found (${record.id}): ${candidate}`); continue; } if (!(0, _configDiiPndBn.Or)(record.rootDir, candidate, { requireRealpath: true })) { log.warn(`plugin skill path escapes plugin root (${record.id}): ${candidate}`); continue; } if (seen.has(candidate)) continue; seen.add(candidate); resolved.push(candidate); } } return resolved; } //#endregion //#region src/agents/skills/serialize.ts const SKILLS_SYNC_QUEUE = /* @__PURE__ */new Map(); async function serializeByKey(key, task) { const next = (SKILLS_SYNC_QUEUE.get(key) ?? Promise.resolve()).then(task, task); SKILLS_SYNC_QUEUE.set(key, next); try { return await next; } finally { if (SKILLS_SYNC_QUEUE.get(key) === next) SKILLS_SYNC_QUEUE.delete(key); } } //#endregion //#region src/agents/skills/workspace.ts const fsp = _nodeFs.default.promises; const skillsLogger = (0, _loggerU3s76KST.a)("skills"); const skillCommandDebugOnce = /* @__PURE__ */new Set(); /** * Replace the user's home directory prefix with `~` in skill file paths * to reduce system prompt token usage. Models understand `~` expansion, * and the read tool resolves `~` to the home directory. * * Example: `/Users/alice/.bun/.../skills/github/SKILL.md` * → `~/.bun/.../skills/github/SKILL.md` * * Saves ~5–6 tokens per skill path × N skills ≈ 400–600 tokens total. */ function compactSkillPaths(skills) { const home = _nodeOs.default.homedir(); if (!home) return skills; const prefix = home.endsWith(_nodePath.default.sep) ? home : home + _nodePath.default.sep; return skills.map((s) => ({ ...s, filePath: s.filePath.startsWith(prefix) ? "~/" + s.filePath.slice(prefix.length) : s.filePath })); } function debugSkillCommandOnce(messageKey, message, meta) { if (skillCommandDebugOnce.has(messageKey)) return; skillCommandDebugOnce.add(messageKey); skillsLogger.debug(message, meta); } function filterSkillEntries(entries, config, skillFilter, eligibility) { let filtered = entries.filter((entry) => shouldIncludeSkill({ entry, config, eligibility })); if (skillFilter !== void 0) { const normalized = (0, _runWithConcurrency2ga3CMk.H)(skillFilter) ?? []; const label = normalized.length > 0 ? normalized.join(", ") : "(none)"; skillsLogger.debug(`Applying skill filter: ${label}`); filtered = normalized.length > 0 ? filtered.filter((entry) => normalized.includes(entry.skill.name)) : []; skillsLogger.debug(`After skill filter: ${filtered.map((entry) => entry.skill.name).join(", ") || "(none)"}`); } return filtered; } const SKILL_COMMAND_MAX_LENGTH = 32; const SKILL_COMMAND_FALLBACK = "skill"; const SKILL_COMMAND_DESCRIPTION_MAX_LENGTH = 100; const DEFAULT_MAX_CANDIDATES_PER_ROOT = 300; const DEFAULT_MAX_SKILLS_LOADED_PER_SOURCE = 200; const DEFAULT_MAX_SKILLS_IN_PROMPT = 150; const DEFAULT_MAX_SKILLS_PROMPT_CHARS = 3e4; const DEFAULT_MAX_SKILL_FILE_BYTES = 256e3; function sanitizeSkillCommandName(raw) { return raw.toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").slice(0, SKILL_COMMAND_MAX_LENGTH) || SKILL_COMMAND_FALLBACK; } function resolveUniqueSkillCommandName(base, used) { const normalizedBase = base.toLowerCase(); if (!used.has(normalizedBase)) return base; for (let index = 2; index < 1e3; index += 1) { const suffix = `_${index}`; const maxBaseLength = Math.max(1, SKILL_COMMAND_MAX_LENGTH - suffix.length); const candidate = `${base.slice(0, maxBaseLength)}${suffix}`; const candidateKey = candidate.toLowerCase(); if (!used.has(candidateKey)) return candidate; } return `${base.slice(0, Math.max(1, SKILL_COMMAND_MAX_LENGTH - 2))}_x`; } function resolveSkillsLimits(config) { const limits = config?.skills?.limits; return { maxCandidatesPerRoot: limits?.maxCandidatesPerRoot ?? DEFAULT_MAX_CANDIDATES_PER_ROOT, maxSkillsLoadedPerSource: limits?.maxSkillsLoadedPerSource ?? DEFAULT_MAX_SKILLS_LOADED_PER_SOURCE, maxSkillsInPrompt: limits?.maxSkillsInPrompt ?? DEFAULT_MAX_SKILLS_IN_PROMPT, maxSkillsPromptChars: limits?.maxSkillsPromptChars ?? DEFAULT_MAX_SKILLS_PROMPT_CHARS, maxSkillFileBytes: limits?.maxSkillFileBytes ?? DEFAULT_MAX_SKILL_FILE_BYTES }; } function listChildDirectories(dir) { try { const entries = _nodeFs.default.readdirSync(dir, { withFileTypes: true }); const dirs = []; for (const entry of entries) { if (entry.name.startsWith(".")) continue; if (entry.name === "node_modules") continue; const fullPath = _nodePath.default.join(dir, entry.name); if (entry.isDirectory()) { dirs.push(entry.name); continue; } if (entry.isSymbolicLink()) try { if (_nodeFs.default.statSync(fullPath).isDirectory()) dirs.push(entry.name); } catch {} } return dirs; } catch { return []; } } function tryRealpath(filePath) { try { return _nodeFs.default.realpathSync(filePath); } catch { return null; } } function warnEscapedSkillPath(params) { skillsLogger.warn("Skipping skill path that resolves outside its configured root.", { source: params.source, rootDir: params.rootDir, path: params.candidatePath, realPath: params.candidateRealPath }); } function resolveContainedSkillPath(params) { const candidateRealPath = tryRealpath(params.candidatePath); if (!candidateRealPath) return null; if ((0, _runWithConcurrency2ga3CMk.z)(params.rootRealPath, candidateRealPath)) return candidateRealPath; warnEscapedSkillPath({ source: params.source, rootDir: params.rootDir, candidatePath: _nodePath.default.resolve(params.candidatePath), candidateRealPath }); return null; } function filterLoadedSkillsInsideRoot(params) { return params.skills.filter((skill) => { if (!resolveContainedSkillPath({ source: params.source, rootDir: params.rootDir, rootRealPath: params.rootRealPath, candidatePath: skill.baseDir })) return false; const skillFileRealPath = resolveContainedSkillPath({ source: params.source, rootDir: params.rootDir, rootRealPath: params.rootRealPath, candidatePath: skill.filePath }); return Boolean(skillFileRealPath); }); } function resolveNestedSkillsRoot(dir, opts) { const nested = _nodePath.default.join(dir, "skills"); try { if (!_nodeFs.default.existsSync(nested) || !_nodeFs.default.statSync(nested).isDirectory()) return { baseDir: dir }; } catch { return { baseDir: dir }; } const nestedDirs = listChildDirectories(nested); const scanLimit = Math.max(0, opts?.maxEntriesToScan ?? 100); const toScan = scanLimit === 0 ? [] : nestedDirs.slice(0, Math.min(nestedDirs.length, scanLimit)); for (const name of toScan) { const skillMd = _nodePath.default.join(nested, name, "SKILL.md"); if (_nodeFs.default.existsSync(skillMd)) return { baseDir: nested, note: `Detected nested skills root at ${nested}` }; } return { baseDir: dir }; } function unwrapLoadedSkills(loaded) { if (Array.isArray(loaded)) return loaded; if (loaded && typeof loaded === "object" && "skills" in loaded) { const skills = loaded.skills; if (Array.isArray(skills)) return skills; } return []; } function loadSkillEntries(workspaceDir, opts) { const limits = resolveSkillsLimits(opts?.config); const loadSkills = (params) => { const rootDir = _nodePath.default.resolve(params.dir); const rootRealPath = tryRealpath(rootDir) ?? rootDir; const baseDir = resolveNestedSkillsRoot(params.dir, { maxEntriesToScan: limits.maxCandidatesPerRoot }).baseDir; const baseDirRealPath = resolveContainedSkillPath({ source: params.source, rootDir, rootRealPath, candidatePath: baseDir }); if (!baseDirRealPath) return []; const rootSkillMd = _nodePath.default.join(baseDir, "SKILL.md"); if (_nodeFs.default.existsSync(rootSkillMd)) { const rootSkillRealPath = resolveContainedSkillPath({ source: params.source, rootDir, rootRealPath: baseDirRealPath, candidatePath: rootSkillMd }); if (!rootSkillRealPath) return []; try { const size = _nodeFs.default.statSync(rootSkillRealPath).size; if (size > limits.maxSkillFileBytes) { skillsLogger.warn("Skipping skills root due to oversized SKILL.md.", { dir: baseDir, filePath: rootSkillMd, size, maxSkillFileBytes: limits.maxSkillFileBytes }); return []; } } catch { return []; } return filterLoadedSkillsInsideRoot({ skills: unwrapLoadedSkills((0, _piCodingAgent.loadSkillsFromDir)({ dir: baseDir, source: params.source })), source: params.source, rootDir, rootRealPath: baseDirRealPath }); } const childDirs = listChildDirectories(baseDir); const suspicious = childDirs.length > limits.maxCandidatesPerRoot; const maxCandidates = Math.max(0, limits.maxSkillsLoadedPerSource); const limitedChildren = childDirs.slice().sort().slice(0, maxCandidates); if (suspicious) skillsLogger.warn("Skills root looks suspiciously large, truncating discovery.", { dir: params.dir, baseDir, childDirCount: childDirs.length, maxCandidatesPerRoot: limits.maxCandidatesPerRoot, maxSkillsLoadedPerSource: limits.maxSkillsLoadedPerSource });else if (childDirs.length > maxCandidates) skillsLogger.warn("Skills root has many entries, truncating discovery.", { dir: params.dir, baseDir, childDirCount: childDirs.length, maxSkillsLoadedPerSource: limits.maxSkillsLoadedPerSource }); const loadedSkills = []; for (const name of limitedChildren) { const skillDir = _nodePath.default.join(baseDir, name); if (!resolveContainedSkillPath({ source: params.source, rootDir, rootRealPath: baseDirRealPath, candidatePath: skillDir })) continue; const skillMd = _nodePath.default.join(skillDir, "SKILL.md"); if (!_nodeFs.default.existsSync(skillMd)) continue; const skillMdRealPath = resolveContainedSkillPath({ source: params.source, rootDir, rootRealPath: baseDirRealPath, candidatePath: skillMd }); if (!skillMdRealPath) continue; try { const size = _nodeFs.default.statSync(skillMdRealPath).size; if (size > limits.maxSkillFileBytes) { skillsLogger.warn("Skipping skill due to oversized SKILL.md.", { skill: name, filePath: skillMd, size, maxSkillFileBytes: limits.maxSkillFileBytes }); continue; } } catch { continue; } const loaded = (0, _piCodingAgent.loadSkillsFromDir)({ dir: skillDir, source: params.source }); loadedSkills.push(...filterLoadedSkillsInsideRoot({ skills: unwrapLoadedSkills(loaded), source: params.source, rootDir, rootRealPath: baseDirRealPath })); if (loadedSkills.length >= limits.maxSkillsLoadedPerSource) break; } if (loadedSkills.length > limits.maxSkillsLoadedPerSource) return loadedSkills.slice().sort((a, b) => a.name.localeCompare(b.name)).slice(0, limits.maxSkillsLoadedPerSource); return loadedSkills; }; const managedSkillsDir = opts?.managedSkillsDir ?? _nodePath.default.join(_loggerU3s76KST.p, "skills"); const workspaceSkillsDir = _nodePath.default.resolve(workspaceDir, "skills"); const bundledSkillsDir = opts?.bundledSkillsDir ?? resolveBundledSkillsDir(); const extraDirs = (opts?.config?.skills?.load?.extraDirs ?? []).map((d) => typeof d === "string" ? d.trim() : "").filter(Boolean); const pluginSkillDirs = resolvePluginSkillDirs({ workspaceDir, config: opts?.config }); const mergedExtraDirs = [...extraDirs, ...pluginSkillDirs]; const bundledSkills = bundledSkillsDir ? loadSkills({ dir: bundledSkillsDir, source: "openclaw-bundled" }) : []; const extraSkills = mergedExtraDirs.flatMap((dir) => { return loadSkills({ dir: (0, _loggerU3s76KST.D)(dir), source: "openclaw-extra" }); }); const managedSkills = loadSkills({ dir: managedSkillsDir, source: "openclaw-managed" }); const personalAgentsSkills = loadSkills({ dir: _nodePath.default.resolve(_nodeOs.default.homedir(), ".agents", "skills"), source: "agents-skills-personal" }); const projectAgentsSkills = loadSkills({ dir: _nodePath.default.resolve(workspaceDir, ".agents", "skills"), source: "agents-skills-project" }); const workspaceSkills = loadSkills({ dir: workspaceSkillsDir, source: "openclaw-workspace" }); const merged = /* @__PURE__ */new Map(); for (const skill of extraSkills) merged.set(skill.name, skill); for (const skill of bundledSkills) merged.set(skill.name, skill); for (const skill of managedSkills) merged.set(skill.name, skill); for (const skill of personalAgentsSkills) merged.set(skill.name, skill); for (const skill of projectAgentsSkills) merged.set(skill.name, skill); for (const skill of workspaceSkills) merged.set(skill.name, skill); return Array.from(merged.values()).map((skill) => { let frontmatter = {}; try { frontmatter = parseFrontmatter(_nodeFs.default.readFileSync(skill.filePath, "utf-8")); } catch {} return { skill, frontmatter, metadata: resolveOpenClawMetadata(frontmatter), invocation: resolveSkillInvocationPolicy(frontmatter) }; }); } function applySkillsPromptLimits(params) { const limits = resolveSkillsLimits(params.config); const total = params.skills.length; const byCount = params.skills.slice(0, Math.max(0, limits.maxSkillsInPrompt)); let skillsForPrompt = byCount; let truncated = total > byCount.length; let truncatedReason = truncated ? "count" : null; const fits = (skills) => { return (0, _piCodingAgent.formatSkillsForPrompt)(skills).length <= limits.maxSkillsPromptChars; }; if (!fits(skillsForPrompt)) { let lo = 0; let hi = skillsForPrompt.length; while (lo < hi) { const mid = Math.ceil((lo + hi) / 2); if (fits(skillsForPrompt.slice(0, mid))) lo = mid;else hi = mid - 1; } skillsForPrompt = skillsForPrompt.slice(0, lo); truncated = true; truncatedReason = "chars"; } return { skillsForPrompt, truncated, truncatedReason }; } function buildWorkspaceSkillSnapshot(workspaceDir, opts) { const { eligible, prompt, resolvedSkills } = resolveWorkspaceSkillPromptState(workspaceDir, opts); const skillFilter = (0, _runWithConcurrency2ga3CMk.H)(opts?.skillFilter); return { prompt, skills: eligible.map((entry) => ({ name: entry.skill.name, primaryEnv: entry.metadata?.primaryEnv, requiredEnv: entry.metadata?.requires?.env?.slice() })), ...(skillFilter === void 0 ? {} : { skillFilter }), resolvedSkills, version: opts?.snapshotVersion }; } function buildWorkspaceSkillsPrompt(workspaceDir, opts) { return resolveWorkspaceSkillPromptState(workspaceDir, opts).prompt; } function resolveWorkspaceSkillPromptState(workspaceDir, opts) { const eligible = filterSkillEntries(opts?.entries ?? loadSkillEntries(workspaceDir, opts), opts?.config, opts?.skillFilter, opts?.eligibility); const promptEntries = eligible.filter((entry) => entry.invocation?.disableModelInvocation !== true); const remoteNote = opts?.eligibility?.remote?.note?.trim(); const resolvedSkills = promptEntries.map((entry) => entry.skill); const { skillsForPrompt, truncated } = applySkillsPromptLimits({ skills: resolvedSkills, config: opts?.config }); return { eligible, prompt: [ remoteNote, truncated ? `⚠️ Skills truncated: included ${skillsForPrompt.length} of ${resolvedSkills.length}. Run \`openclaw skills check\` to audit.` : "", (0, _piCodingAgent.formatSkillsForPrompt)(compactSkillPaths(skillsForPrompt))]. filter(Boolean).join("\n"), resolvedSkills }; } function resolveSkillsPromptForRun(params) { const snapshotPrompt = params.skillsSnapshot?.prompt?.trim(); if (snapshotPrompt) return snapshotPrompt; if (params.entries && params.entries.length > 0) { const prompt = buildWorkspaceSkillsPrompt(params.workspaceDir, { entries: params.entries, config: params.config }); return prompt.trim() ? prompt : ""; } return ""; } function loadWorkspaceSkillEntries(workspaceDir, opts) { return loadSkillEntries(workspaceDir, opts); } function resolveUniqueSyncedSkillDirName(base, used) { if (!used.has(base)) { used.add(base); return base; } for (let index = 2; index < 1e4; index += 1) { const candidate = `${base}-${index}`; if (!used.has(candidate)) { used.add(candidate); return candidate; } } let fallbackIndex = 1e4; let fallback = `${base}-${fallbackIndex}`; while (used.has(fallback)) { fallbackIndex += 1; fallback = `${base}-${fallbackIndex}`; } used.add(fallback); return fallback; } function resolveSyncedSkillDestinationPath(params) { const sourceDirName = _nodePath.default.basename(params.entry.skill.baseDir).trim(); if (!sourceDirName || sourceDirName === "." || sourceDirName === "..") return null; return resolveSandboxPath({ filePath: resolveUniqueSyncedSkillDirName(sourceDirName, params.usedDirNames), cwd: params.targetSkillsDir, root: params.targetSkillsDir }).resolved; } async function syncSkillsToWorkspace(params) { const sourceDir = (0, _loggerU3s76KST.D)(params.sourceWorkspaceDir); const targetDir = (0, _loggerU3s76KST.D)(params.targetWorkspaceDir); if (sourceDir === targetDir) return; await serializeByKey(`syncSkills:${targetDir}`, async () => { const targetSkillsDir = _nodePath.default.join(targetDir, "skills"); const entries = loadSkillEntries(sourceDir, { config: params.config, managedSkillsDir: params.managedSkillsDir, bundledSkillsDir: params.bundledSkillsDir }); await fsp.rm(targetSkillsDir, { recursive: true, force: true }); await fsp.mkdir(targetSkillsDir, { recursive: true }); const usedDirNames = /* @__PURE__ */new Set(); for (const entry of entries) { let dest = null; try { dest = resolveSyncedSkillDestinationPath({ targetSkillsDir, entry, usedDirNames }); } catch (error) { const message = error instanceof Error ? error.message : JSON.stringify(error); skillsLogger.warn(`Failed to resolve safe destination for ${entry.skill.name}: ${message}`); continue; } if (!dest) { skillsLogger.warn(`Failed to resolve safe destination for ${entry.skill.name}: invalid source directory name`); continue; } try { await fsp.cp(entry.skill.baseDir, dest, { recursive: true, force: true }); } catch (error) { const message = error instanceof Error ? error.message : JSON.stringify(error); skillsLogger.warn(`Failed to copy ${entry.skill.name} to sandbox: ${message}`); } } }); } function buildWorkspaceSkillCommandSpecs(workspaceDir, opts) { const userInvocable = filterSkillEntries(opts?.entries ?? loadSkillEntries(workspaceDir, opts), opts?.config, opts?.skillFilter, opts?.eligibility).filter((entry) => entry.invocation?.userInvocable !== false); const used = /* @__PURE__ */new Set(); for (const reserved of opts?.reservedNames ?? []) used.add(reserved.toLowerCase()); const specs = []; for (const entry of userInvocable) { const rawName = entry.skill.name; const base = sanitizeSkillCommandName(rawName); if (base !== rawName) debugSkillCommandOnce(`sanitize:${rawName}:${base}`, `Sanitized skill command name "${rawName}" to "/${base}".`, { rawName, sanitized: `/${base}` }); const unique = resolveUniqueSkillCommandName(base, used); if (unique !== base) debugSkillCommandOnce(`dedupe:${rawName}:${unique}`, `De-duplicated skill command name for "${rawName}" to "/${unique}".`, { rawName, deduped: `/${unique}` }); used.add(unique.toLowerCase()); const rawDescription = entry.skill.description?.trim() || rawName; const description = rawDescription.length > SKILL_COMMAND_DESCRIPTION_MAX_LENGTH ? rawDescription.slice(0, SKILL_COMMAND_DESCRIPTION_MAX_LENGTH - 1) + "…" : rawDescription; const dispatch = (() => { const kindRaw = (entry.frontmatter?.["command-dispatch"] ?? entry.frontmatter?.["command_dispatch"] ?? "").trim().toLowerCase(); if (!kindRaw) return; if (kindRaw !== "tool") return; const toolName = (entry.frontmatter?.["command-tool"] ?? entry.frontmatter?.["command_tool"] ?? "").trim(); if (!toolName) { debugSkillCommandOnce(`dispatch:missingTool:${rawName}`, `Skill command "/${unique}" requested tool dispatch but did not provide command-tool. Ignoring dispatch.`, { skillName: rawName, command: unique }); return; } const argModeRaw = (entry.frontmatter?.["command-arg-mode"] ?? entry.frontmatter?.["command_arg_mode"] ?? "").trim().toLowerCase(); if (!(!argModeRaw || argModeRaw === "raw" ? "raw" : null)) debugSkillCommandOnce(`dispatch:badArgMode:${rawName}:${argModeRaw}`, `Skill command "/${unique}" requested tool dispatch but has unknown command-arg-mode. Falling back to raw.`, { skillName: rawName, command: unique, argMode: argModeRaw }); return { kind: "tool", toolName, argMode: "raw" }; })(); specs.push({ name: unique, skillName: rawName, description, ...(dispatch ? { dispatch } : {}) }); } return specs; } //#endregion /* v9-6bd5fbfc504d3a64 */