fix(wechat): defer 45047/45015 delivery and control customer message budget
Memind CI / Test, build, and release guards (push) Failing after 10s

Persist formal replies when WeChat customer-service quota is exhausted,
claim them once on the next passive response, skip progress notices for
page generation, and suppress failure notices while a formal reply is pending.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-02 09:21:43 +08:00
parent db53090ba1
commit 3e8cdfdda3
5 changed files with 508 additions and 103 deletions
+56 -4
View File
@@ -1220,7 +1220,7 @@ test('wechat mp service splits long agent replies into multiple customer message
assert.equal(combined, longReply);
});
test('wechat mp service surfaces wechat customer-service errcode and errmsg', async () => {
test('wechat mp service defers 45047 customer-service delivery instead of throwing', async () => {
const service = createBoundWechatService({
userAuth: {
async getWechatOpenidForUser() {
@@ -1241,10 +1241,62 @@ test('wechat mp service surfaces wechat customer-service errcode and errmsg', as
},
});
await assert.rejects(
service.sendTextToUser('user-1', 'hello'),
/errcode=45047.*out of response count limit/,
const result = await service.sendTextToUser('user-1', 'hello');
assert.equal(result.deferred, true);
assert.equal(result.errcode, 45047);
});
test('wechat mp service claims deferred delivery on next passive response once', async () => {
const token = 'token';
const timestamp = '1710000000';
const nonce = 'nonce';
const service = createBoundWechatService({
config: {
progressDelayMs: 0,
ackText: '已收到',
},
userAuth: {
async getWechatOpenidForUser() {
return 'openid-1';
},
},
wechatFetch: async (url) => {
if (String(url).includes('/cgi-bin/stable_token')) {
return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
return new Response(JSON.stringify({ errcode: 45047, errmsg: 'out of response count limit rid: test-rid' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
},
});
const deferred = await service.sendTextToUser(
'user-1',
'页面已生成:https://example.com/MindSpace/user-1/public/demo.html',
);
assert.equal(deferred.deferred, true);
assert.equal(deferred.errcode, 45047);
const claimed = await service.handleInboundMessage(inboundXml({ content: '继续' }), {
timestamp,
nonce,
signature: signatureFor(token, timestamp, nonce),
});
assert.equal(claimed.status, 200);
assert.match(String(claimed.body), /demo\.html/);
assert.doesNotMatch(String(claimed.body), /已收到/);
const followUp = await service.handleInboundMessage(inboundXml({ content: '再来一条' }), {
timestamp,
nonce,
signature: signatureFor(token, timestamp, nonce),
});
assert.doesNotMatch(String(followUp.body), /demo\.html/);
assert.ok(followUp.task);
});
test('wechat mp service strips markdown emphasis around outbound links', async () => {