Skip to content

Commit a145356

Browse files
committed
Config: centralize known plugin id resolution
1 parent 47b4005 commit a145356

2 files changed

Lines changed: 109 additions & 6 deletions

File tree

src/config/config.plugin-validation.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,71 @@ describe("config plugin validation", () => {
338338
).toBe(false);
339339
});
340340

341+
it.each([
342+
{
343+
label: "plugins.allow",
344+
buildPluginsConfig: (pluginId: string) => ({
345+
allow: [pluginId],
346+
}),
347+
expectedWarningPath: "plugins.allow",
348+
},
349+
{
350+
label: "plugins.entries.*",
351+
buildPluginsConfig: (pluginId: string) => ({
352+
entries: { [pluginId]: { enabled: true } },
353+
}),
354+
expectedWarningPath: (pluginId: string) => `plugins.entries.${pluginId}`,
355+
},
356+
{
357+
label: "plugins.slots.memory",
358+
buildPluginsConfig: (pluginId: string) => ({
359+
slots: { memory: pluginId },
360+
}),
361+
expectedIssuePath: "plugins.slots.memory",
362+
},
363+
])(
364+
"keeps severity consistent for known vs unknown ids at $label",
365+
({ buildPluginsConfig, expectedWarningPath, expectedIssuePath }) => {
366+
const knownId = "google";
367+
const unknownId = "missing-plugin-id";
368+
const missingBundledEnv = {
369+
...suiteEnv(),
370+
OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(suiteHome, "missing-bundled-plugins"),
371+
};
372+
const knownResult = validateConfigObjectWithPlugins(
373+
{ plugins: buildPluginsConfig(knownId) },
374+
{ env: missingBundledEnv },
375+
);
376+
expect(knownResult.ok).toBe(true);
377+
if (!knownResult.ok) {
378+
return;
379+
}
380+
381+
const unknownResult = validateConfigObjectWithPlugins(
382+
{ plugins: buildPluginsConfig(unknownId) },
383+
{ env: missingBundledEnv },
384+
);
385+
expect(unknownResult.ok).toBe(expectedIssuePath === undefined);
386+
if (!unknownResult.ok) {
387+
expect(
388+
unknownResult.issues.some(
389+
(issue) => issue.path === expectedIssuePath && issue.message.includes(unknownId),
390+
),
391+
).toBe(true);
392+
} else {
393+
const warningPath =
394+
typeof expectedWarningPath === "function"
395+
? expectedWarningPath(unknownId)
396+
: expectedWarningPath;
397+
expect(
398+
unknownResult.warnings.some(
399+
(warning) => warning.path === warningPath && warning.message.includes(unknownId),
400+
),
401+
).toBe(true);
402+
}
403+
},
404+
);
405+
341406
it("does not auto-allow config-loaded overrides of bundled web search plugin ids", async () => {
342407
const res = validateInSuite({
343408
plugins: {

src/config/validation.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import path from "node:path";
22
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
33
import { CHANNEL_IDS, normalizeChatChannelId } from "../channels/registry.js";
4-
import { BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS } from "../plugins/bundled-capability-metadata.js";
4+
import {
5+
BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS,
6+
BUNDLED_LEGACY_PLUGIN_ID_ALIASES,
7+
BUNDLED_PROVIDER_PLUGIN_ID_ALIASES,
8+
} from "../plugins/bundled-capability-metadata.js";
59
import { withBundledPluginAllowlistCompat } from "../plugins/bundled-compat.js";
610
import { listBundledWebSearchPluginIds } from "../plugins/bundled-web-search-ids.js";
711
import {
812
normalizePluginsConfig,
913
resolveEffectiveEnableState,
1014
resolveMemorySlotDecision,
1115
} from "../plugins/config-state.js";
12-
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
16+
import {
17+
loadPluginManifestRegistry,
18+
type PluginManifestRegistry,
19+
} from "../plugins/manifest-registry.js";
1320
import { validateJsonSchemaValue } from "../plugins/schema-validator.js";
1421
import {
1522
hasAvatarUriScheme,
@@ -35,6 +42,12 @@ import { OpenClawSchema } from "./zod-schema.js";
3542

3643
const LEGACY_REMOVED_PLUGIN_IDS = new Set(["google-antigravity-auth", "google-gemini-cli-auth"]);
3744

45+
type KnownPluginIdMetadata = {
46+
providerAliases: Readonly<Record<string, string>>;
47+
legacyAliases: Readonly<Record<string, string>>;
48+
autoEnableProviderPluginIds: Readonly<Record<string, string>>;
49+
};
50+
3851
type UnknownIssueRecord = Record<string, unknown>;
3952
type ConfigPathSegment = string | number;
4053
type AllowedValuesCollection = {
@@ -269,6 +282,27 @@ function collectAllowedValuesFromIssueList(
269282
return { values: collected, incomplete: false, hasValues };
270283
}
271284

285+
function resolveKnownPluginIds(params: {
286+
registry: PluginManifestRegistry;
287+
bundledMetadata: KnownPluginIdMetadata;
288+
}): Set<string> {
289+
const knownIds = new Set(params.registry.plugins.map((record) => record.id));
290+
// Do not rely on the manifest registry alone: cold-start or missing plugin directories can
291+
// temporarily hide bundled manifests, but config validation still needs to recognize bundled
292+
// aliases and auto-enable provider ids so startup remains resilient during upgrades/recovery.
293+
for (const aliasMap of [
294+
params.bundledMetadata.providerAliases,
295+
params.bundledMetadata.legacyAliases,
296+
params.bundledMetadata.autoEnableProviderPluginIds,
297+
]) {
298+
for (const [aliasId, pluginId] of Object.entries(aliasMap)) {
299+
knownIds.add(aliasId);
300+
knownIds.add(pluginId);
301+
}
302+
}
303+
return knownIds;
304+
}
305+
272306
function collectAllowedValuesFromUnknownIssue(issue: unknown): unknown[] {
273307
const collection = collectAllowedValuesFromIssue(issue);
274308
if (collection.incomplete || !collection.hasValues) {
@@ -585,10 +619,14 @@ function validateConfigObjectWithPluginsBase(
585619
const ensureKnownIds = (): Set<string> => {
586620
const info = ensureRegistry();
587621
if (!info.knownIds) {
588-
info.knownIds = new Set([
589-
...info.registry.plugins.map((record) => record.id),
590-
...Object.values(BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS),
591-
]);
622+
info.knownIds = resolveKnownPluginIds({
623+
registry: info.registry,
624+
bundledMetadata: {
625+
providerAliases: BUNDLED_PROVIDER_PLUGIN_ID_ALIASES,
626+
legacyAliases: BUNDLED_LEGACY_PLUGIN_ID_ALIASES,
627+
autoEnableProviderPluginIds: BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS,
628+
},
629+
});
592630
}
593631
return info.knownIds;
594632
};

0 commit comments

Comments
 (0)