Skip to content

Commit febe43f

Browse files
fix(imessage): normalize leading echo-cache corruption bytes
1 parent c2e3b6e commit febe43f

3 files changed

Lines changed: 59 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
1313

1414
### Fixes
1515

16+
- Channels/iMessage: strip only known leading attributedBody corruption markers from echo-cache text keys so reflected outbound replies are still deduped without dropping interior replacement or control characters. Fixes #41330; refs #59973; carries forward #62191. Thanks @maguilar631697.
1617
- Gateway/shutdown: report structured shutdown warnings and HTTP close timeout warnings through `ShutdownResult` while preserving lifecycle hook hardening. Carries forward #41296. Thanks @edenfunf.
1718
- Plugins/QA: prebuild the private QA channel runtime before plugin gauntlet source runs so wrapper CPU/RSS measurements are not polluted by private QA dist rebuild work. Thanks @vincentkoc.
1819
- Gateway/reload: bound default restart deferral and SIGUSR1 restart drain to five minutes while preserving explicit `deferralTimeoutMs: 0` indefinite waits, so stale active work accounting cannot block config reloads forever. Thanks @vincentkoc.

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,30 @@ export type SentMessageCache = {
2323
const SENT_MESSAGE_TEXT_TTL_MS = 4_000;
2424
const SENT_MESSAGE_ID_TTL_MS = 60_000;
2525

26+
function isLeadingIMessageEchoCorruptionCode(code: number): boolean {
27+
return (
28+
code <= 0x1f ||
29+
code === 0x7f ||
30+
(code >= 0x80 && code <= 0x9f) ||
31+
code === 0xfffd ||
32+
code === 0xfffe ||
33+
code === 0xffff
34+
);
35+
}
36+
37+
function stripLeadingIMessageEchoCorruption(text: string): string {
38+
let index = 0;
39+
while (index < text.length && isLeadingIMessageEchoCorruptionCode(text.charCodeAt(index))) {
40+
index += 1;
41+
}
42+
return index === 0 ? text : text.slice(index);
43+
}
44+
2645
function normalizeEchoTextKey(text: string | undefined): string | null {
2746
if (!text) {
2847
return null;
2948
}
30-
const normalized = text.replace(/\r\n?/g, "\n").trim();
49+
const normalized = stripLeadingIMessageEchoCorruption(text.replace(/\r\n?/g, "\n")).trim();
3150
return normalized ? normalized : null;
3251
}
3352

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,44 @@ 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 text after known leading attributedBody corruption markers", () => {
21+
vi.useFakeTimers();
22+
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));
23+
const cache = createSentMessageCache();
24+
const scope = "acct:imessage:+1555";
25+
const leadingCorruption = String.fromCharCode(
26+
0xfffd,
27+
0xfffe,
28+
0xffff,
29+
0x00,
30+
0x1f,
31+
0x7f,
32+
0x80,
33+
0x9f,
34+
);
35+
36+
cache.remember(scope, { text: "Cached reply\tline\nwith interior \ufffd intact" });
37+
vi.advanceTimersByTime(3_000);
38+
39+
expect(
40+
cache.has(scope, {
41+
text: `${leadingCorruption}Cached reply\tline\nwith interior \ufffd intact`,
42+
messageId: "123456",
43+
}),
44+
).toBe(true);
45+
});
46+
47+
it("preserves interior corruption-looking user text while stripping only the prefix", () => {
48+
const cache = createSentMessageCache();
49+
const scope = "acct:imessage:+1555";
50+
const textWithInteriorMarkers = "Keep \ufffd and \u0001 inside\nnext\tline";
51+
52+
cache.remember(scope, { text: textWithInteriorMarkers });
53+
54+
expect(cache.has(scope, { text: `\u0002${textWithInteriorMarkers}` })).toBe(true);
55+
expect(cache.has(scope, { text: "Keep and inside\nnext\tline" })).toBe(false);
56+
});
57+
2058
it("matches by outbound message id and ignores placeholder ids", () => {
2159
vi.useFakeTimers();
2260
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));

0 commit comments

Comments
 (0)