refactor: keep plugin legacy repair in doctor

This commit is contained in:
Peter Steinberger
2026-04-05 15:44:36 +01:00
parent 6f2f840e97
commit 40ffada812
14 changed files with 215 additions and 166 deletions

View File

@@ -27,7 +27,6 @@ export type BuildPluginApiParams = {
| "registerService"
| "registerCliBackend"
| "registerConfigMigration"
| "registerLegacyConfigMigration"
| "registerAutoEnableProbe"
| "registerProvider"
| "registerSpeechProvider"
@@ -60,8 +59,6 @@ const noopRegisterCli: OpenClawPluginApi["registerCli"] = () => {};
const noopRegisterService: OpenClawPluginApi["registerService"] = () => {};
const noopRegisterCliBackend: OpenClawPluginApi["registerCliBackend"] = () => {};
const noopRegisterConfigMigration: OpenClawPluginApi["registerConfigMigration"] = () => {};
const noopRegisterLegacyConfigMigration: OpenClawPluginApi["registerLegacyConfigMigration"] =
() => {};
const noopRegisterAutoEnableProbe: OpenClawPluginApi["registerAutoEnableProbe"] = () => {};
const noopRegisterProvider: OpenClawPluginApi["registerProvider"] = () => {};
const noopRegisterSpeechProvider: OpenClawPluginApi["registerSpeechProvider"] = () => {};
@@ -112,8 +109,6 @@ export function buildPluginApi(params: BuildPluginApiParams): OpenClawPluginApi
registerService: handlers.registerService ?? noopRegisterService,
registerCliBackend: handlers.registerCliBackend ?? noopRegisterCliBackend,
registerConfigMigration: handlers.registerConfigMigration ?? noopRegisterConfigMigration,
registerLegacyConfigMigration:
handlers.registerLegacyConfigMigration ?? noopRegisterLegacyConfigMigration,
registerAutoEnableProbe: handlers.registerAutoEnableProbe ?? noopRegisterAutoEnableProbe,
registerProvider: handlers.registerProvider ?? noopRegisterProvider,
registerSpeechProvider: handlers.registerSpeechProvider ?? noopRegisterSpeechProvider,

View File

@@ -0,0 +1,153 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createJiti } from "jiti";
import type { LegacyConfigRule } from "../config/legacy.shared.js";
import { discoverOpenClawPlugins } from "./discovery.js";
import { loadPluginManifestRegistry } from "./manifest-registry.js";
import { resolvePluginCacheInputs } from "./roots.js";
import {
buildPluginLoaderAliasMap,
buildPluginLoaderJitiOptions,
shouldPreferNativeJiti,
} from "./sdk-alias.js";
const CONTRACT_API_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"] as const;
const CURRENT_MODULE_PATH = fileURLToPath(import.meta.url);
const RUNNING_FROM_BUILT_ARTIFACT =
CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`) ||
CURRENT_MODULE_PATH.includes(`${path.sep}dist-runtime${path.sep}`);
type PluginDoctorContractModule = {
legacyConfigRules?: unknown;
};
type PluginDoctorContractEntry = {
pluginId: string;
rules: LegacyConfigRule[];
};
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
const doctorContractCache = new Map<string, PluginDoctorContractEntry[]>();
function getJiti(modulePath: string) {
const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url);
const cacheKey = JSON.stringify({
tryNative: shouldPreferNativeJiti(modulePath),
aliasMap: Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)),
});
const cached = jitiLoaders.get(cacheKey);
if (cached) {
return cached;
}
const loader = createJiti(modulePath, buildPluginLoaderJitiOptions(aliasMap));
jitiLoaders.set(cacheKey, loader);
return loader;
}
function buildDoctorContractCacheKey(params: {
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): string {
const { roots, loadPaths } = resolvePluginCacheInputs({
workspaceDir: params.workspaceDir,
env: params.env,
});
return JSON.stringify({
roots,
loadPaths,
});
}
function resolveContractApiPath(rootDir: string): string | null {
const orderedExtensions = RUNNING_FROM_BUILT_ARTIFACT
? CONTRACT_API_EXTENSIONS
: ([...CONTRACT_API_EXTENSIONS.slice(3), ...CONTRACT_API_EXTENSIONS.slice(0, 3)] as const);
for (const extension of orderedExtensions) {
const candidate = path.join(rootDir, `contract-api${extension}`);
if (fs.existsSync(candidate)) {
return candidate;
}
}
return null;
}
function coerceLegacyConfigRules(value: unknown): LegacyConfigRule[] {
if (!Array.isArray(value)) {
return [];
}
return value.filter((entry) => {
if (!entry || typeof entry !== "object") {
return false;
}
const candidate = entry as { path?: unknown; message?: unknown };
return Array.isArray(candidate.path) && typeof candidate.message === "string";
}) as LegacyConfigRule[];
}
function resolvePluginDoctorContracts(params?: {
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): PluginDoctorContractEntry[] {
const env = params?.env ?? process.env;
const cacheKey = buildDoctorContractCacheKey({
workspaceDir: params?.workspaceDir,
env,
});
const cached = doctorContractCache.get(cacheKey);
if (cached) {
return cached;
}
const discovery = discoverOpenClawPlugins({
workspaceDir: params?.workspaceDir,
env,
cache: true,
});
const manifestRegistry = loadPluginManifestRegistry({
workspaceDir: params?.workspaceDir,
env,
cache: true,
candidates: discovery.candidates,
diagnostics: discovery.diagnostics,
});
const entries: PluginDoctorContractEntry[] = [];
for (const record of manifestRegistry.plugins) {
const contractSource = resolveContractApiPath(record.rootDir);
if (!contractSource) {
continue;
}
let mod: PluginDoctorContractModule;
try {
mod = getJiti(contractSource)(contractSource) as PluginDoctorContractModule;
} catch {
continue;
}
const rules = coerceLegacyConfigRules(
(mod as { default?: PluginDoctorContractModule }).default?.legacyConfigRules ??
mod.legacyConfigRules,
);
if (rules.length === 0) {
continue;
}
entries.push({
pluginId: record.id,
rules,
});
}
doctorContractCache.set(cacheKey, entries);
return entries;
}
export function clearPluginDoctorContractRegistryCache(): void {
doctorContractCache.clear();
}
export function listPluginDoctorLegacyConfigRules(params?: {
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): LegacyConfigRule[] {
return resolvePluginDoctorContracts(params).flatMap((entry) => entry.rules);
}

View File

@@ -18,7 +18,6 @@ import type {
CliBackendPlugin,
OpenClawPluginModule,
PluginConfigMigration,
PluginLegacyConfigMigration,
PluginLogger,
PluginSetupAutoEnableProbe,
ProviderPlugin,
@@ -45,11 +44,6 @@ type SetupConfigMigrationEntry = {
migrate: PluginConfigMigration;
};
type SetupLegacyConfigMigrationEntry = {
pluginId: string;
migrate: PluginLegacyConfigMigration;
};
type SetupAutoEnableProbeEntry = {
pluginId: string;
probe: PluginSetupAutoEnableProbe;
@@ -59,7 +53,6 @@ type PluginSetupRegistry = {
providers: SetupProviderEntry[];
cliBackends: SetupCliBackendEntry[];
configMigrations: SetupConfigMigrationEntry[];
legacyConfigMigrations: SetupLegacyConfigMigrationEntry[];
autoEnableProbes: SetupAutoEnableProbeEntry[];
};
@@ -167,7 +160,6 @@ export function resolvePluginSetupRegistry(params?: {
const providers: SetupProviderEntry[] = [];
const cliBackends: SetupCliBackendEntry[] = [];
const configMigrations: SetupConfigMigrationEntry[] = [];
const legacyConfigMigrations: SetupLegacyConfigMigrationEntry[] = [];
const autoEnableProbes: SetupAutoEnableProbeEntry[] = [];
const providerKeys = new Set<string>();
const cliBackendKeys = new Set<string>();
@@ -247,12 +239,6 @@ export function resolvePluginSetupRegistry(params?: {
migrate,
});
},
registerLegacyConfigMigration(migrate) {
legacyConfigMigrations.push({
pluginId: record.id,
migrate,
});
},
registerAutoEnableProbe(probe) {
autoEnableProbes.push({
pluginId: record.id,
@@ -276,7 +262,6 @@ export function resolvePluginSetupRegistry(params?: {
providers,
cliBackends,
configMigrations,
legacyConfigMigrations,
autoEnableProbes,
} satisfies PluginSetupRegistry;
setupRegistryCache.set(cacheKey, registry);
@@ -327,17 +312,6 @@ export function runPluginSetupConfigMigrations(params: {
return { config: next, changes };
}
export function runPluginSetupLegacyConfigMigrations(params: {
raw: Record<string, unknown>;
changes: string[];
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): void {
for (const entry of resolvePluginSetupRegistry(params).legacyConfigMigrations) {
entry.migrate(params.raw, params.changes);
}
}
export function resolvePluginSetupAutoEnableReasons(params: {
config: OpenClawConfig;
workspaceDir?: string;

View File

@@ -1996,8 +1996,6 @@ export type PluginConfigMigration = (config: OpenClawConfig) =>
| null
| undefined;
export type PluginLegacyConfigMigration = (raw: Record<string, unknown>, changes: string[]) => void;
export type PluginSetupAutoEnableContext = {
config: OpenClawConfig;
env: NodeJS.ProcessEnv;
@@ -2070,8 +2068,6 @@ export type OpenClawPluginApi = {
registerCliBackend: (backend: CliBackendPlugin) => void;
/** Register a lightweight config migration that can run before plugin runtime loads. */
registerConfigMigration: (migrate: PluginConfigMigration) => void;
/** Register a lightweight raw legacy-config migration for pre-schema config repair. */
registerLegacyConfigMigration: (migrate: PluginLegacyConfigMigration) => void;
/** Register a lightweight config probe that can auto-enable this plugin generically. */
registerAutoEnableProbe: (probe: PluginSetupAutoEnableProbe) => void;
/** Register a native model/provider plugin (text inference capability). */