fix: preserve unsent chunks on flush failure and apply rate-limit cooldown

flushText previously aborted the send loop on the first failure and
dropped all remaining chunks, because the buffer had already been
cleared. Re-enqueue the unsent chunks at the front of the buffer so the
next flush retries them instead of silently losing content.

On ret:-2 rate-limit, push the per-user nextSendTime out by 60s so
subsequent sends queue for the cooldown rather than each one retrying
into the wall and exhausting its own retries.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Wechat-ggGitHub
2026-06-20 11:18:36 +08:00
parent 8aead25c93
commit d6d7d62a65
2 changed files with 24 additions and 5 deletions
+16 -4
View File
@@ -515,13 +515,25 @@ async function sendToClaude(
flushChain = flushChain.then(async () => {
const chunks = splitMessage(captured);
for (const chunk of chunks) {
await sender.sendText(fromUserId, contextToken, chunk);
for (let i = 0; i < chunks.length; i++) {
try {
await sender.sendText(fromUserId, contextToken, chunks[i]);
} catch (err) {
// Rate-limit exhaustion etc.: put the unsent chunks back at the
// front of the buffer so the next flush retries them. Content is
// never silently dropped (previously the for-loop aborted here and
// the already-cleared buffer lost everything from this chunk on).
const remaining = chunks.slice(i).join('\n\n');
textBuffer = remaining + (textBuffer ? '\n\n' + textBuffer : '');
logger.warn('flushText send failed, content retained for retry', {
error: err instanceof Error ? err.message : String(err),
retainedChunks: chunks.length - i,
});
return;
}
}
anySent = true;
lastSentTime = Date.now();
}).catch((err) => {
logger.error('flushText send failed', { error: err instanceof Error ? err.message : String(err) });
});
return flushChain;
}
+8 -1
View File
@@ -20,6 +20,11 @@ export class WeChatApi {
private readonly uin: string;
private readonly nextSendTime = new Map<string, number>();
private static readonly MIN_SEND_INTERVAL = 2500;
// Cooldown applied after a rate-limit (ret:-2). Observed WeChat cooldown can
// last ~2-3 minutes under sustained sending; pushing nextSendTime this far
// out makes subsequent sends queue for the cooldown instead of each one
// independently hitting the wall and exhausting its own retries.
private static readonly RATE_LIMIT_COOLDOWN_MS = 60_000;
constructor(token: string, baseUrl: string = 'https://ilinkai.weixin.qq.com') {
if (baseUrl) {
@@ -118,7 +123,9 @@ export class WeChatApi {
const res = await this.request<{ ret?: number }>('ilink/bot/sendmessage', req);
if (res.ret === -2) {
if (userId) {
this.nextSendTime.set(userId, Date.now() + delay + WeChatApi.MIN_SEND_INTERVAL);
// Push the per-user send clock past the observed cooldown window so
// later sends wait it out instead of retrying into the wall.
this.nextSendTime.set(userId, Date.now() + WeChatApi.RATE_LIMIT_COOLDOWN_MS);
}
if (attempt === MAX_RETRIES) {
logger.warn('sendMessage rate-limited after max retries', { attempts: MAX_RETRIES });