mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
This commit is contained in:
committed by
GitHub
parent
8982d47a55
commit
dbe645a08f
@@ -183,6 +183,9 @@ describe("deliverMatrixReplies", () => {
|
||||
replies: [
|
||||
{ text: "Reasoning:\n_hidden_" },
|
||||
{ text: "<think>still hidden</think>" },
|
||||
{ text: "<mm:think>MiniMax private reasoning</mm:think>" },
|
||||
{ text: "<mm:thought>MiniMax private thought</mm:thought>" },
|
||||
{ text: "<antml:thinking>Anthropic private reasoning</antml:thinking>" },
|
||||
{ text: "Visible answer" },
|
||||
],
|
||||
roomId: "room:5",
|
||||
@@ -198,6 +201,90 @@ describe("deliverMatrixReplies", () => {
|
||||
expect(sendOptions(0).cfg).toBe(cfg);
|
||||
});
|
||||
|
||||
it("delivers literal reasoning tags inside Markdown code", async () => {
|
||||
const text = "Use `<mm:think>example</mm:think>` literally.";
|
||||
|
||||
await deliverMatrixReplies({
|
||||
cfg,
|
||||
replies: [{ text }],
|
||||
roomId: "room:5",
|
||||
client: {} as MatrixClient,
|
||||
runtime: runtimeEnv,
|
||||
textLimit: 4000,
|
||||
replyToMode: "off",
|
||||
});
|
||||
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendCall(0)[1]).toBe(text);
|
||||
});
|
||||
|
||||
it("strips namespaced reasoning while delivering visible Matrix replies", async () => {
|
||||
await deliverMatrixReplies({
|
||||
cfg,
|
||||
replies: [
|
||||
{ text: "<mm:think>MiniMax private reasoning</mm:think>Visible MiniMax answer" },
|
||||
{ text: "<antml:thinking>Anthropic private reasoning</antml:thinking>Visible answer" },
|
||||
{ text: "<br>Visible HTML answer<mm:think>MiniMax private reasoning</mm:think>" },
|
||||
{ text: "Visible safe answer<mm:think>unfinished private reasoning" },
|
||||
{ text: "Visible answer<think>old reasoning</think><think>unfinished private reasoning" },
|
||||
{ text: "<thinking>private reasoning</think>Visible alias answer" },
|
||||
{ text: "<final>Visible final answer" },
|
||||
],
|
||||
roomId: "room:5",
|
||||
client: {} as MatrixClient,
|
||||
runtime: runtimeEnv,
|
||||
textLimit: 4000,
|
||||
replyToMode: "off",
|
||||
});
|
||||
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledTimes(7);
|
||||
expect(sendCall(0)[1]).toBe("Visible MiniMax answer");
|
||||
expect(sendCall(1)[1]).toBe("Visible answer");
|
||||
expect(sendCall(2)[1]).toBe("<br>Visible HTML answer");
|
||||
expect(sendCall(3)[1]).toBe("Visible safe answer");
|
||||
expect(sendCall(4)[1]).toBe("Visible answer");
|
||||
expect(sendCall(5)[1]).toBe("Visible alias answer");
|
||||
expect(sendCall(6)[1]).toBe("Visible final answer");
|
||||
});
|
||||
|
||||
it("preserves significant whitespace in visible Markdown replies", async () => {
|
||||
const text = " indented Markdown code\n\nVisible line with a hard break \nnext line";
|
||||
|
||||
await deliverMatrixReplies({
|
||||
cfg,
|
||||
replies: [{ text }],
|
||||
roomId: "room:5",
|
||||
client: {} as MatrixClient,
|
||||
runtime: runtimeEnv,
|
||||
textLimit: 4000,
|
||||
replyToMode: "off",
|
||||
});
|
||||
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendCall(0)[1]).toBe(text.trim());
|
||||
});
|
||||
|
||||
it("delivers Matrix media without a reasoning-only caption", async () => {
|
||||
await deliverMatrixReplies({
|
||||
cfg,
|
||||
replies: [
|
||||
{
|
||||
text: "<mm:think>MiniMax private reasoning</mm:think>",
|
||||
mediaUrl: "https://example.com/a.jpg",
|
||||
},
|
||||
],
|
||||
roomId: "room:5",
|
||||
client: {} as MatrixClient,
|
||||
runtime: runtimeEnv,
|
||||
textLimit: 4000,
|
||||
replyToMode: "off",
|
||||
});
|
||||
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendCall(0)[1]).toBe("");
|
||||
expect(sendOptions(0).mediaUrl).toBe("https://example.com/a.jpg");
|
||||
});
|
||||
|
||||
it("uses supplied cfg for chunking and send delivery without reloading runtime config", async () => {
|
||||
const explicitCfg = {
|
||||
channels: {
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
// Matrix plugin module implements replies behavior.
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-chunking";
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import type { MatrixClient } from "../sdk.js";
|
||||
import { chunkMatrixText, sendMessageMatrix } from "../send.js";
|
||||
import type { MarkdownTableMode, OpenClawConfig, ReplyPayload, RuntimeEnv } from "./runtime-api.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 {
|
||||
function resolveVisibleMatrixReplyText(text?: string): string | undefined {
|
||||
if (typeof text !== "string") {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
const trimmedStart = text.trimStart();
|
||||
if (!trimmedStart) {
|
||||
return false;
|
||||
return text;
|
||||
}
|
||||
if (normalizeLowercaseStringOrEmpty(trimmedStart).startsWith("reasoning:")) {
|
||||
return true;
|
||||
return undefined;
|
||||
}
|
||||
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();
|
||||
const visibleText = stripReasoningTagsFromText(text, { mode: "strict", trim: "none" });
|
||||
return visibleText.trim() ? visibleText : undefined;
|
||||
}
|
||||
|
||||
export async function deliverMatrixReplies(params: {
|
||||
@@ -60,11 +51,12 @@ export async function deliverMatrixReplies(params: {
|
||||
let hasReplied = false;
|
||||
let deliveredAny = false;
|
||||
for (const reply of params.replies) {
|
||||
if (reply.isReasoning === true || shouldSuppressReasoningReplyText(reply.text)) {
|
||||
const visibleText = resolveVisibleMatrixReplyText(reply.text);
|
||||
const hasMedia = Boolean(reply?.mediaUrl) || (reply?.mediaUrls?.length ?? 0) > 0;
|
||||
if (reply.isReasoning === true || (!hasMedia && reply.text && visibleText === undefined)) {
|
||||
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) {
|
||||
logVerbose("matrix reply has audioAsVoice without media/text; skipping");
|
||||
@@ -79,7 +71,7 @@ export async function deliverMatrixReplies(params: {
|
||||
: params.replyToMode === "off"
|
||||
? undefined
|
||||
: replyToIdRaw;
|
||||
const rawText = reply.text ?? "";
|
||||
const rawText = visibleText ?? "";
|
||||
const mediaList = reply.mediaUrls?.length
|
||||
? reply.mediaUrls
|
||||
: reply.mediaUrl
|
||||
|
||||
Reference in New Issue
Block a user