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: {