fix(imessage): normalize leading echoed text corruption

Fixes #59973
This commit is contained in:
openclaw-clownfish[bot]
2026-04-28 21:04:20 -07:00
committed by GitHub
parent 34ef403cb2
commit be445dd1c1
3 changed files with 37 additions and 1 deletions

View File

@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
- Active Memory: register the prompt-build hook with the configured recall timeout plus setup grace instead of the 150s maximum budget, so default memory recall cannot delay turn startup for multiple minutes. Thanks @vincentkoc.
- CLI/channels logs: reuse the rolling log-file resolver so `openclaw channels logs` falls back to the active dated log across date boundaries without reading unrelated custom log files. Fixes #42875; carries forward #42904 and #43043. Thanks @ethanclaw and @wdskuki.
- CLI/update: skip tracked plugins disabled in config during post-update plugin sync before npm, ClawHub, or marketplace update checks, preserving their install records without failing the update. Fixes #73880. Thanks @islandpreneur007.
- iMessage: normalize known leading attributedBody corruption markers on sent-message echo text keys so delayed reflected echoes with U+FFFD/U+FFFE/U+FFFF/FEFF prefixes are dropped without collapsing interior text. Fixes #59973; carries forward #59980 and #62191. Thanks @neeravmakwana and @maguilar631697.
- Security/audit: recognize dangerous node command IDs as valid `gateway.nodes.denyCommands` entries, so audit only warns on real typos or unsupported patterns. (#56923) Thanks @chziyue.
- Telegram/exec approvals: stop treating general Telegram chat allowlists and `defaultTo` routes as native exec approvers; Telegram now uses explicit `execApprovals.approvers` or owner identity from `commands.ownerAllowFrom`, matching the first-pairing owner bootstrap path. Thanks @pashpashpash.
- Chat commands: route sensitive group `/diagnostics` and `/export-trajectory` approvals and results to a private owner route, preferring same-surface DMs before falling back to the first configured owner route, so Discord group invocations can land in Telegram when that is the primary owner interface. Thanks @pashpashpash.

View File

@@ -22,12 +22,17 @@ export type SentMessageCache = {
// duplicate delivery (noisy but not lossy) — never message loss.
const SENT_MESSAGE_TEXT_TTL_MS = 4_000;
const SENT_MESSAGE_ID_TTL_MS = 60_000;
const LEADING_ATTRIBUTED_BODY_CORRUPTION_MARKERS = /^[\uFEFF\uFFFD\uFFFE\uFFFF]+/u;
function normalizeEchoTextKey(text: string | undefined): string | null {
if (!text) {
return null;
}
const normalized = text.replace(/\r\n?/g, "\n").trim();
const normalized = text
.replace(/\r\n?/g, "\n")
.trim()
.replace(LEADING_ATTRIBUTED_BODY_CORRUPTION_MARKERS, "")
.trim();
return normalized ? normalized : null;
}

View File

@@ -17,6 +17,36 @@ describe("iMessage sent-message echo cache", () => {
expect(cache.has("acct:imessage:+1666", { text: "Reasoning:\n_step_" })).toBe(false);
});
it("matches delayed reflected echoes with leading attributedBody corruption markers", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));
const cache = createSentMessageCache();
cache.remember("acct:imessage:+1555", { text: "Delayed echo reply" });
expect(
cache.has("acct:imessage:+1555", {
text: "\uFFFD\uFFFE\uFFFF\uFEFFDelayed echo reply",
}),
).toBe(true);
});
it("keeps attributedBody corruption cleanup leading-only", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));
const cache = createSentMessageCache();
cache.remember("acct:imessage:+1555", { text: "Delayed echo reply" });
expect(
cache.has("acct:imessage:+1555", {
text: "Delayed \uFFFD echo reply",
}),
).toBe(false);
expect(cache.has("acct:imessage:+1555", { text: "Delayed\techo reply" })).toBe(false);
expect(cache.has("acct:imessage:+1555", { text: "Delayed\necho reply" })).toBe(false);
});
it("matches by outbound message id and ignores placeholder ids", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));