

























@@ -3,7 +3,7 @@ import type { PluginRuntime } from "openclaw/plugin-sdk/core";
33import { describe, expect, it, vi } from "vitest";
44import { handleClickClackInbound } from "./inbound.js";
55import { setClickClackRuntime } from "./runtime.js";
6-import type { CoreConfig, ResolvedClickClackAccount } from "./types.js";
6+import type { ClickClackMessage, CoreConfig, ResolvedClickClackAccount } from "./types.js";
7788const sendClickClackTextMock = vi.hoisted(() => vi.fn());
99@@ -72,6 +72,58 @@ function createRuntime(): PluginRuntime {
7272} as unknown as PluginRuntime);
7373}
747475+function createAgentAccount(
76+overrides: Partial<ResolvedClickClackAccount> = {},
77+): ResolvedClickClackAccount {
78+const base = {
79+accountId: "default",
80+enabled: true,
81+configured: true,
82+baseUrl: "http://127.0.0.1:8080",
83+token: "ccb_default",
84+workspace: "wsp_1",
85+replyMode: "agent",
86+toolsAllow: [],
87+defaultTo: "channel:general",
88+allowFrom: ["*"],
89+reconnectMs: 1_500,
90+config: {
91+allowFrom: ["*"],
92+},
93+} satisfies ResolvedClickClackAccount;
94+95+return {
96+ ...base,
97+ ...overrides,
98+config: {
99+ ...base.config,
100+ ...overrides.config,
101+},
102+};
103+}
104+105+function createMessage(overrides: Partial<ClickClackMessage> = {}): ClickClackMessage {
106+return {
107+id: "msg_1",
108+workspace_id: "wsp_1",
109+channel_id: "chn_1",
110+author_id: "usr_owner",
111+thread_root_id: "msg_1",
112+body: "/fast on",
113+body_format: "markdown",
114+created_at: "2026-05-09T12:00:00.000Z",
115+author: {
116+id: "usr_owner",
117+kind: "human",
118+display_name: "Peter",
119+handle: "steipete",
120+avatar_url: "",
121+created_at: "2026-05-09T12:00:00.000Z",
122+},
123+ ...overrides,
124+};
125+}
126+75127describe("handleClickClackInbound", () => {
76128it("runs model-mode bot accounts without tools and posts the bot reply", async () => {
77129sendClickClackTextMock.mockReset();
@@ -139,4 +191,95 @@ describe("handleClickClackInbound", () => {
139191expect(sendRequest?.text).toBe("service bot online");
140192expect(sendRequest?.replyToId).toBe("msg_1");
141193});
194+195+it("marks agent turns command-authorized for allowlisted senders", async () => {
196+const runtime = createRuntime();
197+vi.mocked(runtime.channel.commands.shouldComputeCommandAuthorized).mockReturnValue(true);
198+setClickClackRuntime(runtime);
199+const cfg = {
200+agents: {
201+defaults: {
202+model: "openai/gpt-5.4-mini",
203+},
204+},
205+} satisfies CoreConfig;
206+207+await handleClickClackInbound({
208+account: createAgentAccount({
209+allowFrom: ["usr_owner"],
210+config: { allowFrom: ["usr_owner"] },
211+}),
212+config: cfg,
213+message: createMessage(),
214+});
215+216+const runPrepared = vi.mocked(runtime.channel.turn.runPrepared);
217+expect(runPrepared).toHaveBeenCalledTimes(1);
218+expect(runPrepared.mock.calls[0]?.[0].ctxPayload.CommandAuthorized).toBe(true);
219+});
220+221+it("accepts ClickClack DM target syntax in allowFrom", async () => {
222+const runtime = createRuntime();
223+vi.mocked(runtime.channel.commands.shouldComputeCommandAuthorized).mockReturnValue(true);
224+setClickClackRuntime(runtime);
225+const cfg = {
226+agents: {
227+defaults: {
228+model: "openai/gpt-5.4-mini",
229+},
230+},
231+} satisfies CoreConfig;
232+233+await handleClickClackInbound({
234+account: createAgentAccount({
235+allowFrom: ["dm:usr_owner"],
236+config: { allowFrom: ["dm:usr_owner"] },
237+}),
238+config: cfg,
239+message: createMessage({
240+channel_id: undefined,
241+direct_conversation_id: "dcn_1",
242+}),
243+});
244+245+const runPrepared = vi.mocked(runtime.channel.turn.runPrepared);
246+expect(runPrepared).toHaveBeenCalledTimes(1);
247+expect(runPrepared.mock.calls[0]?.[0].ctxPayload.ChatType).toBe("direct");
248+expect(runPrepared.mock.calls[0]?.[0].ctxPayload.CommandAuthorized).toBe(true);
249+});
250+251+it("does not dispatch agent turns from senders outside allowFrom", async () => {
252+const runtime = createRuntime();
253+vi.mocked(runtime.channel.commands.shouldComputeCommandAuthorized).mockReturnValue(true);
254+setClickClackRuntime(runtime);
255+const cfg = {
256+agents: {
257+defaults: {
258+model: "openai/gpt-5.4-mini",
259+},
260+},
261+} satisfies CoreConfig;
262+263+await handleClickClackInbound({
264+account: createAgentAccount({
265+allowFrom: ["usr_owner"],
266+config: { allowFrom: ["usr_owner"] },
267+}),
268+config: cfg,
269+message: createMessage({
270+author_id: "usr_attacker",
271+author: {
272+id: "usr_attacker",
273+kind: "human",
274+display_name: "Attacker",
275+handle: "attacker",
276+avatar_url: "",
277+created_at: "2026-05-09T12:00:00.000Z",
278+},
279+}),
280+});
281+282+expect(runtime.channel.turn.runPrepared).not.toHaveBeenCalled();
283+expect(runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
284+});
142285});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。