fix(plugins): split setup registry runtime types

This commit is contained in:
Vincent Koc
2026-04-11 21:36:07 +01:00
parent 057fe786bd
commit 3607cea991
2 changed files with 249 additions and 109 deletions

View File

@@ -3,21 +3,20 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { normalizeProviderId } from "../agents/provider-id.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { buildPluginApi } from "./api-builder.js";
import type { CliBackendPlugin } from "./cli-backend.types.js";
import { collectPluginConfigContractMatches } from "./config-contracts.js";
import { discoverOpenClawPlugins } from "./discovery.js";
import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js";
import { loadPluginManifestRegistry } from "./manifest-registry.js";
import { resolvePluginCacheInputs } from "./roots.js";
import type { PluginRuntime } from "./runtime/types.js";
import type {
CliBackendPlugin,
OpenClawPluginModule,
PluginConfigMigration,
PluginLogger,
PluginSetupAutoEnableProbe,
ProviderPlugin,
} from "./types.js";
SetupOnlyPluginApi,
SetupOnlyPluginModule,
SetupPluginAutoEnableProbe,
SetupPluginConfigMigration,
SetupPluginLogger,
SetupProviderPlugin,
} from "./setup-registry.types.js";
const SETUP_API_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"] as const;
const CURRENT_MODULE_PATH = fileURLToPath(import.meta.url);
@@ -27,7 +26,7 @@ const RUNNING_FROM_BUILT_ARTIFACT =
type SetupProviderEntry = {
pluginId: string;
provider: ProviderPlugin;
provider: SetupProviderPlugin;
};
type SetupCliBackendEntry = {
@@ -37,12 +36,12 @@ type SetupCliBackendEntry = {
type SetupConfigMigrationEntry = {
pluginId: string;
migrate: PluginConfigMigration;
migrate: SetupPluginConfigMigration;
};
type SetupAutoEnableProbeEntry = {
pluginId: string;
probe: PluginSetupAutoEnableProbe;
probe: SetupPluginAutoEnableProbe;
};
type PluginSetupRegistry = {
@@ -57,8 +56,8 @@ type SetupAutoEnableReason = {
reason: string;
};
const EMPTY_RUNTIME = {} as PluginRuntime;
const NOOP_LOGGER: PluginLogger = {
const EMPTY_SETUP_RUNTIME = {};
const NOOP_LOGGER: SetupPluginLogger = {
info() {},
warn() {},
error() {},
@@ -66,7 +65,7 @@ const NOOP_LOGGER: PluginLogger = {
const jitiLoaders: PluginJitiLoaderCache = new Map();
const setupRegistryCache = new Map<string, PluginSetupRegistry>();
const setupProviderCache = new Map<string, ProviderPlugin | null>();
const setupProviderCache = new Map<string, SetupProviderPlugin | null>();
export function clearPluginSetupRegistryCache(): void {
jitiLoaders.clear();
@@ -190,9 +189,9 @@ function resolveRelevantSetupMigrationPluginIds(params: {
return [...ids].toSorted();
}
function resolveRegister(mod: OpenClawPluginModule): {
function resolveRegister(mod: SetupOnlyPluginModule): {
definition?: { id?: string };
register?: (api: ReturnType<typeof buildPluginApi>) => void | Promise<void>;
register?: (api: SetupOnlyPluginApi) => void | Promise<void>;
} {
if (typeof mod === "function") {
return { register: mod };
@@ -206,7 +205,7 @@ function resolveRegister(mod: OpenClawPluginModule): {
return {};
}
function matchesProvider(provider: ProviderPlugin, providerId: string): boolean {
function matchesProvider(provider: SetupProviderPlugin, providerId: string): boolean {
const normalized = normalizeProviderId(providerId);
if (normalizeProviderId(provider.id) === normalized) {
return true;
@@ -216,6 +215,73 @@ function matchesProvider(provider: ProviderPlugin, providerId: string): boolean
);
}
function createSetupOnlyPluginApi(params: {
id: string;
name: string;
version?: string;
description?: string;
source: string;
rootDir?: string;
config?: OpenClawConfig;
registerProvider?: (provider: SetupProviderPlugin) => void;
registerCliBackend?: (backend: CliBackendPlugin) => void;
registerConfigMigration?: (migrate: SetupPluginConfigMigration) => void;
registerAutoEnableProbe?: (probe: SetupPluginAutoEnableProbe) => void;
}): SetupOnlyPluginApi {
const noop = (..._args: unknown[]) => {};
return {
id: params.id,
name: params.name,
version: params.version,
description: params.description,
source: params.source,
rootDir: params.rootDir,
registrationMode: "setup-only",
config: params.config ?? ({} as OpenClawConfig),
runtime: EMPTY_SETUP_RUNTIME,
logger: NOOP_LOGGER,
resolvePath: (input) => input,
registerProvider: params.registerProvider ?? noop,
registerCliBackend: params.registerCliBackend ?? noop,
registerConfigMigration: params.registerConfigMigration ?? noop,
registerAutoEnableProbe: params.registerAutoEnableProbe ?? noop,
registerTool: noop,
registerHook: noop,
registerHttpRoute: noop,
registerChannel: noop,
registerGatewayMethod: noop,
registerCli: noop,
registerReload: noop,
registerNodeHostCommand: noop,
registerSecurityAuditCollector: noop,
registerService: noop,
registerTextTransforms: noop,
registerSpeechProvider: noop,
registerRealtimeTranscriptionProvider: noop,
registerRealtimeVoiceProvider: noop,
registerMediaUnderstandingProvider: noop,
registerImageGenerationProvider: noop,
registerVideoGenerationProvider: noop,
registerMusicGenerationProvider: noop,
registerWebFetchProvider: noop,
registerWebSearchProvider: noop,
registerInteractiveHandler: noop,
onConversationBindingResolved: noop,
registerCommand: noop,
registerContextEngine: noop,
registerCompactionProvider: noop,
registerAgentHarness: noop,
registerMemoryCapability: noop,
registerMemoryPromptSection: noop,
registerMemoryPromptSupplement: noop,
registerMemoryCorpusSupplement: noop,
registerMemoryFlushPlan: noop,
registerMemoryRuntime: noop,
registerMemoryEmbeddingProvider: noop,
on: noop,
};
}
export function resolvePluginSetupRegistry(params?: {
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
@@ -275,14 +341,14 @@ export function resolvePluginSetupRegistry(params?: {
continue;
}
let mod: OpenClawPluginModule;
let mod: SetupOnlyPluginModule;
try {
mod = getJiti(setupSource)(setupSource) as OpenClawPluginModule;
mod = getJiti(setupSource)(setupSource) as SetupOnlyPluginModule;
} catch {
continue;
}
const resolved = resolveRegister((mod as { default?: OpenClawPluginModule }).default ?? mod);
const resolved = resolveRegister((mod as { default?: SetupOnlyPluginModule }).default ?? mod);
if (!resolved.register) {
continue;
}
@@ -290,53 +356,46 @@ export function resolvePluginSetupRegistry(params?: {
continue;
}
const api = buildPluginApi({
const api = createSetupOnlyPluginApi({
id: record.id,
name: record.name ?? record.id,
version: record.version,
description: record.description,
source: setupSource,
rootDir: record.rootDir,
registrationMode: "setup-only",
config: {} as OpenClawConfig,
runtime: EMPTY_RUNTIME,
logger: NOOP_LOGGER,
resolvePath: (input) => input,
handlers: {
registerProvider(provider) {
const key = `${record.id}:${normalizeProviderId(provider.id)}`;
if (providerKeys.has(key)) {
return;
}
providerKeys.add(key);
providers.push({
pluginId: record.id,
provider,
});
},
registerCliBackend(backend) {
const key = `${record.id}:${normalizeProviderId(backend.id)}`;
if (cliBackendKeys.has(key)) {
return;
}
cliBackendKeys.add(key);
cliBackends.push({
pluginId: record.id,
backend,
});
},
registerConfigMigration(migrate) {
configMigrations.push({
pluginId: record.id,
migrate,
});
},
registerAutoEnableProbe(probe) {
autoEnableProbes.push({
pluginId: record.id,
probe,
});
},
registerProvider(provider) {
const key = `${record.id}:${normalizeProviderId(provider.id)}`;
if (providerKeys.has(key)) {
return;
}
providerKeys.add(key);
providers.push({
pluginId: record.id,
provider,
});
},
registerCliBackend(backend) {
const key = `${record.id}:${normalizeProviderId(backend.id)}`;
if (cliBackendKeys.has(key)) {
return;
}
cliBackendKeys.add(key);
cliBackends.push({
pluginId: record.id,
backend,
});
},
registerConfigMigration(migrate) {
configMigrations.push({
pluginId: record.id,
migrate,
});
},
registerAutoEnableProbe(probe) {
autoEnableProbes.push({
pluginId: record.id,
probe,
});
},
});
@@ -364,7 +423,7 @@ export function resolvePluginSetupProvider(params: {
provider: string;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): ProviderPlugin | undefined {
}): SetupProviderPlugin | undefined {
const cacheKey = buildSetupProviderCacheKey(params);
if (setupProviderCache.has(cacheKey)) {
return setupProviderCache.get(cacheKey) ?? undefined;
@@ -398,15 +457,15 @@ export function resolvePluginSetupProvider(params: {
return undefined;
}
let mod: OpenClawPluginModule;
let mod: SetupOnlyPluginModule;
try {
mod = getJiti(setupSource)(setupSource) as OpenClawPluginModule;
mod = getJiti(setupSource)(setupSource) as SetupOnlyPluginModule;
} catch {
setupProviderCache.set(cacheKey, null);
return undefined;
}
const resolved = resolveRegister((mod as { default?: OpenClawPluginModule }).default ?? mod);
const resolved = resolveRegister((mod as { default?: SetupOnlyPluginModule }).default ?? mod);
if (!resolved.register) {
setupProviderCache.set(cacheKey, null);
return undefined;
@@ -416,33 +475,24 @@ export function resolvePluginSetupProvider(params: {
return undefined;
}
let matchedProvider: ProviderPlugin | undefined;
let matchedProvider: SetupProviderPlugin | undefined;
const localProviderKeys = new Set<string>();
const api = buildPluginApi({
const api = createSetupOnlyPluginApi({
id: record.id,
name: record.name ?? record.id,
version: record.version,
description: record.description,
source: setupSource,
rootDir: record.rootDir,
registrationMode: "setup-only",
config: {} as OpenClawConfig,
runtime: EMPTY_RUNTIME,
logger: NOOP_LOGGER,
resolvePath: (input) => input,
handlers: {
registerProvider(provider) {
const key = normalizeProviderId(provider.id);
if (localProviderKeys.has(key)) {
return;
}
localProviderKeys.add(key);
if (matchesProvider(provider, normalizedProvider)) {
matchedProvider = provider;
}
},
registerConfigMigration() {},
registerAutoEnableProbe() {},
registerProvider(provider) {
const key = normalizeProviderId(provider.id);
if (localProviderKeys.has(key)) {
return;
}
localProviderKeys.add(key);
if (matchesProvider(provider, normalizedProvider)) {
matchedProvider = provider;
}
},
});
@@ -498,13 +548,13 @@ export function resolvePluginSetupCliBackend(params: {
return undefined;
}
let mod: OpenClawPluginModule;
let mod: SetupOnlyPluginModule;
try {
mod = getJiti(setupSource)(setupSource) as OpenClawPluginModule;
mod = getJiti(setupSource)(setupSource) as SetupOnlyPluginModule;
} catch {
return undefined;
}
const resolved = resolveRegister((mod as { default?: OpenClawPluginModule }).default ?? mod);
const resolved = resolveRegister((mod as { default?: SetupOnlyPluginModule }).default ?? mod);
if (!resolved.register) {
return undefined;
}
@@ -514,32 +564,22 @@ export function resolvePluginSetupCliBackend(params: {
let matchedBackend: CliBackendPlugin | undefined;
const localBackendKeys = new Set<string>();
const api = buildPluginApi({
const api = createSetupOnlyPluginApi({
id: record.id,
name: record.name ?? record.id,
version: record.version,
description: record.description,
source: setupSource,
rootDir: record.rootDir,
registrationMode: "setup-only",
config: {} as OpenClawConfig,
runtime: EMPTY_RUNTIME,
logger: NOOP_LOGGER,
resolvePath: (input) => input,
handlers: {
registerProvider() {},
registerConfigMigration() {},
registerAutoEnableProbe() {},
registerCliBackend(backend) {
const key = normalizeProviderId(backend.id);
if (localBackendKeys.has(key)) {
return;
}
localBackendKeys.add(key);
if (key === normalized) {
matchedBackend = backend;
}
},
registerCliBackend(backend) {
const key = normalizeProviderId(backend.id);
if (localBackendKeys.has(key)) {
return;
}
localBackendKeys.add(key);
if (key === normalized) {
matchedBackend = backend;
}
},
});

View File

@@ -0,0 +1,100 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { CliBackendPlugin } from "./cli-backend.types.js";
export type SetupPluginLogger = {
debug?: (message: string) => void;
info: (message: string) => void;
warn: (message: string) => void;
error: (message: string) => void;
};
export type SetupProviderPlugin = {
id: string;
aliases?: string[];
hookAliases?: string[];
resolveConfigApiKey?: (params: {
provider: string;
env?: NodeJS.ProcessEnv;
cfg?: OpenClawConfig;
workspaceDir?: string;
}) => string | null | undefined;
};
export type SetupPluginConfigMigration = (config: OpenClawConfig) =>
| {
config: OpenClawConfig;
changes: string[];
}
| null
| undefined;
export type SetupPluginAutoEnableContext = {
config: OpenClawConfig;
env: NodeJS.ProcessEnv;
};
export type SetupPluginAutoEnableProbe = (
ctx: SetupPluginAutoEnableContext,
) => string | string[] | null | undefined;
export type SetupOnlyPluginApi = {
id: string;
name: string;
version?: string;
description?: string;
source: string;
rootDir?: string;
registrationMode: "setup-only";
config: OpenClawConfig;
pluginConfig?: Record<string, unknown>;
runtime: Record<string, never>;
logger: SetupPluginLogger;
resolvePath: (input: string) => string;
registerProvider: (provider: SetupProviderPlugin) => void;
registerCliBackend: (backend: CliBackendPlugin) => void;
registerConfigMigration: (migrate: SetupPluginConfigMigration) => void;
registerAutoEnableProbe: (probe: SetupPluginAutoEnableProbe) => void;
registerTool: (...args: unknown[]) => void;
registerHook: (...args: unknown[]) => void;
registerHttpRoute: (...args: unknown[]) => void;
registerChannel: (...args: unknown[]) => void;
registerGatewayMethod: (...args: unknown[]) => void;
registerCli: (...args: unknown[]) => void;
registerReload: (...args: unknown[]) => void;
registerNodeHostCommand: (...args: unknown[]) => void;
registerSecurityAuditCollector: (...args: unknown[]) => void;
registerService: (...args: unknown[]) => void;
registerTextTransforms: (...args: unknown[]) => void;
registerSpeechProvider: (...args: unknown[]) => void;
registerRealtimeTranscriptionProvider: (...args: unknown[]) => void;
registerRealtimeVoiceProvider: (...args: unknown[]) => void;
registerMediaUnderstandingProvider: (...args: unknown[]) => void;
registerImageGenerationProvider: (...args: unknown[]) => void;
registerVideoGenerationProvider: (...args: unknown[]) => void;
registerMusicGenerationProvider: (...args: unknown[]) => void;
registerWebFetchProvider: (...args: unknown[]) => void;
registerWebSearchProvider: (...args: unknown[]) => void;
registerInteractiveHandler: (...args: unknown[]) => void;
onConversationBindingResolved: (...args: unknown[]) => void;
registerCommand: (...args: unknown[]) => void;
registerContextEngine: (...args: unknown[]) => void;
registerCompactionProvider: (...args: unknown[]) => void;
registerAgentHarness: (...args: unknown[]) => void;
registerMemoryCapability: (...args: unknown[]) => void;
registerMemoryPromptSection: (...args: unknown[]) => void;
registerMemoryPromptSupplement: (...args: unknown[]) => void;
registerMemoryCorpusSupplement: (...args: unknown[]) => void;
registerMemoryFlushPlan: (...args: unknown[]) => void;
registerMemoryRuntime: (...args: unknown[]) => void;
registerMemoryEmbeddingProvider: (...args: unknown[]) => void;
on: (...args: unknown[]) => void;
};
export type SetupOnlyPluginDefinition = {
id?: string;
register?: (api: SetupOnlyPluginApi) => void | Promise<void>;
};
export type SetupOnlyPluginModule =
| SetupOnlyPluginDefinition
| ((api: SetupOnlyPluginApi) => void | Promise<void>);