fix: preserve commands.list metadata (#64147)

Merged via squash.

Reviewed-by: @frankekn
This commit is contained in:
Frank Yang
2026-04-10 15:35:05 +08:00
committed by GitHub
parent c919cc2cef
commit 360955a7c8
5 changed files with 58 additions and 28 deletions

View File

@@ -100,6 +100,9 @@ describe("commands registry", () => {
{ skillCommands },
);
expect(commands.find((spec) => spec.nativeName === "demo_skill")).toBeTruthy();
expect(commands.find((spec) => spec.nativeName === "demo_skill")).toMatchObject({
category: "tools",
});
const native = listNativeCommandSpecsForConfig(
{ commands: { config: false, plugins: false, debug: false, native: true } },

View File

@@ -88,6 +88,7 @@ function buildSkillCommandDefinitions(skillCommands?: SkillCommandSpec[]): ChatC
acceptsArgs: true,
argsParsing: "none",
scope: "both",
category: "tools",
}));
}

View File

@@ -299,12 +299,35 @@ describe("commands.list handler", () => {
it("keeps plugin text commands visible for scope=text even without native provider support", () => {
const { payload } = callHandler({ provider: "whatsapp", scope: "text" });
const { commands } = payload as {
commands: Array<{ name: string; source: string; textAliases?: string[] }>;
commands: Array<{
name: string;
source: string;
textAliases?: string[];
nativeName?: string;
}>;
};
expect(commands.find((c) => c.source === "plugin")).toMatchObject({
name: "tts",
textAliases: ["/tts"],
});
expect(commands.find((c) => c.source === "plugin")?.nativeName).toBeUndefined();
});
it("keeps plugin text names while exposing provider-native aliases for scope=text", () => {
const { payload } = callHandler({ provider: "discord", scope: "text" });
const { commands } = payload as {
commands: Array<{
name: string;
source: string;
textAliases?: string[];
nativeName?: string;
}>;
};
expect(commands.find((c) => c.source === "plugin")).toMatchObject({
name: "tts",
nativeName: "discord_tts",
textAliases: ["/tts"],
});
});
it("returns provider-specific plugin command names", () => {

View File

@@ -120,6 +120,34 @@ function mapCommand(
};
}
function buildPluginCommandEntries(params: {
provider?: string;
nameSurface: CommandNameSurface;
}): CommandEntry[] {
const pluginTextSpecs = listPluginCommands();
const pluginNativeSpecs = getPluginCommandSpecs(params.provider);
const entries: CommandEntry[] = [];
for (const [index, textSpec] of pluginTextSpecs.entries()) {
const nativeSpec = pluginNativeSpecs[index];
const nativeName = nativeSpec?.name;
entries.push({
name: params.nameSurface === "text" ? textSpec.name : (nativeName ?? textSpec.name),
...(nativeName ? { nativeName } : {}),
textAliases: [`/${textSpec.name}`],
description: textSpec.description,
source: "plugin",
scope: "both",
acceptsArgs: textSpec.acceptsArgs,
});
}
if (params.nameSurface === "native") {
return entries.filter((entry) => entry.nativeName);
}
return entries;
}
export function buildCommandsListResult(params: {
cfg: ReturnType<typeof loadConfig>;
agentId: string;
@@ -153,33 +181,7 @@ export function buildCommandsListResult(params: {
);
}
if (nameSurface === "text") {
for (const spec of listPluginCommands()) {
commands.push({
name: spec.name,
textAliases: [`/${spec.name}`],
description: spec.description,
source: "plugin",
scope: "both",
acceptsArgs: spec.acceptsArgs,
});
}
} else {
const pluginTextSpecs = listPluginCommands();
const pluginSpecs = getPluginCommandSpecs(provider);
for (const [index, spec] of pluginSpecs.entries()) {
const textName = pluginTextSpecs[index]?.name ?? spec.name;
commands.push({
name: spec.name,
nativeName: spec.name,
textAliases: [`/${textName}`],
description: spec.description,
source: "plugin",
scope: "both",
acceptsArgs: spec.acceptsArgs,
});
}
}
commands.push(...buildPluginCommandEntries({ provider, nameSurface }));
return { commands };
}