From aec492c4915af2f8aaad22d4ed0e72b94d8de22a Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Thu, 12 Mar 2026 00:57:09 +0000 Subject: [PATCH] Build: sync main manifests and harden Matrix reasoning suppression --- extensions/googlechat/package.json | 5 +- .../matrix/src/matrix/monitor/replies.test.ts | 23 ++++ .../matrix/src/matrix/monitor/replies.ts | 29 +++++ extensions/memory-core/package.json | 5 +- package.json | 3 +- pnpm-lock.yaml | 105 +++++++++++++++--- src/auto-reply/reply/reply-payloads.test.ts | 25 +++++ src/auto-reply/reply/reply-payloads.ts | 14 ++- 8 files changed, 185 insertions(+), 24 deletions(-) diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index 61128b78032..246ea9ac149 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -1,15 +1,12 @@ { "name": "@openclaw/googlechat", - "version": "2026.3.9", + "version": "2026.3.10", "private": true, "description": "OpenClaw Google Chat channel plugin", "type": "module", "dependencies": { "google-auth-library": "^10.6.1" }, - "devDependencies": { - "openclaw": "workspace:*" - }, "peerDependencies": { "openclaw": ">=2026.3.7" }, diff --git a/extensions/matrix/src/matrix/monitor/replies.test.ts b/extensions/matrix/src/matrix/monitor/replies.test.ts index 98fd857300a..f56a6e0e387 100644 --- a/extensions/matrix/src/matrix/monitor/replies.test.ts +++ b/extensions/matrix/src/matrix/monitor/replies.test.ts @@ -137,6 +137,29 @@ describe("deliverMatrixReplies", () => { ); }); + it("suppresses reasoning-only text before Matrix sends", async () => { + await deliverMatrixReplies({ + cfg, + replies: [ + { text: "Reasoning:\n_hidden_" }, + { text: "still hidden" }, + { 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: { diff --git a/extensions/matrix/src/matrix/monitor/replies.ts b/extensions/matrix/src/matrix/monitor/replies.ts index 12582e0c42e..5b47a8d1d98 100644 --- a/extensions/matrix/src/matrix/monitor/replies.ts +++ b/extensions/matrix/src/matrix/monitor/replies.ts @@ -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) { diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index 0af3fc45281..0b7ab2905d1 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -1,12 +1,9 @@ { "name": "@openclaw/memory-core", - "version": "2026.3.9", + "version": "2026.3.10", "private": true, "description": "OpenClaw core memory search plugin", "type": "module", - "devDependencies": { - "openclaw": "workspace:*" - }, "peerDependencies": { "openclaw": ">=2026.3.7" }, diff --git a/package.json b/package.json index 54f3f7e6747..ba2781bc223 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openclaw", - "version": "2026.3.9", + "version": "2026.3.10", "description": "Multi-channel AI gateway with extensible messaging integrations", "keywords": [], "homepage": "https://github.com/openclaw/openclaw#readme", @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2829fa9630e..736b2db205e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 @@ -344,10 +341,9 @@ importers: google-auth-library: specifier: ^10.6.1 version: 10.6.1 - devDependencies: openclaw: - specifier: workspace:* - version: link:../.. + specifier: '>=2026.3.7' + version: 2026.3.8(@discordjs/opus@0.10.0)(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.12.7)(node-llama-cpp@3.16.2(typescript@5.9.3)) extensions/imessage: {} @@ -409,10 +405,10 @@ importers: version: 4.3.6 extensions/memory-core: - devDependencies: + dependencies: openclaw: - specifier: workspace:* - version: link:../.. + specifier: '>=2026.3.7' + version: 2026.3.8(@discordjs/opus@0.10.0)(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.12.7)(node-llama-cpp@3.16.2(typescript@5.9.3)) extensions/memory-lancedb: dependencies: @@ -3619,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: @@ -5347,6 +5343,14 @@ packages: zod: optional: true + openclaw@2026.3.8: + resolution: {integrity: sha512-e5Rk2Aj55sD/5LyX94mdYCQj7zpHXo0xIZsl+k140+nRopePfPAxC7nsu0V/NyypPRtaotP1riFfzK7IhaYkuQ==} + engines: {node: '>=22.12.0'} + hasBin: true + peerDependencies: + '@napi-rs/canvas': ^0.1.89 + node-llama-cpp: 3.16.2 + opus-decoder@0.7.11: resolution: {integrity: sha512-+e+Jz3vGQLxRTBHs8YJQPRPc1Tr+/aC6coV/DlZylriA29BdHQAYXhvNRKtjftof17OFng0+P4wsFIqQu3a48A==} @@ -10467,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 @@ -10482,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 @@ -12320,6 +12324,81 @@ snapshots: ws: 8.19.0 zod: 4.3.6 + openclaw@2026.3.8(@discordjs/opus@0.10.0)(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.12.7)(node-llama-cpp@3.16.2(typescript@5.9.3)): + dependencies: + '@agentclientprotocol/sdk': 0.15.0(zod@4.3.6) + '@aws-sdk/client-bedrock': 3.1007.0 + '@buape/carbon': 0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.12.7)(opusscript@0.1.1) + '@clack/prompts': 1.1.0 + '@discordjs/voice': 0.19.1(@discordjs/opus@0.10.0)(opusscript@0.1.1) + '@grammyjs/runner': 2.0.3(grammy@1.41.1) + '@grammyjs/transformer-throttler': 1.2.1(grammy@1.41.1) + '@homebridge/ciao': 1.3.5 + '@larksuiteoapi/node-sdk': 1.59.0 + '@line/bot-sdk': 10.6.0 + '@lydell/node-pty': 1.2.0-beta.3 + '@mariozechner/pi-agent-core': 0.57.1(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-ai': 0.57.1(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-coding-agent': 0.57.1(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-tui': 0.57.1 + '@mozilla/readability': 0.6.0 + '@napi-rs/canvas': 0.1.95 + '@sinclair/typebox': 0.34.48 + '@slack/bolt': 4.6.0(@types/express@5.0.6) + '@slack/web-api': 7.14.1 + '@whiskeysockets/baileys': 7.0.0-rc.9(audio-decode@2.2.3)(sharp@0.34.5) + ajv: 8.18.0 + chalk: 5.6.2 + chokidar: 5.0.0 + cli-highlight: 2.1.11 + commander: 14.0.3 + croner: 10.0.1 + discord-api-types: 0.38.42 + dotenv: 17.3.1 + express: 5.2.1 + file-type: 21.3.1 + grammy: 1.41.1 + https-proxy-agent: 7.0.6 + ipaddr.js: 2.3.0 + jiti: 2.6.1 + json5: 2.2.3 + jszip: 3.10.1 + linkedom: 0.18.12 + long: 5.3.2 + markdown-it: 14.1.1 + node-edge-tts: 1.2.10 + node-llama-cpp: 3.16.2(typescript@5.9.3) + opusscript: 0.1.1 + osc-progress: 0.3.0 + pdfjs-dist: 5.5.207 + playwright-core: 1.58.2 + qrcode-terminal: 0.12.0 + sharp: 0.34.5 + sqlite-vec: 0.1.7-alpha.2 + tar: 7.5.11 + tslog: 4.10.2 + undici: 7.22.0 + ws: 8.19.0 + yaml: 2.8.2 + zod: 4.3.6 + transitivePeerDependencies: + - '@discordjs/opus' + - '@modelcontextprotocol/sdk' + - '@types/express' + - audio-decode + - aws-crt + - bufferutil + - canvas + - debug + - encoding + - ffmpeg-static + - hono + - jimp + - link-preview-js + - node-opus + - supports-color + - utf-8-validate + opus-decoder@0.7.11: dependencies: '@wasm-audio-decoders/common': 9.0.7 diff --git a/src/auto-reply/reply/reply-payloads.test.ts b/src/auto-reply/reply/reply-payloads.test.ts index 614fcd37951..30dbd98e0ac 100644 --- a/src/auto-reply/reply/reply-payloads.test.ts +++ b/src/auto-reply/reply/reply-payloads.test.ts @@ -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: "hidden" })).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: "hiddenVisible answer" })).toBe( + false, + ); + }); +}); diff --git a/src/auto-reply/reply/reply-payloads.ts b/src/auto-reply/reply/reply-payloads.ts index 5a20d4ba950..367959c33b4 100644 --- a/src/auto-reply/reply/reply-payloads.ts +++ b/src/auto-reply/reply/reply-payloads.ts @@ -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: {