
























11// Discord tests cover native command.options plugin behavior.
22import { ApplicationCommandType, ChannelType, InteractionContextType } from "discord-api-types/v10";
33import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
4-import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
4+import {
5+clearRuntimeConfigSnapshot,
6+setRuntimeConfigSnapshot,
7+} from "openclaw/plugin-sdk/runtime-config-snapshot";
8+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
59610const { logVerboseMock } = vi.hoisted(() => ({
711logVerboseMock: vi.fn(),
@@ -222,10 +226,15 @@ describe("createDiscordNativeCommand option wiring", () => {
222226});
223227224228beforeEach(() => {
229+clearRuntimeConfigSnapshot();
225230logVerboseMock.mockReset();
226231loggerWarnMock.mockReset();
227232});
228233234+afterEach(() => {
235+clearRuntimeConfigSnapshot();
236+});
237+229238it("uses autocomplete for /acp action so inline action values are accepted", async () => {
230239const command = createNativeCommand("acp");
231240const action = requireOption(command, "action");
@@ -399,6 +408,78 @@ describe("createDiscordNativeCommand option wiring", () => {
399408}
400409});
401410411+it("refreshes autocomplete authorization and dynamic choices between invocations", async () => {
412+const restoreMatchPluginCommand = nativeCommandTesting.setMatchPluginCommand((prompt) =>
413+prompt === "/scope" ? ({ command: { name: "scope" }, args: "" } as never) : null,
414+);
415+const sourceCfg = {
416+session: { dmScope: "main" },
417+channels: {
418+discord: {
419+dm: { enabled: true, policy: "disabled" },
420+},
421+},
422+} as OpenClawConfig;
423+const runtimeCfg = {
424+session: { dmScope: "per-channel-peer" },
425+channels: {
426+discord: {
427+dm: { enabled: true, policy: "open", allowFrom: ["*"] },
428+},
429+},
430+} as OpenClawConfig;
431+try {
432+const command = createDiscordNativeCommand({
433+command: {
434+name: "scope",
435+description: "Scope",
436+acceptsArgs: true,
437+args: [
438+{
439+name: "value",
440+description: "Scope value",
441+type: "string",
442+preferAutocomplete: true,
443+choices: ({ cfg }) => {
444+const dmScope = cfg?.session?.dmScope ?? "missing";
445+return [{ label: dmScope, value: dmScope }];
446+},
447+},
448+],
449+},
450+cfg: sourceCfg,
451+discordConfig: sourceCfg.channels?.discord ?? {},
452+accountId: "default",
453+sessionPrefix: "discord:slash",
454+ephemeralDefault: true,
455+threadBindings: createNoopThreadBindingManager("default"),
456+});
457+const value = requireOption(command, "value");
458+const autocomplete = requireAutocomplete(
459+value,
460+"scope value option did not wire autocomplete",
461+);
462+const autocompleteParams = {
463+userId: "owner",
464+channelType: ChannelType.DM,
465+channelId: "dm-1",
466+channelName: "dm-1",
467+focusedValue: "",
468+} as const;
469+470+const blockedRespond = await runAutocomplete(autocomplete, autocompleteParams);
471+expect(blockedRespond).toHaveBeenCalledWith([]);
472+473+setRuntimeConfigSnapshot(runtimeCfg, runtimeCfg);
474+const refreshedRespond = await runAutocomplete(autocomplete, autocompleteParams);
475+expect(refreshedRespond).toHaveBeenCalledWith([
476+{ name: "per-channel-peer", value: "per-channel-peer" },
477+]);
478+} finally {
479+nativeCommandTesting.setMatchPluginCommand(restoreMatchPluginCommand);
480+}
481+});
482+402483it("returns no autocomplete choices outside the Discord allowlist when commands.useAccessGroups is false and commands.allowFrom is not configured", async () => {
403484const command = createNativeCommand("think", {
404485cfg: {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。