mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 08:29:49 +00:00
fix: suppress reasoning payload leakage in whatsapp replies
This commit is contained in:
@@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Security/Voice Call: add Telnyx webhook replay detection and canonicalize replay-key signature encoding (Base64/Base64URL equivalent forms dedupe together), so duplicate signed webhook deliveries no longer re-trigger side effects. (#25832) Thanks @bmendonca3.
|
||||
- Providers/OpenRouter/Auth profiles: bypass auth-profile cooldown/disable windows for OpenRouter, so provider failures no longer put OpenRouter profiles into local cooldown and stale legacy cooldown markers are ignored in fallback and status selection paths. (#25892) Thanks @alexanderatallah for raising this and @vincentkoc for the fix.
|
||||
- WhatsApp/Web reconnect: treat close status `440` as non-retryable (including string-form status values), stop reconnect loops immediately, and emit operator guidance to relink after resolving session conflicts. (#25858) Thanks @markmusson.
|
||||
- WhatsApp/Reasoning safety: suppress outbound payloads marked as reasoning and hard-drop text payloads that begin with `Reasoning:` before WhatsApp delivery, preventing hidden thinking blocks from leaking to end users through final-message paths. (#25804, #25214, #24328)
|
||||
- Onboarding/Telegram: keep core-channel onboarding available when plugin registry population is missing by falling back to built-in adapters and continuing wizard setup with actionable recovery guidance. (#25803) Thanks @Suko.
|
||||
- Models/Bedrock auth: normalize additional Bedrock provider aliases (`bedrock`, `aws-bedrock`, `aws_bedrock`, `amazon bedrock`) to canonical `amazon-bedrock`, ensuring auth-mode resolution consistently selects AWS SDK fallback. (#25756) Thanks @fwhite13.
|
||||
- Providers/SiliconFlow: normalize `thinking="off"` to `thinking: null` for `Pro/*` model payloads to avoid provider-side 400 loops and misleading compaction retries. (#25435) Thanks @Zjianru.
|
||||
|
||||
@@ -70,6 +70,56 @@ const replyLogger = {
|
||||
};
|
||||
|
||||
describe("deliverWebReply", () => {
|
||||
it("suppresses payloads flagged as reasoning", async () => {
|
||||
const msg = makeMsg();
|
||||
|
||||
await deliverWebReply({
|
||||
replyResult: { text: "Reasoning:\n_hidden_", isReasoning: true },
|
||||
msg,
|
||||
maxMediaBytes: 1024 * 1024,
|
||||
textLimit: 200,
|
||||
replyLogger,
|
||||
skipLog: true,
|
||||
});
|
||||
|
||||
expect(msg.reply).not.toHaveBeenCalled();
|
||||
expect(msg.sendMedia).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("suppresses payloads that start with reasoning prefix text", async () => {
|
||||
const msg = makeMsg();
|
||||
|
||||
await deliverWebReply({
|
||||
replyResult: { text: " \n Reasoning:\n_hidden_" },
|
||||
msg,
|
||||
maxMediaBytes: 1024 * 1024,
|
||||
textLimit: 200,
|
||||
replyLogger,
|
||||
skipLog: true,
|
||||
});
|
||||
|
||||
expect(msg.reply).not.toHaveBeenCalled();
|
||||
expect(msg.sendMedia).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not suppress messages that mention Reasoning: mid-text", async () => {
|
||||
const msg = makeMsg();
|
||||
|
||||
await deliverWebReply({
|
||||
replyResult: { text: "Intro line\nReasoning: appears in content but is not a prefix" },
|
||||
msg,
|
||||
maxMediaBytes: 1024 * 1024,
|
||||
textLimit: 200,
|
||||
replyLogger,
|
||||
skipLog: true,
|
||||
});
|
||||
|
||||
expect(msg.reply).toHaveBeenCalledTimes(1);
|
||||
expect(msg.reply).toHaveBeenCalledWith(
|
||||
"Intro line\nReasoning: appears in content but is not a prefix",
|
||||
);
|
||||
});
|
||||
|
||||
it("sends chunked text replies and logs a summary", async () => {
|
||||
const msg = makeMsg();
|
||||
|
||||
|
||||
@@ -12,6 +12,19 @@ import { whatsappOutboundLog } from "./loggers.js";
|
||||
import type { WebInboundMsg } from "./types.js";
|
||||
import { elide } from "./util.js";
|
||||
|
||||
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 text.trimStart().toLowerCase().startsWith(REASONING_PREFIX);
|
||||
}
|
||||
|
||||
export async function deliverWebReply(params: {
|
||||
replyResult: ReplyPayload;
|
||||
msg: WebInboundMsg;
|
||||
@@ -29,6 +42,10 @@ export async function deliverWebReply(params: {
|
||||
}) {
|
||||
const { replyResult, msg, maxMediaBytes, textLimit, replyLogger, connectionId, skipLog } = params;
|
||||
const replyStarted = Date.now();
|
||||
if (shouldSuppressReasoningReply(replyResult)) {
|
||||
whatsappOutboundLog.debug(`Suppressed reasoning payload to ${msg.from}`);
|
||||
return;
|
||||
}
|
||||
const tableMode = params.tableMode ?? "code";
|
||||
const chunkMode = params.chunkMode ?? "length";
|
||||
const convertedText = markdownToWhatsApp(
|
||||
|
||||
Reference in New Issue
Block a user