Skip to content

Commit 1189581

Browse files
committed
fix(imessage): normalize leading echoed text corruption
1 parent 7ddd815 commit 1189581

3 files changed

Lines changed: 37 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
2020
- 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.
2121
- 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.
2222
- 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.
23+
- 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.
2324
- 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.
2425
- 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.
2526
- 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.

extensions/imessage/src/monitor/echo-cache.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@ export type SentMessageCache = {
2222
// duplicate delivery (noisy but not lossy) — never message loss.
2323
const SENT_MESSAGE_TEXT_TTL_MS = 4_000;
2424
const SENT_MESSAGE_ID_TTL_MS = 60_000;
25+
const LEADING_ATTRIBUTED_BODY_CORRUPTION_MARKERS = /^[\uFEFF\uFFFD\uFFFE\uFFFF]+/u;
2526

2627
function normalizeEchoTextKey(text: string | undefined): string | null {
2728
if (!text) {
2829
return null;
2930
}
30-
const normalized = text.replace(/\r\n?/g, "\n").trim();
31+
const normalized = text
32+
.replace(/\r\n?/g, "\n")
33+
.trim()
34+
.replace(LEADING_ATTRIBUTED_BODY_CORRUPTION_MARKERS, "")
35+
.trim();
3136
return normalized ? normalized : null;
3237
}
3338

extensions/imessage/src/monitor/monitor-provider.echo-cache.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,36 @@ describe("iMessage sent-message echo cache", () => {
1717
expect(cache.has("acct:imessage:+1666", { text: "Reasoning:\n_step_" })).toBe(false);
1818
});
1919

20+
it("matches delayed reflected echoes with leading attributedBody corruption markers", () => {
21+
vi.useFakeTimers();
22+
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));
23+
const cache = createSentMessageCache();
24+
25+
cache.remember("acct:imessage:+1555", { text: "Delayed echo reply" });
26+
27+
expect(
28+
cache.has("acct:imessage:+1555", {
29+
text: "\uFFFD\uFFFE\uFFFF\uFEFFDelayed echo reply",
30+
}),
31+
).toBe(true);
32+
});
33+
34+
it("keeps attributedBody corruption cleanup leading-only", () => {
35+
vi.useFakeTimers();
36+
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));
37+
const cache = createSentMessageCache();
38+
39+
cache.remember("acct:imessage:+1555", { text: "Delayed echo reply" });
40+
41+
expect(
42+
cache.has("acct:imessage:+1555", {
43+
text: "Delayed \uFFFD echo reply",
44+
}),
45+
).toBe(false);
46+
expect(cache.has("acct:imessage:+1555", { text: "Delayed\techo reply" })).toBe(false);
47+
expect(cache.has("acct:imessage:+1555", { text: "Delayed\necho reply" })).toBe(false);
48+
});
49+
2050
it("matches by outbound message id and ignores placeholder ids", () => {
2151
vi.useFakeTimers();
2252
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));

0 commit comments

Comments
 (0)