fix: prevent typing indicator leak on agent run exceptions and follow-up race
Two leak paths caused Discord typing indicators to persist indefinitely: 1. If runAgent() threw before turnController.finish(), setTyping(false) was never called. Wrap the agent run in try/finally to guarantee cleanup. 2. Follow-up message piping called setTyping(true) fire-and-forget. If the Promise resolved after finish()'s setTyping(false), the typing interval was recreated with nobody to clear it. Await the call to serialize the on/off sequence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -345,33 +345,40 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
|||||||
|
|
||||||
await turnController.start();
|
await turnController.start();
|
||||||
|
|
||||||
const output = await runAgent(group, prompt, chatJid, runId, (result) =>
|
try {
|
||||||
turnController.handleOutput(result),
|
const output = await runAgent(group, prompt, chatJid, runId, (result) =>
|
||||||
);
|
turnController.handleOutput(result),
|
||||||
|
|
||||||
const { deliverySucceeded, visiblePhase } =
|
|
||||||
await turnController.finish(output);
|
|
||||||
|
|
||||||
if (!deliverySucceeded) {
|
|
||||||
logger.warn(
|
|
||||||
{ chatJid, group: group.name, groupFolder: group.folder, runId },
|
|
||||||
'Persisted produced output for delivery retry without rerunning agent',
|
|
||||||
);
|
);
|
||||||
return false;
|
|
||||||
|
const { deliverySucceeded, visiblePhase } =
|
||||||
|
await turnController.finish(output);
|
||||||
|
|
||||||
|
if (!deliverySucceeded) {
|
||||||
|
logger.warn(
|
||||||
|
{ chatJid, group: group.name, groupFolder: group.folder, runId },
|
||||||
|
'Persisted produced output for delivery retry without rerunning agent',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
{
|
||||||
|
chatJid,
|
||||||
|
group: group.name,
|
||||||
|
groupFolder: group.folder,
|
||||||
|
runId,
|
||||||
|
visiblePhase,
|
||||||
|
},
|
||||||
|
'Queued run completed successfully',
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
// Safety net: always clear typing even if runAgent() or finish() throws.
|
||||||
|
// Prevents stuck typing indicators when exceptions bypass the normal
|
||||||
|
// turnController.finish() → setTyping(false) path.
|
||||||
|
await channel.setTyping?.(chatJid, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
|
||||||
{
|
|
||||||
chatJid,
|
|
||||||
group: group.name,
|
|
||||||
groupFolder: group.folder,
|
|
||||||
runId,
|
|
||||||
visiblePhase,
|
|
||||||
},
|
|
||||||
'Queued run completed successfully',
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -523,7 +530,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
|||||||
endSeq,
|
endSeq,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
channel
|
await channel
|
||||||
.setTyping?.(chatJid, true)
|
.setTyping?.(chatJid, true)
|
||||||
?.catch((err) =>
|
?.catch((err) =>
|
||||||
logger.warn(
|
logger.warn(
|
||||||
|
|||||||
Reference in New Issue
Block a user