mirror of https://github.com/openclaw/openclaw.git
Build: sync main manifests and harden Matrix reasoning suppression
This commit is contained in:
parent
2f71349c03
commit
3d82e38d9d
|
|
@ -137,6 +137,29 @@ describe("deliverMatrixReplies", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("suppresses reasoning-only text before Matrix sends", async () => {
|
||||
await deliverMatrixReplies({
|
||||
cfg,
|
||||
replies: [
|
||||
{ text: "Reasoning:\n_hidden_" },
|
||||
{ text: "<think>still hidden</think>" },
|
||||
{ text: "Visible answer" },
|
||||
],
|
||||
roomId: "room:5",
|
||||
client: {} as MatrixClient,
|
||||
runtime: runtimeEnv,
|
||||
textLimit: 4000,
|
||||
replyToMode: "off",
|
||||
});
|
||||
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledWith(
|
||||
"room:5",
|
||||
"Visible answer",
|
||||
expect.objectContaining({ cfg }),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses supplied cfg for chunking and send delivery without reloading runtime config", async () => {
|
||||
const explicitCfg = {
|
||||
channels: {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,31 @@ import { getMatrixRuntime } from "../../runtime.js";
|
|||
import type { MatrixClient } from "../sdk.js";
|
||||
import { sendMessageMatrix } from "../send.js";
|
||||
|
||||
const THINKING_TAG_RE = /<\s*\/?\s*(?:think(?:ing)?|thought|antthinking)\b[^<>]*>/gi;
|
||||
const THINKING_BLOCK_RE =
|
||||
/<\s*(?:think(?:ing)?|thought|antthinking)\b[^<>]*>[\s\S]*?<\s*\/\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi;
|
||||
|
||||
function shouldSuppressReasoningReplyText(text?: string): boolean {
|
||||
if (typeof text !== "string") {
|
||||
return false;
|
||||
}
|
||||
const trimmedStart = text.trimStart();
|
||||
if (!trimmedStart) {
|
||||
return false;
|
||||
}
|
||||
if (trimmedStart.toLowerCase().startsWith("reasoning:")) {
|
||||
return true;
|
||||
}
|
||||
THINKING_TAG_RE.lastIndex = 0;
|
||||
if (!THINKING_TAG_RE.test(text)) {
|
||||
return false;
|
||||
}
|
||||
THINKING_BLOCK_RE.lastIndex = 0;
|
||||
const withoutThinkingBlocks = text.replace(THINKING_BLOCK_RE, "");
|
||||
THINKING_TAG_RE.lastIndex = 0;
|
||||
return !withoutThinkingBlocks.replace(THINKING_TAG_RE, "").trim();
|
||||
}
|
||||
|
||||
export async function deliverMatrixReplies(params: {
|
||||
cfg: OpenClawConfig;
|
||||
replies: ReplyPayload[];
|
||||
|
|
@ -37,6 +62,10 @@ export async function deliverMatrixReplies(params: {
|
|||
const chunkMode = core.channel.text.resolveChunkMode(params.cfg, "matrix", params.accountId);
|
||||
let hasReplied = false;
|
||||
for (const reply of params.replies) {
|
||||
if (reply.isReasoning === true || shouldSuppressReasoningReplyText(reply.text)) {
|
||||
logVerbose("matrix reply suppressed as reasoning-only");
|
||||
continue;
|
||||
}
|
||||
const hasMedia = Boolean(reply?.mediaUrl) || (reply?.mediaUrls?.length ?? 0) > 0;
|
||||
if (!reply?.text && !hasMedia) {
|
||||
if (reply?.audioAsVoice) {
|
||||
|
|
|
|||
|
|
@ -353,7 +353,6 @@
|
|||
"@mariozechner/pi-ai": "0.57.1",
|
||||
"@mariozechner/pi-coding-agent": "0.57.1",
|
||||
"@mariozechner/pi-tui": "0.57.1",
|
||||
"@matrix-org/matrix-sdk-crypto-nodejs": "^0.4.0",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
"@sinclair/typebox": "0.34.48",
|
||||
"@slack/bolt": "^4.6.0",
|
||||
|
|
|
|||
|
|
@ -70,9 +70,6 @@ importers:
|
|||
'@mariozechner/pi-tui':
|
||||
specifier: 0.57.1
|
||||
version: 0.57.1
|
||||
'@matrix-org/matrix-sdk-crypto-nodejs':
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
'@mozilla/readability':
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0
|
||||
|
|
@ -3618,8 +3615,8 @@ packages:
|
|||
link-preview-js:
|
||||
optional: true
|
||||
|
||||
'@whiskeysockets/libsignal-node@git+https://github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
||||
resolution: {commit: 1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67, repo: https://github.com/whiskeysockets/libsignal-node.git, type: git}
|
||||
'@whiskeysockets/libsignal-node@git+https://git@github.com:whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
||||
resolution: {commit: 1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67, repo: git@github.com:whiskeysockets/libsignal-node.git, type: git}
|
||||
version: 2.0.1
|
||||
|
||||
abbrev@1.1.1:
|
||||
|
|
@ -10474,7 +10471,7 @@ snapshots:
|
|||
'@cacheable/node-cache': 1.7.6
|
||||
'@hapi/boom': 9.1.4
|
||||
async-mutex: 0.5.0
|
||||
libsignal: '@whiskeysockets/libsignal-node@git+https://github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67'
|
||||
libsignal: '@whiskeysockets/libsignal-node@git+https://git@github.com:whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67'
|
||||
lru-cache: 11.2.6
|
||||
music-metadata: 11.12.1
|
||||
p-queue: 9.1.0
|
||||
|
|
@ -10489,7 +10486,7 @@ snapshots:
|
|||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
'@whiskeysockets/libsignal-node@git+https://github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
||||
'@whiskeysockets/libsignal-node@git+https://git@github.com:whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
||||
dependencies:
|
||||
curve25519-js: 0.0.4
|
||||
protobufjs: 6.8.8
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
filterMessagingToolMediaDuplicates,
|
||||
shouldSuppressReasoningPayload,
|
||||
shouldSuppressMessagingToolReplies,
|
||||
} from "./reply-payloads.js";
|
||||
|
||||
|
|
@ -154,3 +155,27 @@ describe("shouldSuppressMessagingToolReplies", () => {
|
|||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldSuppressReasoningPayload", () => {
|
||||
it("suppresses raw reasoning-prefix text even when isReasoning is absent", () => {
|
||||
expect(shouldSuppressReasoningPayload({ text: " Reasoning:\n_hidden_" })).toBe(true);
|
||||
});
|
||||
|
||||
it("suppresses thinking-tag-only text even when isReasoning is absent", () => {
|
||||
expect(shouldSuppressReasoningPayload({ text: "<think>hidden</think>" })).toBe(true);
|
||||
});
|
||||
|
||||
it("does not suppress text that merely mentions reasoning mid-message", () => {
|
||||
expect(
|
||||
shouldSuppressReasoningPayload({
|
||||
text: "Intro line\nReasoning: appears in content but is not a prefix",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not suppress messages that contain an answer outside thinking tags", () => {
|
||||
expect(shouldSuppressReasoningPayload({ text: "<think>hidden</think>Visible answer" })).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { normalizeChannelId } from "../../channels/plugins/index.js";
|
|||
import type { ReplyToMode } from "../../config/types.js";
|
||||
import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js";
|
||||
import { normalizeOptionalAccountId } from "../../routing/account-id.js";
|
||||
import { stripReasoningTagsFromText } from "../../shared/text/reasoning-tags.js";
|
||||
import { parseTelegramTarget } from "../../telegram/targets.js";
|
||||
import type { OriginatingChannelType } from "../templating.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
|
|
@ -71,7 +72,18 @@ export function isRenderablePayload(payload: ReplyPayload): boolean {
|
|||
}
|
||||
|
||||
export function shouldSuppressReasoningPayload(payload: ReplyPayload): boolean {
|
||||
return payload.isReasoning === true;
|
||||
if (payload.isReasoning === true) {
|
||||
return true;
|
||||
}
|
||||
const text = payload.text;
|
||||
if (typeof text !== "string") {
|
||||
return false;
|
||||
}
|
||||
if (text.trimStart().toLowerCase().startsWith("reasoning:")) {
|
||||
return true;
|
||||
}
|
||||
const stripped = stripReasoningTagsFromText(text, { mode: "strict", trim: "both" });
|
||||
return !stripped && stripped !== text;
|
||||
}
|
||||
|
||||
export function applyReplyThreading(params: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue