fix: prevent infinite message replay on container timeout (#164)

Container timeout and idle timeout both fire at 30min, racing the
graceful shutdown. The hard kill returns error status, rolling back
the message cursor even though output was already sent — causing
duplicate messages indefinitely.

- Grace period: hard timeout is now IDLE_TIMEOUT + 30s minimum
- Timeout after output resolves as success (idle cleanup, not failure)
- Don't roll back cursor if output was already sent to user
- Remove src/telegram.ts and config vars (added to PR #156 by mistake)
- Add typecheck step to CI workflow
- Add container-runner timeout behavior tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-02-11 17:25:42 +02:00
parent 2b56fecfdc
commit 8eb80d4ed0
6 changed files with 239 additions and 336 deletions

View File

@@ -167,6 +167,7 @@ async function processGroupMessages(chatJid: string): Promise<boolean> {
await whatsapp.setTyping(chatJid, true);
let hadError = false;
let outputSentToUser = false;
const output = await runAgent(group, prompt, chatJid, async (result) => {
// Streaming output callback — called for each agent result
@@ -177,6 +178,7 @@ async function processGroupMessages(chatJid: string): Promise<boolean> {
logger.info({ group: group.name }, `Agent output: ${raw.slice(0, 200)}`);
if (text) {
await whatsapp.sendMessage(chatJid, `${ASSISTANT_NAME}: ${text}`);
outputSentToUser = true;
}
// Only reset idle timer on actual results, not session-update markers (result: null)
resetIdleTimer();
@@ -191,6 +193,12 @@ async function processGroupMessages(chatJid: string): Promise<boolean> {
if (idleTimer) clearTimeout(idleTimer);
if (output === 'error' || hadError) {
// If we already sent output to the user, don't roll back the cursor —
// the user got their response and re-processing would send duplicates.
if (outputSentToUser) {
logger.warn({ group: group.name }, 'Agent error after output was sent, skipping cursor rollback to prevent duplicates');
return true;
}
// Roll back cursor so retries can re-process these messages
lastAgentTimestamp[chatJid] = previousCursor;
saveState();