Skip to content

Commit 36f847a

Browse files
ai-hpcmcaxtr
andauthored
fix(whatsapp): ignore outbound echoes for inbound activity (#79057)
Merged via squash. Prepared head SHA: 3b1f38a Co-authored-by: ai-hpc <183861985+ai-hpc@users.noreply.github.com> Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com> Reviewed-by: @mcaxtr
1 parent 30214a4 commit 36f847a

4 files changed

Lines changed: 47 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ Docs: https://docs.openclaw.ai
598598
- Hooks/cron: log returned `/hooks/agent` isolated-run errors and failed cron jobs with cron diagnostic summaries, so rejected `payload.model` values are visible instead of looking like accepted-but-missing runs. Fixes #78597. (#78655) Thanks @kevinslin.
599599
- Managed proxy/security: classify raw socket callsites and proxy runtime mutations in boundary checks so new direct egress or unmanaged proxy-state changes cannot land without explicit review. (#77126) Thanks @jesse-merhi.
600600
- Channels/iMessage: surface the silent group-allowlist drop at default log level by emitting a one-time `warn` per account at monitor startup when `channels.imessage.groupPolicy: "allowlist"` is set without a `channels.imessage.groups` block, plus a one-time `warn` per `chat_id` when the runtime gate drops a specific group, naming the exact `channels.imessage.groups[...]` key to add to allow it. Fixes #78749. (#79190) Thanks @omarshahine.
601+
- WhatsApp: stop Gateway-originated outbound echoes from advancing inbound activity in `openclaw channels status`, so outbound self-sends no longer look like handled inbound messages. Fixes #79056. (#79057) Thanks @ai-hpc and @bittoby.
601602

602603
## 2026.5.3-1
603604

extensions/whatsapp/src/inbound/monitor.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ function isGroupJid(jid: string): boolean {
113113
return (typeof isJidGroup === "function" ? isJidGroup(jid) : jid.endsWith("@g.us")) === true;
114114
}
115115

116+
function recordAcceptedInboundActivity(accountId: string): void {
117+
recordChannelActivity({
118+
channel: "whatsapp",
119+
accountId,
120+
direction: "inbound",
121+
});
122+
}
123+
116124
function isRetryableSendDisconnectError(err: unknown): boolean {
117125
return /closed|reset|timed\s*out|disconnect|no active socket/i.test(formatError(err));
118126
}
@@ -799,11 +807,6 @@ export async function attachWebInboxToSocket(
799807
return;
800808
}
801809
for (const msg of upsert.messages ?? []) {
802-
recordChannelActivity({
803-
channel: "whatsapp",
804-
accountId: options.accountId,
805-
direction: "inbound",
806-
});
807810
const inbound = await normalizeInboundMessage(msg);
808811
if (!inbound) {
809812
continue;
@@ -832,6 +835,7 @@ export async function attachWebInboxToSocket(
832835
continue;
833836
}
834837

838+
recordAcceptedInboundActivity(options.accountId);
835839
await enqueueInboundMessage(msg, inbound, enriched);
836840
}
837841
};

extensions/whatsapp/src/monitor-inbox.allows-messages-from-senders-allowfrom-list.test-support.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest";
33
import {
44
buildNotifyMessageUpsert,
55
expectPairingPromptSent,
6+
getRecordChannelActivityMock,
67
installWebMonitorInboxUnitTestHooks,
78
mockLoadConfig,
89
settleInboundWork,
@@ -33,6 +34,20 @@ async function openInboxMonitor(onMessage = vi.fn()) {
3334
return { onMessage, listener, sock };
3435
}
3536

37+
function expectOnlyOutboundChannelActivity(accountId = "default") {
38+
const recordChannelActivityMock = getRecordChannelActivityMock();
39+
expect(recordChannelActivityMock).toHaveBeenCalledWith({
40+
channel: "whatsapp",
41+
accountId,
42+
direction: "outbound",
43+
});
44+
expect(recordChannelActivityMock).not.toHaveBeenCalledWith({
45+
channel: "whatsapp",
46+
accountId,
47+
direction: "inbound",
48+
});
49+
}
50+
3651
async function expectOutboundDmSkipsPairing(params: {
3752
selfChatMode: boolean;
3853
messageId: string;
@@ -294,6 +309,7 @@ describe("web monitor inbox", () => {
294309
await settleInboundWork();
295310

296311
expect(onMessage).not.toHaveBeenCalled();
312+
expectOnlyOutboundChannelActivity();
297313

298314
await listener.close();
299315
});
@@ -333,6 +349,7 @@ describe("web monitor inbox", () => {
333349
await settleInboundWork();
334350

335351
expect(onMessage).not.toHaveBeenCalled();
352+
expectOnlyOutboundChannelActivity();
336353

337354
await listener.close();
338355
});

extensions/whatsapp/src/monitor-inbox.test-harness.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ const sessionState = vi.hoisted(() => ({
5555
sock: undefined as MockSock | undefined,
5656
}));
5757

58+
const channelActivityMocks = vi.hoisted(() => ({
59+
recordChannelActivity: vi.fn(),
60+
}));
61+
62+
export function getRecordChannelActivityMock(): AnyMockFn {
63+
return channelActivityMocks.recordChannelActivity;
64+
}
65+
66+
vi.mock("openclaw/plugin-sdk/channel-activity-runtime", async () => {
67+
const actual = await vi.importActual<
68+
typeof import("openclaw/plugin-sdk/channel-activity-runtime")
69+
>("openclaw/plugin-sdk/channel-activity-runtime");
70+
return {
71+
...actual,
72+
recordChannelActivity: (...args: unknown[]) =>
73+
channelActivityMocks.recordChannelActivity(...args),
74+
};
75+
});
76+
5877
const inboundRuntimeMocks = vi.hoisted(() => {
5978
const wrapperKeys = [
6079
"ephemeralMessage",
@@ -277,6 +296,7 @@ export function installWebMonitorInboxUnitTestHooks(opts?: { authDir?: boolean }
277296
beforeEach(async () => {
278297
vi.useRealTimers();
279298
vi.clearAllMocks();
299+
channelActivityMocks.recordChannelActivity.mockClear();
280300
sessionState.sock = createMockSock();
281301
resetPairingSecurityMocks(DEFAULT_WEB_INBOX_CONFIG);
282302
if (!monitorWebInbox || !resetWebInboundDedupe) {

0 commit comments

Comments
 (0)