openclaw/src/agents/skills/plugin-skills.ts
2026-03-01 23:45:41 +00:00

90 lines
2.7 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import type { OpenClawConfig } from "../../config/config.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import {
normalizePluginsConfig,
resolveEffectiveEnableState,
resolveMemorySlotDecision,
} from "../../plugins/config-state.js";
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
import { isPathInsideWithRealpath } from "../../security/scan-paths.js";
const log = createSubsystemLogger("skills");
export function resolvePluginSkillDirs(params: {
workspaceDir: string | undefined;
config?: OpenClawConfig;
}): string[] {
const workspaceDir = (params.workspaceDir ?? "").trim();
if (!workspaceDir) {
return [];
}
const registry = loadPluginManifestRegistry({
workspaceDir,
config: params.config,
});
if (registry.plugins.length === 0) {
return [];
}
const normalizedPlugins = normalizePluginsConfig(params.config?.plugins);
const acpEnabled = params.config?.acp?.enabled !== false;
const memorySlot = normalizedPlugins.slots.memory;
let selectedMemoryPluginId: string | null = null;
const seen = new Set<string>();
const resolved: string[] = [];
for (const record of registry.plugins) {
if (!record.skills || record.skills.length === 0) {
continue;
}
const enableState = resolveEffectiveEnableState({
id: record.id,
origin: record.origin,
config: normalizedPlugins,
rootConfig: params.config,
});
if (!enableState.enabled) {
continue;
}
// ACP router skills should not be attached when ACP is explicitly disabled.
if (!acpEnabled && record.id === "acpx") {
continue;
}
const memoryDecision = resolveMemorySlotDecision({
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 = path.resolve(record.rootDir, trimmed);
if (!fs.existsSync(candidate)) {
log.warn(`plugin skill path not found (${record.id}): ${candidate}`);
continue;
}
if (!isPathInsideWithRealpath(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;
}