Skip to content

Commit 780395a

Browse files
committed
fix(imessage): normalize echo-cache text prefixes
1 parent 3622d47 commit 780395a

3 files changed

Lines changed: 63 additions & 12 deletions

File tree

CHANGELOG.md

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

1313
### Fixes
1414

15+
- Channels/iMessage: normalize leading attributed-text, replacement, and control corruption bytes before sent-message echo-cache text matching, while ignoring all-prefix/all-control keys so echoed agent replies are deduped without suppressing intentional message body text. Carries forward #66169; covers #62191. Thanks @coder999999999 and @maguilar631697.
1516
- Agents/models: keep per-agent primary models strict when `fallbacks` is omitted, so probe-only custom providers are not tried as hidden fallback candidates unless the agent explicitly opts in. Fixes #73332. Thanks @haumanto.
1617
- Cron/Telegram: preserve explicit `:topic:` delivery targets over stale session-derived thread IDs when isolated cron announces to Telegram forum topics. Carries forward #59069; refs #49704 and #43808. Thanks @roytong9.
1718
- Build/runtime: write the runtime-postbuild stamp after `pnpm build` writes the build stamp, so the next CLI invocation does not re-sync runtime artifacts after a successful build. Fixes #73151. Thanks @bittoby.

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,17 @@ function stripLeadingIMessageAttributedTextPrefix(text: string): string {
5252
if (!first) {
5353
break;
5454
}
55-
const second = text[offset + 1];
56-
const third = text[offset + 2];
57-
if (
58-
isHighAttributedLeadChar(first) &&
59-
third &&
60-
isLowControlOrReplacementChar(third) &&
61-
(!second || isAsciiPrintableChar(second))
62-
) {
63-
offset += second ? 3 : 2;
64-
continue;
55+
if (isHighAttributedLeadChar(first)) {
56+
const second = text[offset + 1];
57+
const third = text[offset + 2];
58+
if (second && third && isAsciiPrintableChar(second) && isLowControlOrReplacementChar(third)) {
59+
offset += 3;
60+
continue;
61+
}
62+
if (second && isLowControlOrReplacementChar(second)) {
63+
offset += 2;
64+
continue;
65+
}
6566
}
6667

6768
let runEnd = offset;
@@ -80,8 +81,7 @@ function stripLeadingIMessageAttributedTextPrefix(text: string): string {
8081
break;
8182
}
8283

83-
const stripped = offset > 0 ? text.slice(offset) : text;
84-
return stripped || text;
84+
return offset > 0 ? text.slice(offset) : text;
8585
}
8686

8787
function normalizeEchoTextKey(text: string | undefined): string | null {

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,54 @@ describe("iMessage sent-message echo cache", () => {
6060
),
6161
).toBe(true);
6262
});
63+
64+
it("strips two-byte attributed-text prefixes before text fallback matching", () => {
65+
vi.useFakeTimers();
66+
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));
67+
const cache = createSentMessageCache();
68+
69+
cache.remember("acct:imessage:+1555", {
70+
text: "Confirmed full access",
71+
messageId: "p:0/GUID-outbound",
72+
});
73+
74+
expect(
75+
cache.has(
76+
"acct:imessage:+1555",
77+
{ text: "\u0093\u0002Confirmed full access", messageId: "123800" },
78+
true,
79+
),
80+
).toBe(true);
81+
});
82+
83+
it("strips replacement/control prefixes before text fallback matching", () => {
84+
vi.useFakeTimers();
85+
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));
86+
const cache = createSentMessageCache();
87+
88+
cache.remember("acct:imessage:+1555", {
89+
text: "Confirmed full access",
90+
messageId: "p:0/GUID-outbound",
91+
});
92+
93+
expect(
94+
cache.has(
95+
"acct:imessage:+1555",
96+
{ text: "\uFFFD\u0000\u0001Confirmed full access", messageId: "123801" },
97+
true,
98+
),
99+
).toBe(true);
100+
});
101+
102+
it("does not cache all-prefix or all-control text keys", () => {
103+
vi.useFakeTimers();
104+
vi.setSystemTime(new Date("2026-02-25T00:00:00Z"));
105+
const cache = createSentMessageCache();
106+
107+
cache.remember("acct:imessage:+1555", { text: "\u0093\u0002" });
108+
cache.remember("acct:imessage:+1555", { text: "\uFFFD\u0000\u0001" });
109+
110+
expect(cache.has("acct:imessage:+1555", { text: "\u0093\u0002" })).toBe(false);
111+
expect(cache.has("acct:imessage:+1555", { text: "\uFFFD\u0000\u0001" })).toBe(false);
112+
});
63113
});

0 commit comments

Comments
 (0)