fix(agents): resolve plugin skill metadata cold

This commit is contained in:
Vincent Koc
2026-04-25 19:26:16 -07:00
parent 0796a888ae
commit bc24b547d0
4 changed files with 84 additions and 22 deletions

View File

@@ -7,10 +7,12 @@ import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import type { BundleMcpServerConfig } from "../plugins/bundle-mcp.js";
import {
normalizePluginsConfig,
normalizePluginsConfigWithResolver,
resolveEffectivePluginActivationState,
} from "../plugins/config-state.js";
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
} from "../plugins/config-policy.js";
import { loadPluginManifestRegistryForInstalledIndex } from "../plugins/manifest-registry-installed.js";
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
import { loadPluginRegistrySnapshot } from "../plugins/plugin-registry.js";
import { isRecord } from "../utils.js";
import { loadEmbeddedPiMcpConfig } from "./embedded-pi-mcp.js";
@@ -68,6 +70,33 @@ function loadBundleSettingsFile(params: {
}
}
function buildRegistryPluginIdAliases(
registry: PluginManifestRegistry,
): Readonly<Record<string, string>> {
return Object.fromEntries(
registry.plugins
.flatMap((record) => [
...(record.providers ?? [])
.filter((providerId) => providerId !== record.id)
.map((providerId) => [providerId, record.id] as const),
...(record.legacyPluginIds ?? []).map(
(legacyPluginId) => [legacyPluginId, record.id] as const,
),
])
.toSorted(([left], [right]) => left.localeCompare(right)),
);
}
function createRegistryPluginIdNormalizer(
registry: PluginManifestRegistry,
): (id: string) => string {
const aliases = buildRegistryPluginIdAliases(registry);
return (id: string) => {
const trimmed = id.trim();
return aliases[trimmed] ?? trimmed;
};
}
export function loadEnabledBundlePiSettingsSnapshot(params: {
cwd: string;
cfg?: OpenClawConfig;
@@ -76,15 +105,24 @@ export function loadEnabledBundlePiSettingsSnapshot(params: {
if (!workspaceDir) {
return {};
}
const registry = loadPluginManifestRegistry({
const index = loadPluginRegistrySnapshot({
workspaceDir,
config: params.cfg,
});
const registry = loadPluginManifestRegistryForInstalledIndex({
index,
workspaceDir,
config: params.cfg,
includeDisabled: true,
});
if (registry.plugins.length === 0) {
return {};
}
const normalizedPlugins = normalizePluginsConfig(params.cfg?.plugins);
const normalizedPlugins = normalizePluginsConfigWithResolver(
params.cfg?.plugins,
createRegistryPluginIdNormalizer(registry),
);
let snapshot: PiSettingsSnapshot = {};
for (const record of registry.plugins) {

View File

@@ -13,11 +13,11 @@ vi.mock("../infra/boundary-file-read.js", async () => {
};
});
vi.mock("../plugins/manifest-registry.js", async () => {
vi.mock("../plugins/manifest-registry-installed.js", async () => {
const fs = await import("node:fs");
const path = await import("node:path");
return {
loadPluginManifestRegistry: (params: { workspaceDir?: string }) => {
loadPluginManifestRegistryForInstalledIndex: (params: { workspaceDir?: string }) => {
const rootDir = path.join(
params.workspaceDir ?? "",
".openclaw",
@@ -45,6 +45,10 @@ vi.mock("../plugins/manifest-registry.js", async () => {
};
});
vi.mock("../plugins/plugin-registry.js", () => ({
loadPluginRegistrySnapshot: () => ({ plugins: [] }),
}));
vi.mock("./embedded-pi-mcp.js", async () => {
const fs = await import("node:fs");
const path = await import("node:path");

View File

@@ -6,11 +6,17 @@ import type { PluginManifestRegistry } from "../../plugins/manifest-registry.js"
import { createTrackedTempDirs } from "../../test-utils/tracked-temp-dirs.js";
const hoisted = vi.hoisted(() => ({
loadPluginManifestRegistry: vi.fn(),
loadPluginManifestRegistryForInstalledIndex: vi.fn(),
loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })),
}));
vi.mock("../../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry: (...args: unknown[]) => hoisted.loadPluginManifestRegistry(...args),
vi.mock("../../plugins/manifest-registry-installed.js", () => ({
loadPluginManifestRegistryForInstalledIndex: (...args: unknown[]) =>
hoisted.loadPluginManifestRegistryForInstalledIndex(...args),
}));
vi.mock("../../plugins/plugin-registry.js", () => ({
loadPluginRegistrySnapshot: (...args: unknown[]) => hoisted.loadPluginRegistrySnapshot(...args),
}));
let resolvePluginSkillDirs: typeof import("./plugin-skills.js").resolvePluginSkillDirs;
@@ -85,7 +91,9 @@ async function setupAcpxAndHelperRegistry() {
const helperRoot = await tempDirs.make("openclaw-helper-plugin-");
await fs.mkdir(path.join(acpxRoot, "skills"), { recursive: true });
await fs.mkdir(path.join(helperRoot, "skills"), { recursive: true });
hoisted.loadPluginManifestRegistry.mockReturnValue(buildRegistry({ acpxRoot, helperRoot }));
hoisted.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
buildRegistry({ acpxRoot, helperRoot }),
);
return { workspaceDir, acpxRoot, helperRoot };
}
@@ -98,7 +106,8 @@ async function setupPluginOutsideSkills() {
}
afterEach(async () => {
hoisted.loadPluginManifestRegistry.mockReset();
hoisted.loadPluginManifestRegistryForInstalledIndex.mockReset();
hoisted.loadPluginRegistrySnapshot.mockReset();
await tempDirs.cleanup();
});
@@ -108,7 +117,13 @@ describe("resolvePluginSkillDirs", () => {
});
beforeEach(() => {
hoisted.loadPluginManifestRegistry.mockReset();
hoisted.loadPluginManifestRegistryForInstalledIndex.mockReset();
hoisted.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
diagnostics: [],
plugins: [],
});
hoisted.loadPluginRegistrySnapshot.mockReset();
hoisted.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] });
});
it.each([
@@ -152,7 +167,7 @@ describe("resolvePluginSkillDirs", () => {
await fs.mkdir(outsideSkills, { recursive: true });
const escapePath = path.relative(pluginRoot, outsideSkills);
hoisted.loadPluginManifestRegistry.mockReturnValue(
hoisted.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
createSinglePluginRegistry({
pluginRoot,
skills: ["./skills", escapePath],
@@ -183,7 +198,7 @@ describe("resolvePluginSkillDirs", () => {
process.platform === "win32" ? ("junction" as const) : ("dir" as const),
);
hoisted.loadPluginManifestRegistry.mockReturnValue(
hoisted.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
createSinglePluginRegistry({
pluginRoot,
skills: ["./skills-link"],
@@ -210,7 +225,7 @@ describe("resolvePluginSkillDirs", () => {
await fs.mkdir(path.join(pluginRoot, "commands"), { recursive: true });
await fs.mkdir(path.join(pluginRoot, "skills"), { recursive: true });
hoisted.loadPluginManifestRegistry.mockReturnValue(
hoisted.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
createSinglePluginRegistry({
pluginRoot,
format: "bundle",
@@ -240,7 +255,7 @@ describe("resolvePluginSkillDirs", () => {
const pluginRoot = await tempDirs.make("openclaw-legacy-plugin-");
await fs.mkdir(path.join(pluginRoot, "skills"), { recursive: true });
hoisted.loadPluginManifestRegistry.mockReturnValue(
hoisted.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
createSinglePluginRegistry({
pluginRoot,
skills: ["./skills"],

View File

@@ -7,10 +7,9 @@ import {
resolveEffectivePluginActivationState,
resolveMemorySlotDecision,
} from "../../plugins/config-policy.js";
import {
loadPluginManifestRegistry,
type PluginManifestRegistry,
} from "../../plugins/manifest-registry.js";
import { loadPluginManifestRegistryForInstalledIndex } from "../../plugins/manifest-registry-installed.js";
import type { PluginManifestRegistry } from "../../plugins/manifest-registry.js";
import { loadPluginRegistrySnapshot } from "../../plugins/plugin-registry.js";
import { hasKind } from "../../plugins/slots.js";
import { isPathInsideWithRealpath } from "../../security/scan-paths.js";
@@ -51,10 +50,16 @@ export function resolvePluginSkillDirs(params: {
if (!workspaceDir) {
return [];
}
const registry = loadPluginManifestRegistry({
const index = loadPluginRegistrySnapshot({
workspaceDir,
config: params.config,
});
const registry = loadPluginManifestRegistryForInstalledIndex({
index,
workspaceDir,
config: params.config,
includeDisabled: true,
});
if (registry.plugins.length === 0) {
return [];
}