mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 15:31:27 +00:00
fix: clear retry error messages promptly after auto-retry succeeds
Previously, when an auto-retry countdown elapsed and the server sent a Retry event (without retryInfo) to signal the actual retry attempt, the error message was not cleared because `pendingRetryCountdownItemRef` was still set. This caused stale error messages to persist in the UI until the user manually initiated a new request. Additionally, when the user pressed Ctrl+Y to retry, the error was committed to history (without hint) instead of being discarded. This was inconsistent with the auto-retry behavior where errors are silently cleared on success. Changes: - Always call clearRetryCountdown() when a Retry event without retryInfo is received, removing the flawed guard condition - Remove error-to-history commit in retryLastPrompt for consistent UX - Add test covering the countdown-elapsed retry scenario Closes #2310 Made-with: Cursor
This commit is contained in:
parent
e181cfc097
commit
d951e30cfa
2 changed files with 110 additions and 14 deletions
|
|
@ -2244,6 +2244,7 @@ describe('useGeminiStream', () => {
|
|||
it('should show a retry countdown and update pending history over time', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
let continueToRetryAttempt: (() => void) | undefined;
|
||||
let resolveStream: (() => void) | undefined;
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
|
|
@ -2256,6 +2257,9 @@ describe('useGeminiStream', () => {
|
|||
delayMs: 3000,
|
||||
},
|
||||
};
|
||||
await new Promise<void>((resolve) => {
|
||||
continueToRetryAttempt = resolve;
|
||||
});
|
||||
yield {
|
||||
type: ServerGeminiEventType.Retry,
|
||||
};
|
||||
|
|
@ -2330,6 +2334,12 @@ describe('useGeminiStream', () => {
|
|||
'2s',
|
||||
);
|
||||
|
||||
continueToRetryAttempt?.();
|
||||
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
resolveStream?.();
|
||||
|
||||
await act(async () => {
|
||||
|
|
@ -2347,6 +2357,103 @@ describe('useGeminiStream', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should clear retry errors after auto-retry succeeds once the countdown has elapsed', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
let continueAfterCountdown: (() => void) | undefined;
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.Retry,
|
||||
retryInfo: {
|
||||
message: '[API Error: Rate limit exceeded]',
|
||||
attempt: 1,
|
||||
maxRetries: 3,
|
||||
delayMs: 1000,
|
||||
},
|
||||
};
|
||||
await new Promise<void>((resolve) => {
|
||||
continueAfterCountdown = resolve;
|
||||
});
|
||||
yield {
|
||||
type: ServerGeminiEventType.Retry,
|
||||
};
|
||||
yield {
|
||||
type: ServerGeminiEventType.Text,
|
||||
value: 'Success after retry',
|
||||
};
|
||||
yield {
|
||||
type: ServerGeminiEventType.Finished,
|
||||
value: { reason: 'STOP', usageMetadata: undefined },
|
||||
};
|
||||
})(),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGeminiStream(
|
||||
new MockedGeminiClientClass(mockConfig),
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockLoadedSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => 'vscode' as EditorType,
|
||||
() => {},
|
||||
() => Promise.resolve(),
|
||||
false,
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
80,
|
||||
24,
|
||||
),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
void result.current.submitQuery('Trigger retry after countdown');
|
||||
});
|
||||
|
||||
let errorItem = result.current.pendingHistoryItems.find(
|
||||
(item) => item.type === MessageType.ERROR,
|
||||
) as { hint?: string } | undefined;
|
||||
for (let attempts = 0; attempts < 5 && !errorItem; attempts++) {
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
errorItem = result.current.pendingHistoryItems.find(
|
||||
(item) => item.type === MessageType.ERROR,
|
||||
) as { hint?: string } | undefined;
|
||||
}
|
||||
expect(errorItem?.hint).toContain('1s');
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
});
|
||||
|
||||
const staleErrorBeforeRetryCompletes =
|
||||
result.current.pendingHistoryItems.find(
|
||||
(item) => item.type === MessageType.ERROR,
|
||||
) as { hint?: string } | undefined;
|
||||
expect(staleErrorBeforeRetryCompletes?.hint).toContain('0s');
|
||||
|
||||
await act(async () => {
|
||||
continueAfterCountdown?.();
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const remainingError = result.current.pendingHistoryItems.find(
|
||||
(item) => item.type === MessageType.ERROR,
|
||||
);
|
||||
expect(remainingError).toBeUndefined();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('should memoize pendingHistoryItems', () => {
|
||||
mockUseReactToolScheduler.mockReturnValue([
|
||||
[],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue