fix(signal): land #31138 syncMessage presence filtering (@Sid-Qin)
Landed from contributor PR #31138 by @Sid-Qin. Co-authored-by: Sid-Qin <sidqin0410@gmail.com>
This commit is contained in:
@@ -112,6 +112,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Signal/Sync message null-handling: treat `syncMessage` presence (including `null`) as sync envelope traffic so replayed sentTranscript payloads cannot bypass loop guards after daemon restart. Landed from contributor PR #31138 by @Sid-Qin. Thanks @Sid-Qin.
|
||||
- Feishu/Multi-account + reply reliability: add `channels.feishu.defaultAccount` outbound routing support with schema validation, prevent inbound preview text from leaking into prompt system events, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as `msg_type: "file"`, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #31209, #29610, #30432, #30331, and #29501. Thanks @stakeswky, @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.
|
||||
- Google Chat/Thread replies: set `messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD` on threaded sends so replies attach to existing threads instead of silently failing thread placement. Landed from contributor PR #30965 by @novan. Thanks @novan.
|
||||
- Mattermost/Private channel policy routing: map Mattermost private channel type `P` to group chat type so `groupPolicy`/`groupAllowFrom` gates apply correctly instead of being treated as open public channels. Landed from contributor PR #30891 by @BlueBirdBack. Thanks @BlueBirdBack.
|
||||
|
||||
@@ -201,4 +201,29 @@ describe("signal createSignalEventHandler inbound contract", () => {
|
||||
expect(capture.ctx).toBeUndefined();
|
||||
expect(dispatchInboundMessageMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("drops sync envelopes when syncMessage is present but null", async () => {
|
||||
const handler = createSignalEventHandler(
|
||||
createBaseSignalEventHandlerDeps({
|
||||
cfg: {
|
||||
messages: { inbound: { debounceMs: 0 } },
|
||||
channels: { signal: { dmPolicy: "open", allowFrom: ["*"] } },
|
||||
},
|
||||
historyLimit: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
await handler(
|
||||
createSignalReceiveEvent({
|
||||
syncMessage: null,
|
||||
dataMessage: {
|
||||
message: "replayed sentTranscript envelope",
|
||||
attachments: [],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(capture.ctx).toBeUndefined();
|
||||
expect(dispatchInboundMessageMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -438,9 +438,11 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For non-own sync messages (e.g., messages synced from other devices),
|
||||
// we could process them but for now we skip to be conservative
|
||||
if (envelope.syncMessage) {
|
||||
// Filter all sync messages (sentTranscript, readReceipts, etc.).
|
||||
// signal-cli may set syncMessage to null instead of omitting it, so
|
||||
// check property existence rather than truthiness to avoid replaying
|
||||
// the bot's own sent messages on daemon restart.
|
||||
if ("syncMessage" in envelope) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user