fix(qqbot): sanitize outbound text to strip reasoning/thinking content (#90132)

Summary:
- Adds QQBot outbound `sanitizeText` wired to `sanitizeAssistantVisibleText` plus a regression test for stripping `<thinking>` and `<think>` blocks.
- PR surface: Source +2, Tests +19. Total +21 across 2 files.
- Reproducibility: yes. source-reproducible: current main QQBot outbound lacks `sanitizeText`, and shared deli ... nnel text sanitization when that hook exists. I did not run a live Tencent QQBot plus MiniMax reproduction.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(qqbot): add curly braces for eslint(curly) compliance

Validation:
- ClawSweeper review passed for head 17cf140183.
- Required merge gates passed before the squash merge.

Prepared head SHA: 17cf140183
Review: https://github.com/openclaw/openclaw/pull/90132#issuecomment-4618527026

Co-authored-by: openperf <16864032@qq.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
This commit is contained in:
Chunyue Wang
2026-06-05 14:57:16 +08:00
committed by GitHub
parent 560b77a4af
commit 1a3ce7c2a8
2 changed files with 21 additions and 0 deletions

View File

@@ -4,6 +4,25 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { describe, expect, it, vi } from "vitest";
import { qqbotPlugin } from "./channel.js";
describe("qqbot outbound sanitizeText", () => {
it("strips reasoning/thinking tags before delivery", () => {
const sanitize = qqbotPlugin.outbound?.sanitizeText;
expect(sanitize).toBeDefined();
if (!sanitize) {
return;
}
const input1 = "<thinking>internal reasoning</thinking>final answer";
expect(sanitize({ text: input1, payload: { text: input1 } })).toBe("final answer");
const input2 = "<think>step by step</think>result";
expect(sanitize({ text: input2, payload: { text: input2 } })).toBe("result");
const input3 = "plain text without tags";
expect(sanitize({ text: input3, payload: { text: input3 } })).toBe("plain text without tags");
});
});
const sendTextMock = vi.hoisted(() => vi.fn());
const sendMediaMock = vi.hoisted(() => vi.fn());

View File

@@ -8,6 +8,7 @@ import {
} from "openclaw/plugin-sdk/channel-outbound";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
// Register the PlatformAdapter before any core/ module is used.
import "./bridge/bootstrap.js";
import { getQQBotApprovalCapability } from "./bridge/approval/capability.js";
@@ -254,6 +255,7 @@ export const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount> = {
chunker: (text, limit) => getQQBotRuntime().channel.text.chunkMarkdownText(text, limit),
chunkerMode: "markdown",
textChunkLimit: 5000,
sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text),
shouldSuppressLocalPayloadPrompt: ({ cfg, accountId, payload, hint }) =>
shouldSuppressLocalQQBotApprovalPrompt({
cfg,