Files
openclaw/src/plugins/command-specs.ts
T
Peter Steinberger d6f70a96cb fix(plugins): native commands execute the selected plugin (#121544)
* fix(plugins): preserve selected command identity

* test(telegram): use scoped command registries

* test(telegram): isolate command runtime fixtures

* test(telegram): warm native command runtime

* refactor(plugins): keep command metadata private

* fix(plugins): accept synchronous command handlers

* fix(plugins): scope command drain bypass to live execution

* test(telegram): use scoped command registry fixtures

* test(telegram): isolate native menu runtime fixtures

* test(telegram): isolate login session store

* test(telegram): surface login flow failures

* test(telegram): preload native login module

* test(telegram): scope native command registries

* fix(plugins): complete command dispatch contracts

* fix(plugins): break command dispatch import cycles

* fix(plugins): stabilize command dispatch contracts

* fix(channels): keep plugin dispatch options internal

* fix(plugins): keep command dispatch carrier opaque

* test(channels): align delivery adapter fixtures

* test(delivery): align custody ownership coverage

* test(delivery): align latest queue reconciliation

* test(channels): drop obsolete delivery wrappers

* fix(plugins): rebind channel reload starts

* fix(plugins): scope command catalog reloads

* fix(ci): align current runtime contracts

* chore(plugin-sdk): refresh API baseline
2026-08-10 19:30:47 -07:00

149 lines
4.7 KiB
TypeScript

// Normalizes plugin command specs for CLI and slash command surfaces.
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
import { getLoadedChannelPlugin } from "../channels/plugins/index.js";
import { resolveReadOnlyChannelCommandDefaults } from "../channels/plugins/read-only-command-defaults.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { pluginCommands } from "./command-registry-state.js";
import {
pluginCommandSupportsChannel,
projectPluginCommandNativeMetadata,
} from "./plugin-command-metadata.js";
import type { PluginCommandRegistration } from "./registry-types.js";
import type { OpenClawPluginCommandDefinition } from "./types.js";
type PluginCommandSpecOptions = {
env?: NodeJS.ProcessEnv;
stateDir?: string;
workspaceDir?: string;
config?: OpenClawConfig;
};
type PluginCommandEntrySpec = {
name: string;
description: string;
acceptsArgs: boolean;
nativeName?: string;
};
function resolvePluginTextName(command: OpenClawPluginCommandDefinition): string {
const name = command.name.trim();
return name || command.name;
}
function pluginNativeCommandsEnabled(
providerName: string | undefined,
options: PluginCommandSpecOptions,
): boolean {
if (!providerName) {
return true;
}
const commandDefaults = options.config
? resolveReadOnlyChannelCommandDefaults(providerName, {
...options,
config: options.config,
})
: undefined;
return (
(getLoadedChannelPlugin(providerName)?.commands ?? commandDefaults)
?.nativeCommandsAutoEnabled === true
);
}
export function getPluginCommandSpecs(
provider?: string,
options: PluginCommandSpecOptions = {},
): Array<{
name: string;
description: string;
descriptionLocalizations?: Record<string, string>;
acceptsArgs: boolean;
}> {
const providerName = normalizeOptionalLowercaseString(provider);
if (!pluginNativeCommandsEnabled(providerName, options)) {
return [];
}
return listProviderPluginCommandSpecs(providerName);
}
export function getPluginCommandEntrySpecs(
provider?: string,
options: PluginCommandSpecOptions = {},
): PluginCommandEntrySpec[] {
const providerName = normalizeOptionalLowercaseString(provider);
const nativeCommandsEnabled = pluginNativeCommandsEnabled(providerName, options);
return Array.from(pluginCommands.values())
.map((cmd) => serializePluginCommandEntrySpec(cmd, providerName, nativeCommandsEnabled))
.filter((spec): spec is PluginCommandEntrySpec => spec !== null);
}
export function getPluginCommandEntrySpecsFromRegistrations(
commands: readonly PluginCommandRegistration[],
provider?: string,
options: PluginCommandSpecOptions = {},
): PluginCommandEntrySpec[] {
const providerName = normalizeOptionalLowercaseString(provider);
const nativeCommandsEnabled = pluginNativeCommandsEnabled(providerName, options);
return commands
.map((entry) =>
serializePluginCommandEntrySpec(entry.command, providerName, nativeCommandsEnabled),
)
.filter((spec): spec is PluginCommandEntrySpec => spec !== null);
}
/** Resolve plugin command specs for a provider's native naming surface without support gating. */
export function listProviderPluginCommandSpecs(provider?: string): Array<{
name: string;
description: string;
descriptionLocalizations?: Record<string, string>;
acceptsArgs: boolean;
}> {
return Array.from(pluginCommands.values())
.filter((cmd) => pluginCommandSupportsChannel(cmd, provider))
.map((cmd) => serializePluginCommandSpec(cmd, provider));
}
function serializePluginCommandSpec(
cmd: OpenClawPluginCommandDefinition,
provider?: string,
): {
name: string;
description: string;
descriptionLocalizations?: Record<string, string>;
acceptsArgs: boolean;
} {
const metadata = projectPluginCommandNativeMetadata(cmd, provider);
const spec: {
name: string;
description: string;
descriptionLocalizations?: Record<string, string>;
acceptsArgs: boolean;
} = {
name: metadata.name,
description: metadata.description,
acceptsArgs: metadata.acceptsArgs,
};
if (metadata.descriptionLocalizations) {
spec.descriptionLocalizations = { ...metadata.descriptionLocalizations };
}
return spec;
}
function serializePluginCommandEntrySpec(
cmd: OpenClawPluginCommandDefinition,
provider: string | undefined,
nativeCommandsEnabled: boolean,
): PluginCommandEntrySpec | null {
if (!pluginCommandSupportsChannel(cmd, provider)) {
return null;
}
const nativeName = nativeCommandsEnabled
? projectPluginCommandNativeMetadata(cmd, provider).name
: undefined;
return {
name: resolvePluginTextName(cmd),
description: cmd.description.trim(),
acceptsArgs: cmd.acceptsArgs ?? false,
...(nativeName ? { nativeName } : {}),
};
}