mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-31 11:51:22 +00:00
fix(imessage): try all inbound echo ids
This commit is contained in:
@@ -75,13 +75,10 @@ describe("resolveIMessageInboundDecision echo detection", () => {
|
||||
});
|
||||
|
||||
expect(decision).toEqual({ kind: "drop", reason: "echo" });
|
||||
expect(echoHas).toHaveBeenCalledWith(
|
||||
"default:imessage:+15555550123",
|
||||
expect.objectContaining({
|
||||
text: "Reasoning:\n_step_",
|
||||
messageId: "42",
|
||||
}),
|
||||
);
|
||||
expect(echoHas).toHaveBeenNthCalledWith(1, "default:imessage:+15555550123", {
|
||||
messageId: "42",
|
||||
});
|
||||
expect(echoHas).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("matches attachment-only echoes by bodyText placeholder", () => {
|
||||
@@ -100,12 +97,17 @@ describe("resolveIMessageInboundDecision echo detection", () => {
|
||||
});
|
||||
|
||||
expect(decision).toEqual({ kind: "drop", reason: "echo" });
|
||||
expect(echoHas).toHaveBeenCalledWith(
|
||||
expect(echoHas).toHaveBeenNthCalledWith(1, "default:imessage:+15555550123", {
|
||||
messageId: "42",
|
||||
});
|
||||
expect(echoHas).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"default:imessage:+15555550123",
|
||||
expect.objectContaining({
|
||||
{
|
||||
text: "<media:image>",
|
||||
messageId: "42",
|
||||
}),
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -64,6 +64,50 @@ function describeReplyContext(message: IMessagePayload): IMessageReplyContext |
|
||||
return { body, id, sender };
|
||||
}
|
||||
|
||||
function resolveInboundEchoMessageIds(message: IMessagePayload): string[] {
|
||||
const values = [
|
||||
message.id != null ? String(message.id) : undefined,
|
||||
normalizeReplyField(message.guid),
|
||||
];
|
||||
const ids: string[] = [];
|
||||
for (const value of values) {
|
||||
if (!value || ids.includes(value)) {
|
||||
continue;
|
||||
}
|
||||
ids.push(value);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function hasIMessageEchoMatch(params: {
|
||||
echoCache: {
|
||||
has: (
|
||||
scope: string,
|
||||
lookup: { text?: string; messageId?: string },
|
||||
skipIdShortCircuit?: boolean,
|
||||
) => boolean;
|
||||
};
|
||||
scope: string;
|
||||
text?: string;
|
||||
messageIds: string[];
|
||||
skipIdShortCircuit?: boolean;
|
||||
}): boolean {
|
||||
for (const messageId of params.messageIds) {
|
||||
if (params.echoCache.has(params.scope, { messageId })) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const fallbackMessageId = params.messageIds[0];
|
||||
if (!params.text && !fallbackMessageId) {
|
||||
return false;
|
||||
}
|
||||
return params.echoCache.has(
|
||||
params.scope,
|
||||
{ text: params.text, messageId: fallbackMessageId },
|
||||
params.skipIdShortCircuit,
|
||||
);
|
||||
}
|
||||
|
||||
export type IMessageInboundDispatchDecision = {
|
||||
kind: "dispatch";
|
||||
isGroup: boolean;
|
||||
@@ -168,9 +212,9 @@ export function resolveIMessageInboundDecision(params: {
|
||||
// When true, the selfChatCache.has() check below must be skipped — we just
|
||||
// called remember() and would immediately match our own entry.
|
||||
let skipSelfChatHasCheck = false;
|
||||
const inboundMessageId =
|
||||
normalizeReplyField(params.message.guid) ??
|
||||
(params.message.id != null ? String(params.message.id) : undefined);
|
||||
const inboundMessageIds = resolveInboundEchoMessageIds(params.message);
|
||||
const inboundMessageId = inboundMessageIds[0];
|
||||
const hasInboundGuid = Boolean(normalizeReplyField(params.message.guid));
|
||||
|
||||
if (params.message.is_from_me) {
|
||||
// Always cache in selfChatCache so the upcoming is_from_me=false reflection
|
||||
@@ -190,11 +234,13 @@ export function resolveIMessageInboundDecision(params: {
|
||||
if (
|
||||
params.echoCache &&
|
||||
(bodyText || inboundMessageId) &&
|
||||
params.echoCache.has(
|
||||
echoScope,
|
||||
{ text: bodyText || undefined, messageId: inboundMessageId },
|
||||
!normalizeReplyField(params.message.guid),
|
||||
)
|
||||
hasIMessageEchoMatch({
|
||||
echoCache: params.echoCache,
|
||||
scope: echoScope,
|
||||
text: bodyText || undefined,
|
||||
messageIds: inboundMessageIds,
|
||||
skipIdShortCircuit: !hasInboundGuid,
|
||||
})
|
||||
) {
|
||||
return { kind: "drop", reason: "agent echo in self-chat" };
|
||||
}
|
||||
@@ -305,9 +351,11 @@ export function resolveIMessageInboundDecision(params: {
|
||||
sender,
|
||||
});
|
||||
if (
|
||||
params.echoCache.has(echoScope, {
|
||||
hasIMessageEchoMatch({
|
||||
echoCache: params.echoCache,
|
||||
scope: echoScope,
|
||||
text: bodyText || undefined,
|
||||
messageId: inboundMessageId,
|
||||
messageIds: inboundMessageIds,
|
||||
})
|
||||
) {
|
||||
params.logVerbose?.(
|
||||
|
||||
@@ -439,6 +439,39 @@ describe("self-chat is_from_me=true handling (Bruce Phase 2 fix)", () => {
|
||||
expect(decision).toEqual({ kind: "drop", reason: "agent echo in self-chat" });
|
||||
});
|
||||
|
||||
it("drops self-chat echo when outbound cache stored numeric id but inbound also carries a guid", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-03-24T12:00:00Z"));
|
||||
|
||||
const echoCache = createSentMessageCache();
|
||||
const selfChatCache = createSelfChatCache();
|
||||
|
||||
const scope = "default:imessage:+15551234567";
|
||||
echoCache.remember(scope, { text: "Numeric id echo", messageId: "123709" });
|
||||
|
||||
vi.advanceTimersByTime(1000);
|
||||
|
||||
const decision = resolveIMessageInboundDecision(
|
||||
createParams({
|
||||
message: {
|
||||
id: 123709,
|
||||
guid: "p:0/GUID-different-shape",
|
||||
sender: "+15551234567",
|
||||
chat_identifier: "+15551234567",
|
||||
text: "Numeric id echo",
|
||||
is_from_me: true,
|
||||
is_group: false,
|
||||
},
|
||||
messageText: "Numeric id echo",
|
||||
bodyText: "Numeric id echo",
|
||||
echoCache,
|
||||
selfChatCache,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(decision).toEqual({ kind: "drop", reason: "agent echo in self-chat" });
|
||||
});
|
||||
|
||||
it("does not drop a real self-chat image just because a recent agent image used the same placeholder", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-03-24T12:00:00Z"));
|
||||
|
||||
Reference in New Issue
Block a user