Files
openclaw/src/plugins/cli-registry-loader.ts
Peter Steinberger 77d9ac30bb refactor: reuse shared coercion helpers (#86419)
* refactor: share talk event metric extraction

* refactor: reuse shared coercion helpers

* refactor: reuse shared primitive guards

* refactor: reuse shared record guard

* refactor: reuse shared primitive helpers

* refactor: reuse shared string guards

* refactor: reuse shared non-empty string guard

* refactor: share plugin primitive coercion helpers

* refactor: reuse plugin coercion helpers

* refactor: reuse plugin coercion helpers in more plugins

* refactor: reuse channel coercion helpers

* refactor: reuse monitor coercion helpers

* refactor: reuse provider coercion helpers

* refactor: reuse core coercion helpers

* refactor: reuse runtime coercion helpers

* refactor: reuse helper coercion in codex paths

* refactor: reuse helper coercion in runtime paths

* refactor: reuse codex app-server coercion helpers

* refactor: reuse codex record helpers

* refactor: reuse migration and qa record helpers

* refactor: reuse feishu and core helper guards

* refactor: reuse browser and policy coercion helpers

* refactor: reuse memory wiki record helper

* refactor: share boolean coercion helpers

* refactor: reuse finite number coercion

* refactor: reuse trimmed string list helpers

* refactor: reuse string list normalization

* refactor: reuse remaining string list helpers

* refactor: reuse string entry normalizer

* refactor: share sorted string helpers

* refactor: share string list normalization

* test: preserve command registry browser imports

* refactor: reuse trimmed list helpers

* refactor: reuse string dedupe helpers

* refactor: reuse local dedupe helpers

* refactor: reuse more string dedupe helpers

* refactor: reuse command string dedupe helpers

* refactor: dedupe memory path lists with helper

* refactor: expose string dedupe helpers to plugins

* refactor: reuse core string dedupe helpers

* refactor: reuse shared unique value helpers

* refactor: reuse unique helpers in agent utilities

* refactor: reuse unique helpers in config plumbing

* refactor: reuse unique helpers in extensions

* refactor: reuse unique helpers in core utilities

* refactor: reuse unique helpers in qa plugins

* refactor: reuse unique helpers in memory plugins

* refactor: reuse unique helpers in channel plugins

* refactor: reuse unique helpers in core tails

* refactor: reuse unique helper in comfy workflow

* refactor: reuse unique helpers in test utilities

* refactor: expose unique value helper to plugins

* refactor: reuse unique helpers for numeric lists

* refactor: replace index dedupe filters

* refactor: reuse string entry normalization

* refactor: reuse string normalization in plugin helpers

* refactor: reuse string normalization in extension helpers

* refactor: reuse string normalization in channel parsers

* refactor: reuse string normalization in memory search

* refactor: reuse string normalization in provider parsers

* refactor: reuse string normalization in qa helpers

* refactor: reuse string normalization in infra parsers

* refactor: reuse string normalization in messaging parsers

* refactor: reuse string normalization in core parsers

* refactor: reuse string normalization in extension parsers

* refactor: reuse string normalization in remaining parsers

* refactor: reuse string normalization in final parser spots

* refactor: reuse string normalization in qa media helpers

* refactor: reuse normalization in provider and media lists

* refactor: reuse normalization for remaining set filters

* refactor: reuse normalization in policy allowlists

* refactor: reuse normalization in session and owner lists

* refactor: centralize primitive string lists

* refactor: reuse lowercase entry helpers

* refactor: reuse sorted string helpers

* refactor: reuse unique trimmed helpers

* refactor: reuse string normalization helpers

* refactor: reuse catalog string helpers

* refactor: reuse remaining string helpers

* refactor: simplify remaining list normalization

* refactor: reuse codex auth order normalization

* chore: refresh plugin sdk api baseline

* fix: make shared string sorting deterministic

* chore: refresh plugin sdk api baseline

* fix: align host env security ordering
2026-05-25 21:20:41 +01:00

293 lines
8.8 KiB
TypeScript

import { collectUniqueCommandDescriptors } from "../cli/program/command-descriptor-utils.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { uniqueStrings } from "../shared/string-normalization.js";
import { resolveManifestActivationPluginIds } from "./activation-planner.js";
import { createPluginCliGatewayNodesRuntime } from "./cli-gateway-nodes-runtime.js";
import type { PluginLoadOptions } from "./loader.js";
import { loadOpenClawPluginCliRegistry, loadOpenClawPlugins } from "./loader.js";
import { createEmptyPluginRegistry } from "./registry-empty.js";
import type { PluginRegistry } from "./registry.js";
import {
buildPluginRuntimeLoadOptions,
createPluginRuntimeLoaderLogger,
resolvePluginRuntimeLoadContext,
type PluginRuntimeLoadContext,
} from "./runtime/load-context.js";
import type {
OpenClawPluginCliCommandDescriptor,
OpenClawPluginCliContext,
PluginLogger,
} from "./types.js";
export type PluginCliLoaderOptions = Pick<PluginLoadOptions, "pluginSdkResolution">;
export type PluginCliPublicLoadParams = {
cfg?: OpenClawConfig;
env?: NodeJS.ProcessEnv;
loaderOptions?: PluginCliLoaderOptions;
logger?: PluginLogger;
primaryCommand?: string;
};
export type PluginCliLoadContext = PluginRuntimeLoadContext;
export type PluginCliRegistryLoadResult = PluginCliLoadContext & {
registry: PluginRegistry;
};
export type PluginCliCommandGroupEntry = {
pluginId: string;
parentPath: readonly string[];
placeholders: readonly OpenClawPluginCliCommandDescriptor[];
names: readonly string[];
register: (program: OpenClawPluginCliContext["program"]) => Promise<void>;
};
export function createPluginCliLogger(): PluginLogger {
return createPluginRuntimeLoaderLogger();
}
function resolvePluginCliLogger(logger?: PluginLogger): PluginLogger {
return logger ?? createPluginCliLogger();
}
function buildPluginCliLoaderParams(
context: PluginCliLoadContext,
params?: { primaryCommand?: string },
loaderOptions?: PluginCliLoaderOptions,
) {
const onlyPluginIds = resolvePrimaryCommandManifestPluginIds(context, params?.primaryCommand);
return buildPluginRuntimeLoadOptions(context, {
...loaderOptions,
...(onlyPluginIds && onlyPluginIds.length > 0 ? { onlyPluginIds } : {}),
});
}
function normalizePluginCliRootName(value: string | undefined): string {
return normalizeLowercaseStringOrEmpty(value);
}
function resolvePrimaryCommandManifestPluginIds(
context: PluginCliLoadContext,
primaryCommand: string | undefined,
): string[] | undefined {
const normalizedPrimary = normalizePluginCliRootName(primaryCommand);
if (!normalizedPrimary) {
return undefined;
}
return resolveManifestActivationPluginIds({
trigger: {
kind: "command",
command: normalizedPrimary,
},
config: context.activationSourceConfig,
workspaceDir: context.workspaceDir,
env: context.env,
});
}
function listPluginCliRootOwnerIds(registry: PluginRegistry, primaryCommand: string): string[] {
const normalizedPrimary = normalizePluginCliRootName(primaryCommand);
if (!normalizedPrimary) {
return [];
}
return uniqueStrings(
registry.cliRegistrars
.filter((entry) => {
const parentPath = entry.parentPath ?? [];
const roots =
parentPath.length > 0
? [parentPath[0]]
: [...entry.commands, ...entry.descriptors.map((descriptor) => descriptor.name)];
return roots.includes(normalizedPrimary);
})
.map((entry) => entry.pluginId),
);
}
async function resolvePrimaryCommandPluginIds(
context: PluginCliLoadContext,
primaryCommand: string | undefined,
loaderOptions?: PluginCliLoaderOptions,
): Promise<string[] | undefined> {
const normalizedPrimary = normalizePluginCliRootName(primaryCommand);
if (!normalizedPrimary) {
return undefined;
}
const manifestPluginIds = resolvePrimaryCommandManifestPluginIds(context, normalizedPrimary);
if (manifestPluginIds && manifestPluginIds.length > 0) {
return manifestPluginIds;
}
const { registry } = await loadPluginCliMetadataRegistryWithContext(
context,
{ primaryCommand: normalizedPrimary },
loaderOptions,
);
return listPluginCliRootOwnerIds(registry, normalizedPrimary);
}
export function resolvePluginCliLoadContext(params: {
cfg?: OpenClawConfig;
env?: NodeJS.ProcessEnv;
logger: PluginLogger;
}): PluginCliLoadContext {
return resolvePluginRuntimeLoadContext({
config: params.cfg,
env: params.env,
logger: params.logger,
});
}
export async function loadPluginCliMetadataRegistryWithContext(
context: PluginCliLoadContext,
params?: { primaryCommand?: string },
loaderOptions?: PluginCliLoaderOptions,
): Promise<PluginCliRegistryLoadResult> {
return {
...context,
registry: await loadOpenClawPluginCliRegistry(
buildPluginCliLoaderParams(context, params, loaderOptions),
),
};
}
export async function loadPluginCliCommandRegistryWithContext(params: {
context: PluginCliLoadContext;
primaryCommand?: string;
loaderOptions?: PluginCliLoaderOptions;
}): Promise<PluginCliRegistryLoadResult> {
let onlyPluginIds: string[] | undefined;
try {
onlyPluginIds = await resolvePrimaryCommandPluginIds(
params.context,
params.primaryCommand,
params.loaderOptions,
);
} catch {
onlyPluginIds = resolvePrimaryCommandManifestPluginIds(params.context, params.primaryCommand);
}
if (onlyPluginIds && onlyPluginIds.length === 0) {
return {
...params.context,
registry: createEmptyPluginRegistry(),
};
}
return {
...params.context,
registry: loadOpenClawPlugins(
buildPluginRuntimeLoadOptions(params.context, {
...params.loaderOptions,
...(onlyPluginIds && onlyPluginIds.length > 0 ? { onlyPluginIds } : {}),
activate: false,
cache: false,
runtimeOptions: {
nodes: createPluginCliGatewayNodesRuntime(),
},
}),
),
};
}
function buildPluginCliCommandGroupEntries(params: {
registry: PluginRegistry;
config: OpenClawConfig;
workspaceDir: string | undefined;
logger: PluginLogger;
}): PluginCliCommandGroupEntry[] {
return params.registry.cliRegistrars.map((entry) => ({
pluginId: entry.pluginId,
parentPath: entry.parentPath ?? [],
placeholders: entry.descriptors,
names: entry.commands,
register: async (program) => {
await entry.register({
program,
parentPath: entry.parentPath ?? [],
config: params.config,
workspaceDir: params.workspaceDir,
logger: params.logger,
});
},
}));
}
export async function loadPluginCliDescriptors(
params: PluginCliPublicLoadParams,
): Promise<OpenClawPluginCliCommandDescriptor[]> {
try {
const logger = resolvePluginCliLogger(params.logger);
const context = resolvePluginCliLoadContext({
cfg: params.cfg,
env: params.env,
logger,
});
const { registry } = await loadPluginCliMetadataRegistryWithContext(
context,
{ primaryCommand: params.primaryCommand },
params.loaderOptions,
);
return collectUniqueCommandDescriptors(
registry.cliRegistrars
.filter((entry) => (entry.parentPath ?? []).length === 0)
.map((entry) => entry.descriptors),
);
} catch {
return [];
}
}
export async function loadPluginCliRegistrationEntries(params: {
cfg?: OpenClawConfig;
env?: NodeJS.ProcessEnv;
loaderOptions?: PluginCliLoaderOptions;
logger?: PluginLogger;
primaryCommand?: string;
}): Promise<PluginCliCommandGroupEntry[]> {
const resolvedLogger = resolvePluginCliLogger(params.logger);
const context = resolvePluginCliLoadContext({
cfg: params.cfg,
env: params.env,
logger: resolvedLogger,
});
const { config, workspaceDir, logger, registry } = await loadPluginCliCommandRegistryWithContext({
context,
primaryCommand: params.primaryCommand,
loaderOptions: params.loaderOptions,
});
return buildPluginCliCommandGroupEntries({
registry,
config,
workspaceDir,
logger,
});
}
export async function resolvePluginCliRootOwnerIds(
params: PluginCliPublicLoadParams,
): Promise<string[] | null> {
const primaryCommand = normalizePluginCliRootName(params.primaryCommand);
if (!primaryCommand) {
return null;
}
const logger = resolvePluginCliLogger(params.logger);
const context = resolvePluginCliLoadContext({
cfg: params.cfg,
env: params.env,
logger,
});
return (
(await resolvePrimaryCommandPluginIds(context, primaryCommand, params.loaderOptions)) ?? null
);
}
export async function loadPluginCliRegistrationEntriesWithDefaults(
params: PluginCliPublicLoadParams,
): Promise<PluginCliCommandGroupEntry[]> {
const logger = resolvePluginCliLogger(params.logger);
return loadPluginCliRegistrationEntries({
...params,
logger,
});
}