fix(redstone): close race in withSlowToolIndicator orphaning wiki loop

The setTimeout callback awaited sendMessage before installing the
rotation interval. If the tool resolved during that await, the finally
cleanup ran before rotateTimer existed, leaving an interval that edited
the "reading the wiki…" placeholder forever. Added a `done` flag so the
callback bails (and deletes the placeholder) if the tool already finished.
This commit is contained in:
2026-05-14 12:42:55 +02:00
parent 3647174c30
commit 978211dc65

View File

@@ -1524,10 +1524,19 @@ async function withSlowToolIndicator<T>(chatId: number, toolName: string, fn: ()
if (!WIKI_TOOL_RE.test(toolName)) return fn(); if (!WIKI_TOOL_RE.test(toolName)) return fn();
let placeholderId: number | null = null; let placeholderId: number | null = null;
let rotateTimer: ReturnType<typeof setInterval> | null = null; let rotateTimer: ReturnType<typeof setInterval> | null = null;
let done = false;
let phraseIdx = 0; let phraseIdx = 0;
const showTimer = setTimeout(async () => { const showTimer = setTimeout(async () => {
try { try {
const m = await bot.api.sendMessage(chatId, SLOW_TOOL_PHRASES[0]); const m = await bot.api.sendMessage(chatId, SLOW_TOOL_PHRASES[0]);
// fn() may have resolved while sendMessage was in flight — if so the
// finally block already ran and saw no placeholder/timer to clean up.
// Delete the just-sent message and bail instead of installing an
// orphaned rotation loop that nothing will ever clear.
if (done) {
try { await bot.api.deleteMessage(chatId, m.message_id); } catch { /* ignore */ }
return;
}
placeholderId = m.message_id; placeholderId = m.message_id;
rotateTimer = setInterval(async () => { rotateTimer = setInterval(async () => {
phraseIdx = (phraseIdx + 1) % SLOW_TOOL_PHRASES.length; phraseIdx = (phraseIdx + 1) % SLOW_TOOL_PHRASES.length;
@@ -1539,6 +1548,7 @@ async function withSlowToolIndicator<T>(chatId: number, toolName: string, fn: ()
try { try {
return await fn(); return await fn();
} finally { } finally {
done = true;
clearTimeout(showTimer); clearTimeout(showTimer);
if (rotateTimer) clearInterval(rotateTimer); if (rotateTimer) clearInterval(rotateTimer);
if (placeholderId != null) { if (placeholderId != null) {