Files
openclaw/src/plugins/plugin-command-matcher.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

76 lines
2.4 KiB
TypeScript

import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "@openclaw/normalization-core/string-coerce";
import type { RegisteredPluginCommand } from "./command-registry-state.js";
import { pluginCommandSupportsChannel } from "./plugin-command-metadata.js";
type PluginCommandAliasScope = { kind: "all" } | { kind: "provider"; provider: string };
function listInvocationKeys(
command: RegisteredPluginCommand,
aliasScope: PluginCommandAliasScope,
): string[] {
const keys = new Set<string>();
const add = (value: string | undefined) => {
const normalized = normalizeOptionalLowercaseString(value);
if (normalized) {
keys.add(`/${normalized}`);
}
};
add(command.name);
if (aliasScope.kind === "all") {
for (const alias of Object.values(command.nativeNames ?? {})) {
if (typeof alias === "string") {
add(alias);
}
}
return [...keys];
}
const provider = normalizeOptionalLowercaseString(aliasScope.provider);
const providerAlias = provider ? command.nativeNames?.[provider] : undefined;
add(typeof providerAlias === "string" ? providerAlias : command.nativeNames?.default);
return [...keys];
}
export function matchRegisteredPluginCommand(params: {
commands: readonly RegisteredPluginCommand[];
commandBody: string;
channel?: string;
aliasScope: PluginCommandAliasScope;
}): { command: RegisteredPluginCommand; args?: string } | null {
const trimmed = params.commandBody.trim();
if (!trimmed.startsWith("/")) {
return null;
}
const commandMatch = trimmed.match(/^\/\s*([^\s]+)(?:\s+([\s\S]*))?$/);
if (!commandMatch) {
return null;
}
const key = normalizeLowercaseStringOrEmpty(`/${commandMatch[1]}`);
const alternateKeys = [key];
if (key.includes("_")) {
alternateKeys.push(key.replace(/_/g, "-"));
}
if (key.includes("-")) {
alternateKeys.push(key.replace(/-/g, "_"));
}
const command = alternateKeys
.map((candidateKey) =>
params.commands.find(
(candidate) =>
pluginCommandSupportsChannel(candidate, params.channel) &&
listInvocationKeys(candidate, params.aliasScope).includes(candidateKey),
),
)
.find((candidate): candidate is RegisteredPluginCommand => candidate !== undefined);
if (!command) {
return null;
}
const args = commandMatch[2]?.trim();
if (args && !command.acceptsArgs) {
return null;
}
return { command, args: args || undefined };
}