Skip to content

Commit 80b1dd1

Browse files
fix(imessage): normalize leading echo-cache corruption bytes
1 parent 8e5fcff commit 80b1dd1

3 files changed

Lines changed: 52 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 leading imsg replacement/control-byte corruption when matching sent-message echo-cache text, preserving line breaks and interior bytes in a narrow ProjectClownfish repair of #62191. Fixes #41330; refs #59973. Thanks @maguilar631697.
1617
- 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.
1718
- 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.
1819
- Plugin SDK/Discord: restore a deprecated `openclaw/plugin-sdk/discord` compatibility facade and the legacy compat group-policy warning export for the published `@openclaw/[email protected]` package, covering its config, account, directory, status, and thread-binding imports while keeping new plugins on generic SDK subpaths. Fixes #73685; supersedes #73703. Thanks @rderickson9 and @SymbolStar.

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

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

26+
function isLeadingImsgEchoCorruptionChar(char: string): boolean {
27+
if (char === "\uFFFD") {
28+
return true;
29+
}
30+
const code = char.codePointAt(0);
31+
if (code === undefined) {
32+
return false;
33+
}
34+
return (
35+
(code >= 0x00 && code <= 0x08) ||
36+
(code >= 0x0b && code <= 0x0c) ||
37+
(code >= 0x0e && code <= 0x1f) ||
38+
(code >= 0x7f && code <= 0x9f)
39+
);
40+
}
41+
42+
function stripLeadingImsgEchoCorruption(text: string): string {
43+
let start = 0;
44+
for (const char of text) {
45+
if (!isLeadingImsgEchoCorruptionChar(char)) {
46+
break;
47+
}
48+
start += char.length;
49+
}
50+
return start === 0 ? text : text.slice(start);
51+
}
52+
2653
function normalizeEchoTextKey(text: string | undefined): string | null {
2754
if (!text) {
2855
return null;
2956
}
30-
const normalized = text.replace(/\r\n?/g, "\n").trim();
57+
const normalized = stripLeadingImsgEchoCorruption(text.replace(/\r\n?/g, "\n").trim()).trim();
3158
return normalized ? normalized : null;
3259
}
3360

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

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

20+
it("matches text with leading imsg replacement and control-byte corruption", () => {
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: "Delivered reply" });
26+
27+
expect(cache.has("acct:imessage:+1555", { text: "\uFFFD\u0001Delivered reply" })).toBe(true);
28+
expect(cache.has("acct:imessage:+1555", { text: "\u0002Delivered reply" })).toBe(true);
29+
});
30+
31+
it("does not collapse interior replacement bytes, control bytes, tabs, or line breaks", () => {
32+
vi.useFakeTimers();
33+
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));
34+
const cache = createSentMessageCache();
35+
const text = "alpha\n\tbeta\u0001gamma\uFFFDdelta";
36+
37+
cache.remember("acct:imessage:+1555", { text });
38+
39+
expect(cache.has("acct:imessage:+1555", { text })).toBe(true);
40+
expect(cache.has("acct:imessage:+1555", { text: "alpha betagammadelta" })).toBe(false);
41+
});
42+
2043
it("matches by outbound message id and ignores placeholder ids", () => {
2144
vi.useFakeTimers();
2245
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));

0 commit comments

Comments
 (0)