fix(mattermost): suppress reasoning-only replies

This commit is contained in:
lawrence3699
2026-04-22 11:40:45 +10:00
committed by Peter Steinberger
parent 115accfc82
commit 367faac596
2 changed files with 89 additions and 0 deletions

View File

@@ -38,6 +38,78 @@ function createReplyDeliveryCore(): DeliverMattermostReplyPayloadParams["core"]
}
describe("deliverMattermostReplyPayload", () => {
it("suppresses payloads flagged as reasoning", async () => {
const sendMessage = vi.fn(async () => undefined);
const cfg = {} satisfies OpenClawConfig;
const core = createReplyDeliveryCore();
await deliverMattermostReplyPayload({
core,
cfg,
payload: { text: "Reasoning:\n_hidden_", isReasoning: true },
to: "channel:town-square",
accountId: "default",
agentId: "agent-1",
replyToId: "root-post",
textLimit: 4000,
tableMode: "off",
sendMessage,
});
expect(sendMessage).not.toHaveBeenCalled();
});
it("suppresses reasoning-prefixed payloads even without an explicit flag", async () => {
const sendMessage = vi.fn(async () => undefined);
const cfg = {} satisfies OpenClawConfig;
const core = createReplyDeliveryCore();
await deliverMattermostReplyPayload({
core,
cfg,
payload: { text: " \n Reasoning:\n_hidden_" },
to: "channel:town-square",
accountId: "default",
agentId: "agent-1",
replyToId: "root-post",
textLimit: 4000,
tableMode: "off",
sendMessage,
});
expect(sendMessage).not.toHaveBeenCalled();
});
it("does not suppress messages that mention Reasoning: mid-text", async () => {
const sendMessage = vi.fn(async () => undefined);
const cfg = {} satisfies OpenClawConfig;
const core = createReplyDeliveryCore();
await deliverMattermostReplyPayload({
core,
cfg,
payload: { text: "Intro line\nReasoning: appears in content but is not a prefix" },
to: "channel:town-square",
accountId: "default",
agentId: "agent-1",
replyToId: "root-post",
textLimit: 4000,
tableMode: "off",
sendMessage,
});
expect(sendMessage).toHaveBeenCalledTimes(1);
expect(sendMessage).toHaveBeenCalledWith(
"channel:town-square",
"Intro line\nReasoning: appears in content but is not a prefix",
expect.objectContaining({
cfg,
accountId: "default",
replyToId: "root-post",
}),
);
});
it("passes agent-scoped mediaLocalRoots when sending media paths", async () => {
const previousStateDir = process.env.OPENCLAW_STATE_DIR;
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mm-state-"));

View File

@@ -2,6 +2,7 @@ import {
deliverTextOrMediaReply,
resolveSendableOutboundReplyParts,
} from "openclaw/plugin-sdk/reply-payload";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
getAgentScopedMediaLocalRoots,
type OpenClawConfig,
@@ -23,6 +24,19 @@ type SendMattermostMessage = (
},
) => Promise<unknown>;
const REASONING_PREFIX = "reasoning:";
function shouldSuppressReasoningReply(payload: ReplyPayload): boolean {
if (payload.isReasoning === true) {
return true;
}
const text = payload.text;
if (typeof text !== "string") {
return false;
}
return normalizeLowercaseStringOrEmpty(text.trimStart()).startsWith(REASONING_PREFIX);
}
export async function deliverMattermostReplyPayload(params: {
core: PluginRuntime;
cfg: OpenClawConfig;
@@ -35,6 +49,9 @@ export async function deliverMattermostReplyPayload(params: {
tableMode: MarkdownTableMode;
sendMessage: SendMattermostMessage;
}): Promise<void> {
if (shouldSuppressReasoningReply(params.payload)) {
return;
}
const reply = resolveSendableOutboundReplyParts(params.payload, {
text: params.core.channel.text.convertMarkdownTables(
params.payload.text ?? "",