mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:41:36 +00:00
fix(auto-reply): land #31080 from @scoootscooob
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import { sanitizeUserFacingText } from "../../agents/pi-embedded-helpers.js";
|
||||
import { stripHeartbeatToken } from "../heartbeat.js";
|
||||
import { HEARTBEAT_TOKEN, isSilentReplyText, SILENT_REPLY_TOKEN } from "../tokens.js";
|
||||
import {
|
||||
HEARTBEAT_TOKEN,
|
||||
isSilentReplyText,
|
||||
SILENT_REPLY_TOKEN,
|
||||
stripSilentToken,
|
||||
} from "../tokens.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
import { hasLineDirectives, parseLineDirectives } from "./line-directives.js";
|
||||
import {
|
||||
@@ -43,6 +48,16 @@ export function normalizeReplyPayload(
|
||||
}
|
||||
text = "";
|
||||
}
|
||||
// Strip NO_REPLY from mixed-content messages (e.g. "😄 NO_REPLY") so the
|
||||
// token never leaks to end users. If stripping leaves nothing, treat it as
|
||||
// silent just like the exact-match path above. (#30916, #30955)
|
||||
if (text && text.includes(silentToken) && !isSilentReplyText(text, silentToken)) {
|
||||
text = stripSilentToken(text, silentToken);
|
||||
if (!text && !hasMedia && !hasChannelData) {
|
||||
opts.onSkip?.("silent");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (text && !trimmed) {
|
||||
// Keep empty text when media exists so media-only replies still send.
|
||||
text = "";
|
||||
|
||||
@@ -108,6 +108,48 @@ describe("normalizeReplyPayload", () => {
|
||||
expect(reasons, testCase.name).toEqual([testCase.reason]);
|
||||
}
|
||||
});
|
||||
|
||||
it("strips NO_REPLY from mixed emoji message (#30916)", () => {
|
||||
const result = normalizeReplyPayload({ text: "😄 NO_REPLY" });
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.text).toContain("😄");
|
||||
expect(result!.text).not.toContain("NO_REPLY");
|
||||
});
|
||||
|
||||
it("strips NO_REPLY appended after substantive text (#30916)", () => {
|
||||
const result = normalizeReplyPayload({
|
||||
text: "File's there. Not urgent.\n\nNO_REPLY",
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.text).toContain("File's there");
|
||||
expect(result!.text).not.toContain("NO_REPLY");
|
||||
});
|
||||
|
||||
it("keeps NO_REPLY when used as leading substantive text", () => {
|
||||
const result = normalizeReplyPayload({ text: "NO_REPLY -- nope" });
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.text).toBe("NO_REPLY -- nope");
|
||||
});
|
||||
|
||||
it("suppresses message when stripping NO_REPLY leaves nothing", () => {
|
||||
const reasons: string[] = [];
|
||||
const result = normalizeReplyPayload(
|
||||
{ text: " NO_REPLY " },
|
||||
{ onSkip: (reason) => reasons.push(reason) },
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
expect(reasons).toEqual(["silent"]);
|
||||
});
|
||||
|
||||
it("strips NO_REPLY but keeps media payload", () => {
|
||||
const result = normalizeReplyPayload({
|
||||
text: "NO_REPLY",
|
||||
mediaUrl: "https://example.com/img.png",
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.text).toBe("");
|
||||
expect(result!.mediaUrl).toBe("https://example.com/img.png");
|
||||
});
|
||||
});
|
||||
|
||||
describe("typing controller", () => {
|
||||
|
||||
Reference in New Issue
Block a user