fix(qa): restore release channel reply checks

This commit is contained in:
Peter Steinberger
2026-04-28 21:05:30 +01:00
parent 3aac8e650c
commit 96a21e2553
14 changed files with 120 additions and 6 deletions

View File

@@ -0,0 +1,9 @@
import { describe, expect, it } from "vitest";
import setupEntry from "./setup-entry.js";
describe("qa-channel setup entry", () => {
it("exposes the bundled setup-entry contract", () => {
expect(setupEntry.kind).toBe("bundled-channel-setup-entry");
expect(setupEntry.loadSetupPlugin().id).toBe("qa-channel");
});
});

View File

@@ -1,4 +1,13 @@
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
import { qaChannelPlugin } from "./src/channel.js";
import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract";
export default defineSetupPluginEntry(qaChannelPlugin);
export default defineBundledChannelSetupEntry({
importMetaUrl: import.meta.url,
plugin: {
specifier: "./channel-plugin-api.js",
exportName: "qaChannelPlugin",
},
runtime: {
specifier: "./api.js",
exportName: "setQaChannelRuntime",
},
});

View File

@@ -1,5 +1,17 @@
import { describe, expect, it } from "vitest";
import { isHttpMediaUrl } from "./inbound.js";
import { createPluginRuntimeMock } from "openclaw/plugin-sdk/channel-test-helpers";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { setQaChannelRuntime } from "../api.js";
import { handleQaInbound, isHttpMediaUrl } from "./inbound.js";
const dispatchInboundReplyWithBaseMock = vi.hoisted(() => vi.fn());
vi.mock("openclaw/plugin-sdk/inbound-reply-dispatch", () => ({
dispatchInboundReplyWithBase: dispatchInboundReplyWithBaseMock,
}));
beforeEach(() => {
dispatchInboundReplyWithBaseMock.mockReset();
});
describe("isHttpMediaUrl", () => {
it("accepts only http and https urls", () => {
@@ -10,3 +22,45 @@ describe("isHttpMediaUrl", () => {
expect(isHttpMediaUrl("data:text/plain;base64,SGVsbG8=")).toBe(false);
});
});
describe("handleQaInbound", () => {
it("marks group messages that match configured mention patterns", async () => {
const runtime = createPluginRuntimeMock();
vi.mocked(runtime.channel.mentions.buildMentionRegexes).mockReturnValue([/\b@?openclaw\b/i]);
setQaChannelRuntime(runtime);
await handleQaInbound({
channelId: "qa-channel",
channelLabel: "QA Channel",
account: {
accountId: "default",
enabled: true,
configured: true,
baseUrl: "http://127.0.0.1:43123",
botUserId: "openclaw",
botDisplayName: "OpenClaw QA",
pollTimeoutMs: 250,
config: {},
},
config: {},
message: {
id: "msg-1",
accountId: "default",
direction: "inbound",
conversation: {
kind: "channel",
id: "qa-room",
title: "QA Room",
},
senderId: "alice",
senderName: "Alice",
text: "@openclaw ping",
timestamp: 1_777_000_000_000,
reactions: [],
},
});
expect(dispatchInboundReplyWithBaseMock).toHaveBeenCalledTimes(1);
expect(dispatchInboundReplyWithBaseMock.mock.calls[0]?.[0].ctxPayload.WasMentioned).toBe(true);
});
});

View File

@@ -81,6 +81,16 @@ export async function handleQaInbound(params: {
id: target,
},
});
const isGroup = inbound.conversation.kind !== "direct";
const wasMentioned = isGroup
? runtime.channel.mentions.matchesMentionPatterns(
inbound.text,
runtime.channel.mentions.buildMentionRegexes(
params.config as OpenClawConfig,
route.agentId,
),
)
: undefined;
const storePath = runtime.channel.session.resolveStorePath(params.config.session?.store, {
agentId: route.agentId,
});
@@ -111,6 +121,7 @@ export async function handleQaInbound(params: {
SessionKey: route.sessionKey,
AccountId: route.accountId ?? params.account.accountId,
ChatType: inbound.conversation.kind === "direct" ? "direct" : "group",
WasMentioned: wasMentioned,
ConversationLabel:
inbound.threadTitle ||
inbound.conversation.title ||

View File

@@ -134,6 +134,7 @@ describe("discord live qa runtime", () => {
expect(next.plugins?.allow).toContain("discord");
expect(next.plugins?.entries?.discord).toEqual({ enabled: true });
expect(next.messages?.groupChat?.visibleReplies).toBe("automatic");
expect(next.channels?.discord).toEqual({
enabled: true,
defaultAccount: "sut",

View File

@@ -287,6 +287,13 @@ function buildDiscordQaConfig(
allow: pluginAllow,
entries: pluginEntries,
},
messages: {
...baseCfg.messages,
groupChat: {
...baseCfg.messages?.groupChat,
visibleReplies: "automatic",
},
},
channels: {
...baseCfg.channels,
discord: {

View File

@@ -165,6 +165,7 @@ describe("telegram live qa runtime", () => {
expect(next.agents?.defaults?.skipBootstrap).toBe(true);
expect(next.plugins?.allow).toContain("telegram");
expect(next.plugins?.entries?.telegram).toEqual({ enabled: true });
expect(next.messages?.groupChat?.visibleReplies).toBe("automatic");
expect(next.channels?.telegram).toEqual({
enabled: true,
defaultAccount: "sut",

View File

@@ -486,6 +486,13 @@ function buildTelegramQaConfig(
allow: pluginAllow,
entries: pluginEntries,
},
messages: {
...baseCfg.messages,
groupChat: {
...baseCfg.messages?.groupChat,
visibleReplies: "automatic",
},
},
channels: {
...baseCfg.channels,
telegram: {

View File

@@ -24,6 +24,7 @@ describe("qa channel transport", () => {
messages: {
groupChat: {
mentionPatterns: ["\\b@?openclaw\\b"],
visibleReplies: "automatic",
},
},
});

View File

@@ -90,6 +90,7 @@ export function createQaChannelGatewayConfig(params: {
messages: {
groupChat: {
mentionPatterns: ["\\b@?openclaw\\b"],
visibleReplies: "automatic",
},
},
};

View File

@@ -23,6 +23,7 @@ function createQaChannelTransportParams(baseUrl = "http://127.0.0.1:43124") {
messages: {
groupChat: {
mentionPatterns: ["\\b@?openclaw\\b"],
visibleReplies: "automatic",
},
},
} satisfies QaTransportGatewayConfig,
@@ -77,7 +78,10 @@ describe("buildQaGatewayConfig", () => {
baseUrl: "http://127.0.0.1:43124",
pollTimeoutMs: 250,
});
expect(cfg.messages?.groupChat?.mentionPatterns).toEqual(["\\b@?openclaw\\b"]);
expect(cfg.messages?.groupChat).toMatchObject({
mentionPatterns: ["\\b@?openclaw\\b"],
visibleReplies: "automatic",
});
});
it("maps provider-qualified openai and anthropic refs through the mock provider lane", () => {

View File

@@ -148,6 +148,7 @@ describe("matrix live qa runtime", () => {
expect(next.plugins?.allow).toContain("matrix");
expect(next.plugins?.entries?.matrix).toEqual({ enabled: true });
expect(next.messages?.groupChat?.visibleReplies).toBe("automatic");
expect(next.channels?.matrix).toEqual({
enabled: true,
defaultAccount: "sut",

View File

@@ -76,6 +76,7 @@ describe("matrix qa config", () => {
replyToMode: "off",
threadReplies: "inbound",
});
expect(next.messages?.groupChat?.visibleReplies).toBe("automatic");
});
it("applies room-keyed Matrix QA config overrides", () => {

View File

@@ -640,6 +640,13 @@ export function buildMatrixQaConfig(
matrix: { enabled: true },
},
},
messages: {
...baseCfg.messages,
groupChat: {
...baseCfg.messages?.groupChat,
visibleReplies: "automatic",
},
},
channels: {
...baseCfg.channels,
matrix: {