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:
+16
-4
@@ -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
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user