Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Openclaw/src/auto-reply/reply/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 18 kB image not shown  

Quelle  commands-gating.test.ts

  Sprache: JAVA
 

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

import { beforeEach, describe, expect, it, vi } from "vitest";
import { isCommandFlagEnabled } from "../../config/commands.js";
import type { OpenClawConfig } from "../../config/config.js";
import type { MsgContext } from "../templating.js";
import { handleBashChatCommand } from "./bash-command.js";
import { handleConfigCommand, handleDebugCommand } from "./commands-config.js";
import type { HandleCommandsParams } from "./commands-types.js";
import { parseInlineDirectives } from "./directive-handling.parse.js";

const readConfigFileSnapshotMock = vi.hoisted(() =>
  vi.fn(async () => ({ valid: true, parsed: {} })),
);
const validateConfigObjectWithPluginsMock = vi.hoisted(() =>
  vi.fn(() => ({
    ok: true,
    config: {},
    issues: [],
  })),
);
const writeConfigFileMock = vi.hoisted(() => vi.fn(async () => undefined));
const getConfigOverridesMock = vi.hoisted(() => vi.fn(() => ({})));
const getConfigValueAtPathMock = vi.hoisted(() => vi.fn());
const parseConfigPathMock = vi.hoisted(() => vi.fn());
const setConfigValueAtPathMock = vi.hoisted(() => vi.fn());
const resolveConfigWriteDeniedTextMock = vi.hoisted(() =>
  vi.fn<(...args: never[]) => string | null>(() => null),
);
const isInternalMessageChannelMock = vi.hoisted(() => vi.fn(() => false));

vi.mock("../../agents/agent-scope.js", () => ({
  resolveSessionAgentId: vi.fn(() => "agent:main"),
}));

vi.mock("../../agents/bash-process-registry.js", () => ({
  getFinishedSession: vi.fn(() => undefined),
  getSession: vi.fn(() => undefined),
}));

vi.mock("../../agents/bash-tools.js", () => ({
  createExecTool: vi.fn(),
}));

vi.mock("../../agents/sandbox.js", () => ({
  resolveSandboxRuntimeStatus: vi.fn(() => ({ sandboxed: false })),
}));

vi.mock("../../globals.js", () => ({
  logVerbose: vi.fn(),
}));

vi.mock("../../utils.js", async () => {
  const actual = await vi.importActual<typeof import("../../utils.js")>("../../utils.js");
  return {
    ...actual,
    clampInt: vi.fn((value: number) => value),
  };
});

vi.mock("./mentions.js", () => ({
  stripMentions: vi.fn((value: string) => value),
  stripStructuralPrefixes: vi.fn((value: string) => value),
}));

vi.mock("../../channels/plugins/config-writes.js", () => ({
  resolveConfigWriteTargetFromPath: vi.fn(() => "config"),
}));

vi.mock("../../channels/registry.js", () => ({
  normalizeChannelId: vi.fn((value?: string) => value),
}));

vi.mock("../../config/config-paths.js", () => ({
  getConfigValueAtPath: getConfigValueAtPathMock,
  parseConfigPath: parseConfigPathMock,
  setConfigValueAtPath: setConfigValueAtPathMock,
  unsetConfigValueAtPath: vi.fn(() => true),
}));

vi.mock("../../config/config.js", () => ({
  readConfigFileSnapshot: readConfigFileSnapshotMock,
  validateConfigObjectWithPlugins: validateConfigObjectWithPluginsMock,
  writeConfigFile: writeConfigFileMock,
}));

vi.mock("../../config/runtime-overrides.js", () => ({
  getConfigOverrides: getConfigOverridesMock,
  resetConfigOverrides: vi.fn(),
  setConfigOverride: vi.fn(() => ({ ok: true })),
  unsetConfigOverride: vi.fn(() => ({ ok: true, removed: true })),
}));

vi.mock("../../utils/message-channel.js", () => ({
  isInternalMessageChannel: isInternalMessageChannelMock,
}));

vi.mock("./channel-context.js", () => ({
  resolveChannelAccountId: vi.fn(() => undefined),
}));

vi.mock("./config-commands.js", () => ({
  parseConfigCommand: vi.fn((raw: string) => {
    if (!raw.startsWith("/config")) {
      return null;
    }
    const parts = raw.trim().split(/\s+/);
    const action = parts[1];
    if (action === "show" || action === "get") {
      return { action: "show", path: parts.slice(2).join(" ") || undefined };
    }
    if (action === "set") {
      const assignment = raw.slice(raw.indexOf(" set ") + 5).trim();
      const equalsIndex = assignment.indexOf("=");
      return {
        action: "set",
        path: assignment.slice(0, equalsIndex),
        value: JSON.parse(assignment.slice(equalsIndex + 1)),
      };
    }
    if (action === "unset") {
      return { action: "unset", path: parts.slice(2).join(" ") };
    }
    return { action: "error", message: "Invalid /config syntax." };
  }),
}));

vi.mock("./config-write-authorization.js", () => ({
  resolveConfigWriteDeniedText: resolveConfigWriteDeniedTextMock,
}));

vi.mock("./debug-commands.js", () => ({
  parseDebugCommand: vi.fn((raw: string) => {
    if (!raw.startsWith("/debug")) {
      return null;
    }
    return { action: "show" };
  }),
}));

function buildParams(commandBody: string, cfg: OpenClawConfig): HandleCommandsParams {
  const ctx = {
    Body: commandBody,
    CommandBody: commandBody,
    CommandSource: "text",
    CommandAuthorized: true,
    Provider: "whatsapp",
    Surface: "whatsapp",
    SessionKey: "agent:main:main",
  } as MsgContext;

  return {
    ctx,
    cfg,
    command: {
      surface: "whatsapp",
      channel: "whatsapp",
      channelId: "whatsapp",
      ownerList: [],
      senderIsOwner: false,
      isAuthorizedSender: true,
      senderId: "user-1",
      rawBodyNormalized: commandBody.trim(),
      commandBodyNormalized: commandBody.trim(),
      from: "user-1",
      to: "bot-1",
    },
    directives: parseInlineDirectives(""),
    elevated: { enabled: true, allowed: true, failures: [] },
    sessionKey: "agent:main:main",
    workspaceDir: "/tmp",
    defaultGroupActivation: () => "mention",
    resolvedVerboseLevel: "off",
    resolvedReasoningLevel: "off",
    resolveDefaultThinkingLevel: async () => undefined,
    provider: "whatsapp",
    model: "test-model",
    contextTokens: 0,
    isGroup: false,
  };
}

describe("command gating", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    readConfigFileSnapshotMock.mockResolvedValue({ valid: true, parsed: {} });
    validateConfigObjectWithPluginsMock.mockReturnValue({
      ok: true,
      config: {},
      issues: [],
    });
    getConfigOverridesMock.mockReturnValue({});
    getConfigValueAtPathMock.mockImplementation((target: unknown, path: string[]) => {
      let current = target as Record<string, unknown> | undefined;
      for (const segment of path) {
        if (!current || typeof current !== "object") {
          return undefined;
        }
        current = current[segment] as Record<string, unknown> | undefined;
      }
      return current;
    });
    parseConfigPathMock.mockImplementation((raw: string) => ({
      ok: true,
      path: raw.split("."),
    }));
    setConfigValueAtPathMock.mockImplementation(
      (target: Record<string, unknown>, path: string[], value: unknown) => {
        let cursor: Record<string, unknown> = target;
        for (const segment of path.slice(0, -1)) {
          const next = cursor[segment];
          if (!next || typeof next !== "object") {
            cursor[segment] = {};
          }
          cursor = cursor[segment] as Record<string, unknown>;
        }
        const leaf = path[path.length - 1];
        if (leaf) {
          cursor[leaf] = value;
        }
      },
    );
  });

  it("blocks disabled bash", async () => {
    const result = await handleBashChatCommand({
      ctx: {
        Body: "/bash echo hi",
        CommandBody: "/bash echo hi",
        SessionKey: "agent:main:main",
      } as MsgContext,
      cfg: { commands: { bash: false } } as OpenClawConfig,
      sessionKey: "agent:main:main",
      isGroup: false,
      elevated: { enabled: true, allowed: true, failures: [] },
    });
    expect(result.text).toContain("bash is disabled");
  });

  it("blocks bash when elevated access is unavailable", async () => {
    const result = await handleBashChatCommand({
      ctx: {
        Body: "/bash echo hi",
        CommandBody: "/bash echo hi",
        SessionKey: "agent:main:main",
      } as MsgContext,
      cfg: { commands: { bash: true } } as OpenClawConfig,
      sessionKey: "agent:main:main",
      isGroup: false,
      elevated: {
        enabled: true,
        allowed: false,
        failures: [{ gate: "allowFrom", key: "tools.elevated.allowFrom.whatsapp" }],
      },
    });
    expect(result.text).toContain("elevated is not available");
  });

  it("blocks disabled config", async () => {
    const params = buildParams("/config show", {
      commands: { config: false, debug: false, text: true },
      channels: { whatsapp: { allowFrom: ["*"] } },
    } as OpenClawConfig);
    params.command.senderIsOwner = true;
    const result = await handleConfigCommand(params, true);
    expect(result?.reply?.text).toContain("/config is disabled");
  });

  it("blocks disabled debug", async () => {
    const params = buildParams("/debug show", {
      commands: { config: false, debug: false, text: true },
      channels: { whatsapp: { allowFrom: ["*"] } },
    } as OpenClawConfig);
    params.command.senderIsOwner = true;
    const result = await handleDebugCommand(params, true);
    expect(result?.reply?.text).toContain("/debug is disabled");
  });

  it("blocks authorized non-owners from /config show and /debug show", async () => {
    const configParams = buildParams("/config show", {
      commands: { config: true, text: true },
      channels: { whatsapp: { allowFrom: ["*"] } },
    } as OpenClawConfig);
    const configResult = await handleConfigCommand(configParams, true);
    expect(configResult).toEqual({ shouldContinue: false });

    const debugParams = buildParams("/debug show", {
      commands: { debug: true, text: true },
      channels: { whatsapp: { allowFrom: ["*"] } },
    } as OpenClawConfig);
    const debugResult = await handleDebugCommand(debugParams, true);
    expect(debugResult).toEqual({ shouldContinue: false });
  });

  it("keeps /config show and /debug show available for owners", async () => {
    readConfigFileSnapshotMock.mockResolvedValueOnce({
      valid: true,
      parsed: { messages: { ackReaction: ":)" } },
    });
    const configParams = buildParams("/config show messages.ackReaction", {
      commands: { config: true, text: true },
      channels: { whatsapp: { allowFrom: ["*"] } },
    } as OpenClawConfig);
    configParams.command.senderIsOwner = true;
    const configResult = await handleConfigCommand(configParams, true);
    expect(configResult?.reply?.text).toContain("⚙️ Config");
    expect(configResult?.reply?.text).toContain('":)"');

    const debugParams = buildParams("/debug show", {
      commands: { debug: true, text: true },
      channels: { whatsapp: { allowFrom: ["*"] } },
    } as OpenClawConfig);
    debugParams.command.senderIsOwner = true;
    const debugResult = await handleDebugCommand(debugParams, true);
    expect(debugResult?.reply?.text).toContain("Debug overrides");
  });

  it("returns explicit unauthorized replies for native privileged commands", async () => {
    const configParams = buildParams("/config show", {
      commands: { config: true, text: true },
      channels: { telegram: { allowFrom: ["*"] } },
    } as OpenClawConfig);
    configParams.ctx.CommandSource = "native";
    configParams.command.channel = "telegram";
    configParams.command.channelId = "telegram";
    configParams.command.surface = "telegram";
    const configResult = await handleConfigCommand(configParams, true);
    expect(configResult).toEqual({
      shouldContinue: false,
      reply: { text: "You are not authorized to use this command." },
    });

    const debugParams = buildParams("/debug show", {
      commands: { debug: true, text: true },
      channels: { telegram: { allowFrom: ["*"] } },
    } as OpenClawConfig);
    debugParams.ctx.CommandSource = "native";
    debugParams.command.channel = "telegram";
    debugParams.command.channelId = "telegram";
    debugParams.command.surface = "telegram";
    const debugResult = await handleDebugCommand(debugParams, true);
    expect(debugResult).toEqual({
      shouldContinue: false,
      reply: { text: "You are not authorized to use this command." },
    });
  });

  it("ignores inherited command flags", () => {
    const inheritedCommands = Object.create({
      bash: true,
      config: true,
      debug: true,
    }) as Record<string, unknown>;
    const cfg = { commands: inheritedCommands as never } as OpenClawConfig;
    expect(isCommandFlagEnabled(cfg, "bash")).toBe(false);
    expect(isCommandFlagEnabled(cfg, "config")).toBe(false);
    expect(isCommandFlagEnabled(cfg, "debug")).toBe(false);
  });

  it("blocks disallowed /config set writes", async () => {
    resolveConfigWriteDeniedTextMock
      .mockReturnValueOnce("Config writes are disabled")
      .mockReturnValueOnce("channels.telegram.accounts.work.configWrites=true")
      .mockReturnValueOnce("cannot replace channels, channel roots, or accounts collections");

    const cases = [
      {
        name: "channel config writes disabled",
        params: (() => {
          const params = buildParams('/config set messages.ackReaction=":)"', {
            commands: { config: true, text: true },
            channels: { whatsapp: { allowFrom: ["*"], configWrites: false } },
          } as OpenClawConfig);
          params.command.senderIsOwner = true;
          return params;
        })(),
        expectedText: "Config writes are disabled",
      },
      {
        name: "target account disables writes",
        params: (() => {
          const params = buildParams("/config set channels.telegram.accounts.work.enabled=false", {
            commands: { config: true, text: true },
            channels: {
              telegram: {
                configWrites: true,
                accounts: {
                  work: { configWrites: false, enabled: true },
                },
              },
            },
          } as OpenClawConfig);
          params.ctx.Provider = "telegram";
          params.ctx.Surface = "telegram";
          params.command.channel = "telegram";
          params.command.channelId = "telegram";
          params.command.surface = "telegram";
          params.command.senderIsOwner = true;
          return params;
        })(),
        expectedText: "channels.telegram.accounts.work.configWrites=true",
      },
      {
        name: "ambiguous channel-root write",
        params: (() => {
          const params = buildParams('/config set channels.telegram={"enabled":false}', {
            commands: { config: true, text: true },
            channels: { telegram: { configWrites: true } },
          } as OpenClawConfig);
          params.ctx.Provider = "telegram";
          params.ctx.Surface = "telegram";
          params.command.channel = "telegram";
          params.command.channelId = "telegram";
          params.command.surface = "telegram";
          params.command.senderIsOwner = true;
          return params;
        })(),
        expectedText: "cannot replace channels, channel roots, or accounts collections",
      },
    ] as const;

    for (const testCase of cases) {
      const previousWriteCount = writeConfigFileMock.mock.calls.length;
      const result = await handleConfigCommand(testCase.params, true);
      expect(result?.shouldContinue).toBe(false);
      expect(result?.reply?.text).toContain(testCase.expectedText);
      expect(writeConfigFileMock.mock.calls.length).toBe(previousWriteCount);
    }
  });

  it("honors the configured default account when gating omitted-account /config writes", async () => {
    resolveConfigWriteDeniedTextMock.mockReturnValueOnce(
      "channels.telegram.accounts.work.configWrites=true",
    );
    const params = buildParams('/config set messages.ackReaction=":)"', {
      commands: { config: true, text: true },
      channels: {
        telegram: {
          defaultAccount: "work",
          configWrites: true,
          accounts: {
            work: { configWrites: false, enabled: true },
          },
        },
      },
    } as OpenClawConfig);
    params.ctx.Provider = "telegram";
    params.ctx.Surface = "telegram";
    params.command.channel = "telegram";
    params.command.channelId = "telegram";
    params.command.surface = "telegram";
    params.command.senderIsOwner = true;

    const previousWriteCount = writeConfigFileMock.mock.calls.length;
    const result = await handleConfigCommand(params, true);

    expect(result?.shouldContinue).toBe(false);
    expect(result?.reply?.text).toContain("channels.telegram.accounts.work.configWrites=true");
    expect(writeConfigFileMock.mock.calls.length).toBe(previousWriteCount);
  });

  it("enforces gateway client permissions for /config commands", async () => {
    const baseCfg = { commands: { config: true, text: true } } as OpenClawConfig;

    const blockedParams = buildParams('/config set messages.ackReaction=":)"', baseCfg);
    blockedParams.ctx.Provider = "webchat";
    blockedParams.ctx.Surface = "webchat";
    blockedParams.ctx.GatewayClientScopes = ["operator.write"];
    blockedParams.command.channel = "webchat";
    blockedParams.command.channelId = "webchat";
    blockedParams.command.surface = "webchat";
    blockedParams.command.senderIsOwner = true;
    isInternalMessageChannelMock.mockReturnValueOnce(true);
    const blockedResult = await handleConfigCommand(blockedParams, true);
    expect(blockedResult?.shouldContinue).toBe(false);
    expect(blockedResult?.reply?.text).toContain("requires operator.admin");

    readConfigFileSnapshotMock.mockResolvedValueOnce({
      valid: true,
      parsed: { messages: { ackReaction: ":)" } },
    });
    const showParams = buildParams("/config show messages.ackReaction", baseCfg);
    showParams.ctx.Provider = "webchat";
    showParams.ctx.Surface = "webchat";
    showParams.ctx.GatewayClientScopes = ["operator.write"];
    showParams.command.channel = "webchat";
    showParams.command.channelId = "webchat";
    showParams.command.surface = "webchat";
    isInternalMessageChannelMock.mockReturnValueOnce(true);
    const showResult = await handleConfigCommand(showParams, true);
    expect(showResult?.shouldContinue).toBe(false);
    expect(showResult?.reply?.text).toContain("Config messages.ackReaction");

    readConfigFileSnapshotMock.mockResolvedValueOnce({
      valid: true,
      parsed: { messages: { ackReaction: ":)" } },
    });
    const setParams = buildParams('/config set messages.ackReaction=":D"', baseCfg);
    setParams.ctx.Provider = "webchat";
    setParams.ctx.Surface = "webchat";
    setParams.ctx.GatewayClientScopes = ["operator.write", "operator.admin"];
    setParams.command.channel = "webchat";
    setParams.command.channelId = "webchat";
    setParams.command.surface = "webchat";
    setParams.command.senderIsOwner = true;
    isInternalMessageChannelMock.mockReturnValueOnce(true);
    const setResult = await handleConfigCommand(setParams, true);
    expect(setResult?.shouldContinue).toBe(false);
    expect(setResult?.reply?.text).toContain("Config updated");
    expect(writeConfigFileMock).toHaveBeenCalled();
  });
});

¤ Dauer der Verarbeitung: 0.21 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.