Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  commands-registry.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { createChannelTestPluginBase, createTestRegistry } from "../test-utils/channel-plugins.js";
import {
  buildCommandText,
  buildCommandTextFromArgs,
  findCommandByNativeName,
  getCommandDetection,
  listChatCommands,
  listChatCommandsForConfig,
  listNativeCommandSpecs,
  listNativeCommandSpecsForConfig,
  normalizeCommandBody,
  parseCommandArgs,
  resolveCommandArgChoices,
  resolveCommandArgMenu,
  serializeCommandArgs,
  shouldHandleTextCommands,
} from "./commands-registry.js";
import type { ChatCommandDefinition } from "./commands-registry.types.js";

type NativeCommandNameResolver = (params: { commandKey: string; defaultName: string }) => string;

function installNativeCommandOverridePlugin(params: {
  id: "discord" | "slack";
  resolveNativeCommandName: NativeCommandNameResolver;
}) {
  setActivePluginRegistry(
    createTestRegistry([
      {
        pluginId: params.id,
        plugin: {
          ...createChannelTestPluginBase({
            id: params.id,
            capabilities: { nativeCommands: true, chatTypes: ["direct"] },
          }),
          commands: {
            resolveNativeCommandName: params.resolveNativeCommandName,
          },
        },
        source: "test",
      },
    ]),
  );
}

function installDiscordNativeCommandOverrides() {
  installNativeCommandOverridePlugin({
    id: "discord",
    resolveNativeCommandName: ({ commandKey, defaultName }) =>
      commandKey === "tts" ? "voice" : defaultName,
  });
}

function installSlackNativeCommandOverrides() {
  installNativeCommandOverridePlugin({
    id: "slack",
    resolveNativeCommandName: ({ commandKey, defaultName }) =>
      commandKey === "status" ? "agentstatus" : defaultName,
  });
}

beforeEach(() => {
  vi.doUnmock("../channels/plugins/index.js");
  setActivePluginRegistry(createTestRegistry([]));
});

afterEach(() => {
  setActivePluginRegistry(createTestRegistry([]));
});

describe("commands registry", () => {
  it("builds command text with args", () => {
    expect(buildCommandText("status")).toBe("/status");
    expect(buildCommandText("tasks")).toBe("/tasks");
    expect(buildCommandText("model", "gpt-5")).toBe("/model gpt-5");
    expect(buildCommandText("models")).toBe("/models");
  });

  it("exposes native specs", () => {
    const specs = listNativeCommandSpecs();
    expect(specs.find((spec) => spec.name === "help")).toBeTruthy();
    expect(specs.find((spec) => spec.name === "stop")).toBeTruthy();
    expect(specs.find((spec) => spec.name === "skill")).toBeTruthy();
    expect(specs.find((spec) => spec.name === "tasks")).toBeTruthy();
    expect(specs.find((spec) => spec.name === "whoami")).toBeTruthy();
    expect(specs.find((spec) => spec.name === "compact")).toBeTruthy();
  });

  it("filters commands based on config flags", () => {
    const disabled = listChatCommandsForConfig({
      commands: { config: false, plugins: false, debug: false },
    });
    expect(disabled.find((spec) => spec.key === "config")).toBeFalsy();
    expect(disabled.find((spec) => spec.key === "plugins")).toBeFalsy();
    expect(disabled.find((spec) => spec.key === "debug")).toBeFalsy();

    const enabled = listChatCommandsForConfig({
      commands: { config: true, plugins: true, debug: true },
    });
    expect(enabled.find((spec) => spec.key === "config")).toBeTruthy();
    expect(enabled.find((spec) => spec.key === "plugins")).toBeTruthy();
    expect(enabled.find((spec) => spec.key === "debug")).toBeTruthy();

    const nativeDisabled = listNativeCommandSpecsForConfig({
      commands: { config: false, plugins: false, debug: false, native: true },
    });
    expect(nativeDisabled.find((spec) => spec.name === "config")).toBeFalsy();
    expect(nativeDisabled.find((spec) => spec.name === "plugins")).toBeFalsy();
    expect(nativeDisabled.find((spec) => spec.name === "debug")).toBeFalsy();
  });

  it("does not enable restricted commands from inherited flags", () => {
    const inheritedCommands = Object.create({
      config: true,
      plugins: true,
      debug: true,
      bash: true,
    }) as Record<string, unknown>;
    const commands = listChatCommandsForConfig({
      commands: inheritedCommands as never,
    });
    expect(commands.find((spec) => spec.key === "config")).toBeFalsy();
    expect(commands.find((spec) => spec.key === "plugins")).toBeFalsy();
    expect(commands.find((spec) => spec.key === "debug")).toBeFalsy();
    expect(commands.find((spec) => spec.key === "bash")).toBeFalsy();
  });

  it("appends skill commands when provided", () => {
    const skillCommands = [
      {
        name: "demo_skill",
        skillName: "demo-skill",
        description: "Demo skill",
      },
    ];
    const commands = listChatCommandsForConfig(
      {
        commands: { config: false, plugins: false, debug: false },
      },
      { 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 } },
      { skillCommands },
    );
    expect(native.find((spec) => spec.name === "demo_skill")).toBeTruthy();
  });

  it("applies discord native command overrides", () => {
    installDiscordNativeCommandOverrides();
    const native = listNativeCommandSpecsForConfig(
      { commands: { native: true } },
      { provider: "discord" },
    );
    expect(native.find((spec) => spec.name === "voice")).toBeTruthy();
    expect(findCommandByNativeName("voice", "discord")?.key).toBe("tts");
    expect(findCommandByNativeName("tts", "discord")).toBeUndefined();
  });

  it("applies slack native command overrides", () => {
    installSlackNativeCommandOverrides();
    const native = listNativeCommandSpecsForConfig(
      { commands: { native: true } },
      { provider: "slack" },
    );
    expect(native.find((spec) => spec.name === "agentstatus")).toBeTruthy();
    expect(findCommandByNativeName("agentstatus", "slack")?.key).toBe("status");
    expect(findCommandByNativeName("status", "slack")).toBeUndefined();
    expect(
      findCommandByNativeName("agentstatus", "slack", {
        includeBundledChannelFallback: false,
      })?.key,
    ).toBe("status");
    expect(
      findCommandByNativeName("status", "slack", {
        includeBundledChannelFallback: false,
      }),
    ).toBeUndefined();
  });

  it("can resolve default native command names without loading bundled channel fallbacks", () => {
    expect(
      findCommandByNativeName("status", "discord", {
        includeBundledChannelFallback: false,
      })?.key,
    ).toBe("status");
  });

  it("keeps discord native command specs within slash-command limits", () => {
    installDiscordNativeCommandOverrides();
    const cfg = { commands: { native: true } };
    const native = listNativeCommandSpecsForConfig(cfg, { provider: "discord" });
    for (const spec of native) {
      expect(spec.name).toMatch(/^[a-z0-9_-]{1,32}$/);
      expect(spec.description.length).toBeGreaterThan(0);
      expect(spec.description.length).toBeLessThanOrEqual(100);
      expect(spec.args?.length ?? 0).toBeLessThanOrEqual(25);

      const command = findCommandByNativeName(spec.name, "discord");
      expect(command).toBeTruthy();

      const args = command?.args ?? spec.args ?? [];
      const argNames = new Set<string>();
      let sawOptional = false;
      for (const arg of args) {
        expect(argNames.has(arg.name)).toBe(false);
        argNames.add(arg.name);

        const isRequired = arg.required ?? false;
        if (!isRequired) {
          sawOptional = true;
        } else {
          expect(sawOptional).toBe(false);
        }

        expect(arg.name).toMatch(/^[a-z0-9_-]{1,32}$/);
        expect(arg.description.length).toBeGreaterThan(0);
        expect(arg.description.length).toBeLessThanOrEqual(100);

        if (!command) {
          continue;
        }
        const choices = resolveCommandArgChoices({
          command,
          arg,
          cfg,
          provider: "discord",
        });
        if (choices.length === 0) {
          continue;
        }
        expect(choices.length).toBeLessThanOrEqual(25);
        for (const choice of choices) {
          expect(choice.label.length).toBeGreaterThan(0);
          expect(choice.label.length).toBeLessThanOrEqual(100);
          expect(choice.value.length).toBeGreaterThan(0);
          expect(choice.value.length).toBeLessThanOrEqual(100);
        }
      }
    }
  });

  it("keeps ACP native action choices aligned with implemented handlers", () => {
    const acp = listChatCommands().find((command) => command.key === "acp");
    expect(acp).toBeTruthy();
    const actionArg = acp?.args?.find((arg) => arg.name === "action");
    expect(actionArg?.choices).toEqual([
      "spawn",
      "cancel",
      "steer",
      "close",
      "sessions",
      "status",
      "set-mode",
      "set",
      "cwd",
      "permissions",
      "timeout",
      "model",
      "reset-options",
      "doctor",
      "install",
      "help",
    ]);
  });

  it("registers fast mode as a first-class options command", () => {
    const fast = listChatCommands().find((command) => command.key === "fast");
    expect(fast).toMatchObject({
      nativeName: "fast",
      textAliases: ["/fast"],
      category: "options",
    });
    const modeArg = fast?.args?.find((arg) => arg.name === "mode");
    expect(modeArg?.choices).toEqual(["status", "on", "off"]);
  });

  it("detects known text commands", () => {
    const detection = getCommandDetection();
    expect(detection.exact.has("/commands")).toBe(true);
    expect(detection.exact.has("/skill")).toBe(true);
    expect(detection.exact.has("/compact")).toBe(true);
    expect(detection.exact.has("/whoami")).toBe(true);
    expect(detection.exact.has("/id")).toBe(true);
    for (const command of listChatCommands()) {
      for (const alias of command.textAliases) {
        expect(detection.exact.has(alias.toLowerCase())).toBe(true);
        expect(detection.regex.test(alias)).toBe(true);
        expect(detection.regex.test(`${alias}:`)).toBe(true);

        if (command.acceptsArgs) {
          expect(detection.regex.test(`${alias} list`)).toBe(true);
          expect(detection.regex.test(`${alias}: list`)).toBe(true);
        } else {
          expect(detection.regex.test(`${alias} list`)).toBe(false);
          expect(detection.regex.test(`${alias}: list`)).toBe(false);
        }
      }
    }
    expect(detection.regex.test("try /status")).toBe(false);
  });

  it("respects text command gating", () => {
    setActivePluginRegistry(
      createTestRegistry([
        {
          pluginId: "discord",
          plugin: createChannelTestPluginBase({
            id: "discord",
            capabilities: { nativeCommands: true, chatTypes: ["direct"] },
          }),
          source: "test",
        },
      ]),
    );
    const cfg = { commands: { text: false } };
    expect(
      shouldHandleTextCommands({
        cfg,
        surface: "discord",
        commandSource: "text",
      }),
    ).toBe(false);
    expect(
      shouldHandleTextCommands({
        cfg,
        surface: "whatsapp",
        commandSource: "text",
      }),
    ).toBe(true);
    expect(
      shouldHandleTextCommands({
        cfg,
        surface: "discord",
        commandSource: "native",
      }),
    ).toBe(true);
  });

  it("normalizes telegram-style command mentions for the current bot", () => {
    expect(normalizeCommandBody("/help@openclaw", { botUsername: "openclaw" })).toBe("/help");
    expect(
      normalizeCommandBody("/help@openclaw args", {
        botUsername: "openclaw",
      }),
    ).toBe("/help args");
    expect(
      normalizeCommandBody("/help@openclaw: args", {
        botUsername: "openclaw",
      }),
    ).toBe("/help args");
  });

  it("keeps telegram-style command mentions for other bots", () => {
    expect(normalizeCommandBody("/help@otherbot", { botUsername: "openclaw" })).toBe(
      "/help@otherbot",
    );
  });

  it("keeps unregistered dock underscore aliases unchanged", () => {
    expect(normalizeCommandBody("/dock_telegram")).toBe("/dock_telegram");
  });
});

describe("commands registry args", () => {
  function createUsageModeCommand(
    argsParsing: ChatCommandDefinition["argsParsing"] = "positional",
    description = "mode",
  ): ChatCommandDefinition {
    return {
      key: "usage",
      description: "usage",
      textAliases: [],
      scope: "both",
      argsMenu: "auto",
      argsParsing,
      args: [
        {
          name: "mode",
          description,
          type: "string",
          choices: ["off", "tokens", "full", "cost"],
        },
      ],
    };
  }

  it("parses positional args and captureRemaining", () => {
    const command: ChatCommandDefinition = {
      key: "debug",
      description: "debug",
      textAliases: [],
      scope: "both",
      argsParsing: "positional",
      args: [
        { name: "action", description: "action", type: "string" },
        { name: "path", description: "path", type: "string" },
        { name: "value", description: "value", type: "string", captureRemaining: true },
      ],
    };

    const args = parseCommandArgs(command, "set foo bar baz");
    expect(args?.values).toEqual({ action: "set", path: "foo", value: "bar baz" });
  });

  it("serializes args via raw first, then values", () => {
    const command: ChatCommandDefinition = {
      key: "model",
      description: "model",
      textAliases: [],
      scope: "both",
      argsParsing: "positional",
      args: [{ name: "model", description: "model", type: "string", captureRemaining: true }],
    };

    expect(serializeCommandArgs(command, { raw: "gpt-5.4" })).toBe("gpt-5.4");
    expect(serializeCommandArgs(command, { values: { model: "gpt-5.4" } })).toBe("gpt-5.4");
    expect(buildCommandTextFromArgs(command, { values: { model: "gpt-5.4" } })).toBe(
      "/model gpt-5.4",
    );
  });

  it("resolves auto arg menus when missing a choice arg", () => {
    const command = createUsageModeCommand();

    const menu = resolveCommandArgMenu({ command, args: undefined, cfg: {} as never });
    expect(menu?.arg.name).toBe("mode");
    expect(menu?.choices).toEqual([
      { label: "off", value: "off" },
      { label: "tokens", value: "tokens" },
      { label: "full", value: "full" },
      { label: "cost", value: "cost" },
    ]);
  });

  it("does not show menus when arg already provided", () => {
    const command = createUsageModeCommand();

    const menu = resolveCommandArgMenu({
      command,
      args: { values: { mode: "tokens" } },
      cfg: {} as never,
    });
    expect(menu).toBeNull();
  });

  it("resolves function-based choices with a default provider/model context", () => {
    let seen: {
      provider?: string;
      model?: string;
      commandKey: string;
      argName: string;
    } | null = null;

    const command: ChatCommandDefinition = {
      key: "think",
      description: "think",
      textAliases: [],
      scope: "both",
      argsMenu: "auto",
      argsParsing: "positional",
      args: [
        {
          name: "level",
          description: "level",
          type: "string",
          choices: ({ provider, model, command, arg }) => {
            seen = { provider, model, commandKey: command.key, argName: arg.name };
            return ["low", "high"];
          },
        },
      ],
    };

    const menu = resolveCommandArgMenu({ command, args: undefined, cfg: {} as never });
    expect(menu?.arg.name).toBe("level");
    expect(menu?.choices).toEqual([
      { label: "low", value: "low" },
      { label: "high", value: "high" },
    ]);
    const seenChoice = seen as {
      provider?: string;
      model?: string;
      commandKey: string;
      argName: string;
    } | null;
    expect(seenChoice?.commandKey).toBe("think");
    expect(seenChoice?.argName).toBe("level");
    expect(seenChoice?.provider).toBeTruthy();
    expect(seenChoice?.model).toBeTruthy();
  });

  it("does not show menus when args were provided as raw text only", () => {
    const command = createUsageModeCommand("none", "on or off");

    const menu = resolveCommandArgMenu({
      command,
      args: { raw: "on" },
      cfg: {} as never,
    });
    expect(menu).toBeNull();
  });
});

¤ Dauer der Verarbeitung: 0.44 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge