fix(auto-reply): land #31080 from @scoootscooob

Co-authored-by: scoootscooob <zhentongfan@gmail.com>
This commit is contained in:
Peter Steinberger
2026-03-02 01:17:35 +00:00
parent e7cd4bf1bd
commit a6a742f3d0
5 changed files with 101 additions and 2 deletions

View File

@@ -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 = "";

View File

@@ -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", () => {