From c2940adc80b69384a3de236e8f5525cfc719ce8d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 24 Jan 2026 05:47:45 +0000 Subject: [PATCH] fix: register plugin commands natively (#1558) (thanks @Glucksberg) --- src/auto-reply/commands-registry.test.ts | 16 ++++++++++++++++ src/auto-reply/commands-registry.ts | 15 +++++++++++++-- src/plugins/commands.ts | 2 ++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/auto-reply/commands-registry.test.ts b/src/auto-reply/commands-registry.test.ts index e1192c9cd..be51d5544 100644 --- a/src/auto-reply/commands-registry.test.ts +++ b/src/auto-reply/commands-registry.test.ts @@ -15,15 +15,18 @@ import { shouldHandleTextCommands, } from "./commands-registry.js"; import type { ChatCommandDefinition } from "./commands-registry.types.js"; +import { clearPluginCommands, registerPluginCommand } from "../plugins/commands.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; import { createTestRegistry } from "../test-utils/channel-plugins.js"; beforeEach(() => { setActivePluginRegistry(createTestRegistry([])); + clearPluginCommands(); }); afterEach(() => { setActivePluginRegistry(createTestRegistry([])); + clearPluginCommands(); }); describe("commands registry", () => { @@ -85,6 +88,19 @@ describe("commands registry", () => { expect(native.find((spec) => spec.name === "demo_skill")).toBeTruthy(); }); + it("includes plugin commands in native specs", () => { + registerPluginCommand("plugin-core", { + name: "plugstatus", + description: "Plugin status", + handler: () => ({ text: "ok" }), + }); + const native = listNativeCommandSpecsForConfig( + { commands: { config: false, debug: false, native: true } }, + { skillCommands: [] }, + ); + expect(native.find((spec) => spec.name === "plugstatus")).toBeTruthy(); + }); + it("detects known text commands", () => { const detection = getCommandDetection(); expect(detection.exact.has("/commands")).toBe(true); diff --git a/src/auto-reply/commands-registry.ts b/src/auto-reply/commands-registry.ts index 983f2ea9c..eb191a24f 100644 --- a/src/auto-reply/commands-registry.ts +++ b/src/auto-reply/commands-registry.ts @@ -1,6 +1,7 @@ import type { ClawdbotConfig } from "../config/types.js"; import type { SkillCommandSpec } from "../agents/skills.js"; import { getChatCommands, getNativeCommandSurfaces } from "./commands-registry.data.js"; +import { getPluginCommandSpecs } from "../plugins/commands.js"; import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js"; import { resolveConfiguredModelRef } from "../agents/model-selection.js"; import type { @@ -108,7 +109,7 @@ export function listChatCommandsForConfig( export function listNativeCommandSpecs(params?: { skillCommands?: SkillCommandSpec[]; }): NativeCommandSpec[] { - return listChatCommands({ skillCommands: params?.skillCommands }) + const base = listChatCommands({ skillCommands: params?.skillCommands }) .filter((command) => command.scope !== "text" && command.nativeName) .map((command) => ({ name: command.nativeName ?? command.key, @@ -116,13 +117,18 @@ export function listNativeCommandSpecs(params?: { acceptsArgs: Boolean(command.acceptsArgs), args: command.args, })); + const pluginSpecs = getPluginCommandSpecs(); + if (pluginSpecs.length === 0) return base; + const seen = new Set(base.map((spec) => spec.name.toLowerCase())); + const extras = pluginSpecs.filter((spec) => !seen.has(spec.name.toLowerCase())); + return extras.length > 0 ? [...base, ...extras] : base; } export function listNativeCommandSpecsForConfig( cfg: ClawdbotConfig, params?: { skillCommands?: SkillCommandSpec[] }, ): NativeCommandSpec[] { - return listChatCommandsForConfig(cfg, params) + const base = listChatCommandsForConfig(cfg, params) .filter((command) => command.scope !== "text" && command.nativeName) .map((command) => ({ name: command.nativeName ?? command.key, @@ -130,6 +136,11 @@ export function listNativeCommandSpecsForConfig( acceptsArgs: Boolean(command.acceptsArgs), args: command.args, })); + const pluginSpecs = getPluginCommandSpecs(); + if (pluginSpecs.length === 0) return base; + const seen = new Set(base.map((spec) => spec.name.toLowerCase())); + const extras = pluginSpecs.filter((spec) => !seen.has(spec.name.toLowerCase())); + return extras.length > 0 ? [...base, ...extras] : base; } export function findCommandByNativeName(name: string): ChatCommandDefinition | undefined { diff --git a/src/plugins/commands.ts b/src/plugins/commands.ts index 8fedda351..c376170b7 100644 --- a/src/plugins/commands.ts +++ b/src/plugins/commands.ts @@ -271,9 +271,11 @@ export function listPluginCommands(): Array<{ export function getPluginCommandSpecs(): Array<{ name: string; description: string; + acceptsArgs: boolean; }> { return Array.from(pluginCommands.values()).map((cmd) => ({ name: cmd.name, description: cmd.description, + acceptsArgs: Boolean(cmd.acceptsArgs), })); }