fix: remove silent run budget
This commit is contained in:
@@ -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<string, string> = {};
|
||||
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 {
|
||||
|
||||
@@ -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<typeof setTimeout> | 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',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user