fix(messaging): persist getUpdates replay claims on both admission lanes - #41
Conversation
Claim/commit OpenClaw createClaimableDedupe tombstones (pluginId openclaw-weixin, account namespace, 24h) inside ordinary and approval lanes so at-least-once iLink replays do not run twice across restart. Stable MessageSid follows the same transport key when present. Port of Tencent/openclaw-weixin#240 for the community distribution. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Adds persistent, account-scoped replay protection for inbound Weixin updates.
Changes:
- Adds stable dedupe keys and 24-hour tombstones.
- Integrates dedupe with ordinary and approval lanes.
- Adds tests and bilingual documentation.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/monitor/monitor.ts |
Integrates claims into admission lanes. |
src/monitor/monitor.test.ts |
Tests duplicate suppression across lanes. |
src/messaging/inbound-dedupe.ts |
Implements persistent replay dedupe. |
src/messaging/inbound-dedupe.test.ts |
Tests keys, TTL, isolation, and persistence. |
src/messaging/inbound.ts |
Derives stable message SIDs. |
src/messaging/inbound.test.ts |
Tests stable SID generation. |
docs/architecture.md |
Documents tombstone storage. |
CHANGELOG.md |
Documents the fix in Chinese. |
CHANGELOG_EN.md |
Documents the fix in English. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Use the resolveFilePath createClaimableDedupe shape so min-host CI can typecheck, and soften Windows temp-dir cleanup when the state DB stays open. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Pushed a CI fix:
|
Wait on ClaimableDedupe inflight pending so a replay is dropped only after the owner commits; reclaim if the owner releases. Duplicate logs keep only non-sensitive identity kinds (mid/cid/seq/body). Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed the remaining Copilot notes:
(The earlier |
NewFuture
left a comment
There was a problem hiding this comment.
Two blocking claim/lane lifecycle issues and one conditional fallback-key collision remain. Details and suggested regression tests are inline.
Observe ClaimableDedupe inflight pending outside ordinary/approval lanes and re-enqueue only after owner release; wrap all post-admission work in the claim lifecycle; prefer item msg_id digests and never key by sender alone for empty-body media. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed the three blocking review notes:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/monitor/monitor.ts:163
- Add a regression test for this release/re-enqueue branch: let an owner release the lane via
onReplyAdmitted, queue an in-flight replay, then make the owner throw and assert that the replay re-enters admission and runs exactly once. The current tests cover an in-flight owner that commits and a later delivery after preprocessing failure, but not this out-of-band retry path, so a regression here could silently discard the waiting replay.
// Owner released — re-enter admission so the message is not lost.
scheduleInboundMessage(full);
src/messaging/inbound-dedupe.ts:53
- This selects the legacy
resolveFilePath/fileMaxEntriesAPI, which contradicts the PR note claimingpluginId/stateMaxEntriesplugin-state persistence and “not a custom JSON path.” On the minimum 2026.6.1 host this shape writes the stated JSON file; on newer hosts it is mapped as a legacy core-owned SQLite namespace rather than state owned byopenclaw-weixin. Please align the implementation and PR description—if the legacy shape is required for minimum-host compatibility, the PR note should say so explicitly.
return createClaimableDedupe({
...base,
fileMaxEntries: WEIXIN_INBOUND_DEDUPE_FILE_MAX,
resolveFilePath: resolveReplayDedupeFilePath,
onDiskError: onReplayDedupeDiskError,
|
@copilot resolve the merge conflicts in this pull request |
Resolve Unreleased changelog conflicts by keeping the docs site Added notes from main and the inbound replay-dedupe Fixed notes from this branch. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Author-led port of Tencent/openclaw-weixin#240 onto current community
main, as invited on that PR.message_id→client_id→seq→ body fingerprint) via OpenClawcreateClaimableDedupeinside ordinary and approval admission lanes, then commit a 24h plugin-state tombstone (pluginId: openclaw-weixin, account-scoped namespace) so at-least-oncegetUpdatesreplays do not run the AI pipeline twice — including across process restart.message_idare not swallowed.MessageSiduses the same key when transport ids are present.Tracks #36
Notes for reviewers
pluginId+stateMaxEntries), not a custom JSON path underopenclaw-weixin/.Test plan
npm run check:fast(Node 22.22.3)message_idreplay is dropped/approve plugin:…still bypasses an active ordinary turn; duplicate approval deliveries process onceMade with Cursor