From 3435b99cfcbe77f017683c309c0cebea068645d1 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Mon, 23 Mar 2026 04:07:12 +0900 Subject: [PATCH] fix: remove silent run budget --- src/message-runtime.test.ts | 97 +++++++++++-------------------------- src/message-runtime.ts | 77 +---------------------------- 2 files changed, 30 insertions(+), 144 deletions(-) diff --git a/src/message-runtime.test.ts b/src/message-runtime.test.ts index 3a05f14..69fd571 100644 --- a/src/message-runtime.test.ts +++ b/src/message-runtime.test.ts @@ -896,7 +896,7 @@ describe('createMessageRuntime', () => { } }); - it('retries a silent run internally before any visible output and succeeds in the same queue item', async () => { + it('does not retry or emit a synthetic final when a run completes silently', async () => { const chatJid = 'group@test'; const group = makeGroup('codex'); const channel = makeChannel(chatJid); @@ -915,25 +915,11 @@ describe('createMessageRuntime', () => { }, ]); - vi.mocked(agentRunner.runAgentProcess) - .mockResolvedValueOnce({ - status: 'success', - result: null, - newSessionId: 'session-silent-rollover', - }) - .mockImplementationOnce(async (_group, _input, _onProcess, onOutput) => { - await onOutput?.({ - status: 'success', - phase: 'final', - result: '두 번째 시도에서 성공했습니다.', - newSessionId: 'session-silent-rollover', - }); - return { - status: 'success', - result: null, - newSessionId: 'session-silent-rollover', - }; - }); + vi.mocked(agentRunner.runAgentProcess).mockResolvedValue({ + status: 'success', + result: null, + newSessionId: 'session-silent-run', + }); const runtime = createMessageRuntime({ assistantName: 'Andy', @@ -963,17 +949,14 @@ describe('createMessageRuntime', () => { }); expect(result).toBe(true); - expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(2); + expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(1); expect(saveState).toHaveBeenCalled(); expect(lastAgentTimestamps[chatJid]).toBe('1'); - expect(channel.sendMessage).toHaveBeenCalledWith( - chatJid, - '두 번째 시도에서 성공했습니다.', - ); + expect(channel.sendMessage).not.toHaveBeenCalled(); expect(channel.sendAndTrack).not.toHaveBeenCalled(); }); - it('retries a silent run with the original prompt still included after a queued follow-up arrives', async () => { + it('does not emit a synthetic failure final when follow-up activity happens during a silent run', async () => { const chatJid = 'group@test'; const group = makeGroup('codex'); const channel = makeChannel(chatJid); @@ -1043,36 +1026,20 @@ describe('createMessageRuntime', () => { }, ); - vi.mocked(agentRunner.runAgentProcess) - .mockImplementationOnce(async () => { - lastAgentTimestamps[chatJid] = '2'; - followUpPersisted = true; - activityTouch?.({ - source: 'follow-up', - textLength: 9, - filename: 'follow-up.json', - }); - return { - status: 'success', - result: null, - newSessionId: 'session-silent-follow-up', - }; - }) - .mockImplementationOnce(async (_group, input, _onProcess, onOutput) => { - expect(input.prompt).toContain('hello'); - expect(input.prompt).toContain('follow-up'); - await onOutput?.({ - status: 'success', - phase: 'final', - result: '원래 요청과 follow-up을 같이 처리했습니다.', - newSessionId: 'session-silent-follow-up', - }); - return { - status: 'success', - result: null, - newSessionId: 'session-silent-follow-up', - }; + vi.mocked(agentRunner.runAgentProcess).mockImplementationOnce(async () => { + lastAgentTimestamps[chatJid] = '2'; + followUpPersisted = true; + activityTouch?.({ + source: 'follow-up', + textLength: 9, + filename: 'follow-up.json', }); + return { + status: 'success', + result: null, + newSessionId: 'session-silent-follow-up', + }; + }); const runtime = createMessageRuntime({ assistantName: 'Andy', @@ -1106,13 +1073,10 @@ describe('createMessageRuntime', () => { }); expect(result).toBe(true); - expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(2); + expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(1); expect(saveState).toHaveBeenCalled(); expect(lastAgentTimestamps[chatJid]).toBe('2'); - expect(channel.sendMessage).toHaveBeenCalledWith( - chatJid, - '원래 요청과 follow-up을 같이 처리했습니다.', - ); + expect(channel.sendMessage).not.toHaveBeenCalled(); }); it('resets tracked progress after a final output that becomes empty after formatting', async () => { @@ -1549,12 +1513,11 @@ describe('createMessageRuntime', () => { } }); - it('emits one visible failure final after silent rollovers exhaust the quiet budget', async () => { + it('does not emit a visible failure final when a run stays silent', async () => { vi.useFakeTimers(); const chatJid = 'group@test'; const group = makeGroup('codex'); const channel = makeChannel(chatJid); - const closeStdin = vi.fn(); const lastAgentTimestamps: Record = {}; const saveState = vi.fn(); @@ -1588,7 +1551,7 @@ describe('createMessageRuntime', () => { channels: [channel], queue: { registerProcess: vi.fn(), - closeStdin, + closeStdin: vi.fn(), notifyIdle: vi.fn(), } as any, getRegisteredGroups: () => ({ [chatJid]: group }), @@ -1608,12 +1571,8 @@ describe('createMessageRuntime', () => { }); expect(result).toBe(true); - expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(3); - expect(channel.sendMessage).toHaveBeenCalledTimes(1); - expect(channel.sendMessage).toHaveBeenCalledWith( - chatJid, - '요청을 완료하지 못했습니다. 다시 시도해 주세요.', - ); + expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(1); + expect(channel.sendMessage).not.toHaveBeenCalled(); expect(lastAgentTimestamps[chatJid]).toBe('1'); expect(saveState).toHaveBeenCalled(); } finally { diff --git a/src/message-runtime.ts b/src/message-runtime.ts index 3f7a7ce..bf72acd 100644 --- a/src/message-runtime.ts +++ b/src/message-runtime.ts @@ -565,18 +565,8 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { const isMainGroup = group.isMain === true; const isClaudeCodeAgent = (group.agentType || 'claude-code') === 'claude-code'; - const QUIET_RUN_ROLLOVER_MS = Math.min(deps.idleTimeout, 10_000); - const MAX_SILENT_ROLLOVERS = 2; - const MAX_TOTAL_SILENT_WAIT_MS = - QUIET_RUN_ROLLOVER_MS * (MAX_SILENT_ROLLOVERS + 1); const FAILURE_FINAL_TEXT = '요청을 완료하지 못했습니다. 다시 시도해 주세요.'; - const queueItemBaseCursor = normalizeStoredSeqCursor( - deps.getLastAgentTimestamps()[chatJid] || '0', - chatJid, - ); - let silentRolloverCount = 0; - let totalSilentWaitMs = 0; while (true) { const sinceSeqCursor = deps.getLastAgentTimestamps()[chatJid] || '0'; @@ -656,7 +646,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { } const prompt = formatMessages(missedMessages, deps.timezone); - const previousCursor = deps.getLastAgentTimestamps()[chatJid] || '0'; const startSeq = missedMessages[0].seq ?? null; const endSeq = missedMessages[missedMessages.length - 1].seq ?? null; if (endSeq !== null) { @@ -670,8 +659,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { groupFolder: group.folder, runId, messageCount: missedMessages.length, - silentRolloverCount, - totalSilentWaitMs, }, 'Dispatching queued messages to agent', ); @@ -685,7 +672,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { let idleTimer: ReturnType | null = null; let hadError = false; let producedDeliverySucceeded = true; - let quietStopReason: string | null = null; let visiblePhase: VisiblePhase = 'silent'; let latestProgressText: string | null = null; let latestProgressRendered: string | null = null; @@ -697,7 +683,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { let poisonedSessionDetected = false; let canPipeFollowUps = true; let pendingFollowUp: PendingFollowUp = null; - const attemptStartedAt = Date.now(); const hasVisibleOutput = () => visiblePhase !== 'silent'; const terminalObserved = () => visiblePhase === 'final'; @@ -935,10 +920,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { !hasVisibleOutput() && pendingFollowUp ? 'follow-up-no-output-preemption' : 'idle-timeout'; - quietStopReason ??= - !hasVisibleOutput() && pendingFollowUp - ? 'follow-up-no-output-timeout' - : 'idle-timeout'; if (!hasVisibleOutput() && pendingFollowUp) { logger.warn( { @@ -962,7 +943,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { } deps.queue.closeStdin(chatJid, { runId, reason: closeReason }); }, - hasVisibleOutput() ? deps.idleTimeout : QUIET_RUN_ROLLOVER_MS, + deps.idleTimeout, ); }; @@ -1112,8 +1093,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { await finalizeProgressMessage(); latestProgressTextForFinal = null; } else { - quietStopReason ??= - result.status === 'success' ? 'success-null-result' : 'error'; await finalizeProgressMessage(); } @@ -1142,9 +1121,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { if (output === 'error') { hadError = true; - quietStopReason ??= 'error'; - } else if (visiblePhase === 'silent') { - quietStopReason ??= 'success-null-result'; } const settledVisiblePhase = visiblePhase as VisiblePhase; @@ -1169,64 +1145,17 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { } else if ( settledVisiblePhase === 'progress' && !terminalObserved() && - (hadError || quietStopReason === 'idle-timeout') + hadError ) { await publishFailureFinal(); } - const pendingFollowUpSnapshot = pendingFollowUp as PendingFollowUp; - const quietWaitStartedAt = - pendingFollowUpSnapshot?.queuedAt ?? attemptStartedAt; - const quietWaitMs = Date.now() - quietWaitStartedAt; - clearProgressTicker(); if (idleTimer) clearTimeout(idleTimer); deps.queue.setActivityTouch?.(chatJid, null); deps.queue.setFollowUpPipeAllowed?.(chatJid, null); clearPendingFollowUp(); - if (settledVisiblePhase === 'silent') { - const nextTotalSilentWaitMs = totalSilentWaitMs + quietWaitMs; - const canRolloverAgain = - silentRolloverCount < MAX_SILENT_ROLLOVERS && - nextTotalSilentWaitMs <= MAX_TOTAL_SILENT_WAIT_MS; - - if (canRolloverAgain) { - deps.getLastAgentTimestamps()[chatJid] = queueItemBaseCursor; - deps.saveState(); - silentRolloverCount++; - totalSilentWaitMs = nextTotalSilentWaitMs; - logger.warn( - { - chatJid, - group: group.name, - groupFolder: group.folder, - runId, - quietStopReason, - rollbackCursor: queueItemBaseCursor, - silentRolloverCount, - totalSilentWaitMs, - }, - 'Retrying silent run within the same queue item', - ); - continue; - } - - logger.warn( - { - chatJid, - group: group.name, - groupFolder: group.folder, - runId, - quietStopReason, - silentRolloverCount, - totalSilentWaitMs: nextTotalSilentWaitMs, - }, - 'Silent run budget exhausted, emitting failure final', - ); - await publishFailureFinal(); - } - if (!producedDeliverySucceeded) { logger.warn( { chatJid, group: group.name, groupFolder: group.folder, runId }, @@ -1242,8 +1171,6 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): { groupFolder: group.folder, runId, visiblePhase: settledVisiblePhase, - silentRolloverCount, - totalSilentWaitMs, }, 'Queued run completed successfully', );