fix: prevent silent refresh from overwriting newly created conversations in EditorAgentConversation
This commit is contained in:
+92
@@ -450,6 +450,98 @@ describe('useEditorAgentConversation', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('stops the first send while conversation creation is pending', async () => {
|
||||
const client = createClient();
|
||||
let resolveCreate!: (detail: EditorAgentConversationDetail) => void;
|
||||
vi.mocked(client.listConversations).mockResolvedValueOnce([]);
|
||||
vi.mocked(client.createConversation).mockImplementationOnce(
|
||||
() =>
|
||||
new Promise<EditorAgentConversationDetail>((resolve) => {
|
||||
resolveCreate = resolve;
|
||||
}),
|
||||
);
|
||||
const { result } = renderHook(() =>
|
||||
useEditorAgentConversation({ projectId: 'project-1', client }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoadingConversations).toBe(false);
|
||||
});
|
||||
|
||||
let sendPromise!: Promise<void>;
|
||||
act(() => {
|
||||
sendPromise = result.current.sendMessage('首次创建时停止');
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(client.createConversation).toHaveBeenCalledWith('project-1', {});
|
||||
});
|
||||
|
||||
expect(result.current.isWaiting).toBe(true);
|
||||
act(() => {
|
||||
result.current.stopCurrentTurn();
|
||||
});
|
||||
expect(result.current.isAborting).toBe(true);
|
||||
|
||||
await act(async () => {
|
||||
resolveCreate({
|
||||
conversationId: 'conversation-2',
|
||||
projectId: 'project-1',
|
||||
title: '新对话',
|
||||
messages: [],
|
||||
createdAt: '2026-07-03T00:00:00.000Z',
|
||||
updatedAt: '2026-07-03T00:01:00.000Z',
|
||||
});
|
||||
await sendPromise;
|
||||
});
|
||||
|
||||
expect(client.sendMessage).not.toHaveBeenCalled();
|
||||
expect(result.current.activeConversationId).toBe('conversation-2');
|
||||
expect(result.current.messages).toEqual([]);
|
||||
expect(result.current.isWaiting).toBe(false);
|
||||
expect(result.current.isAborting).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps a stopped first send silent when conversation creation fails', async () => {
|
||||
const client = createClient();
|
||||
let rejectCreate!: (error: Error) => void;
|
||||
vi.mocked(client.listConversations).mockResolvedValueOnce([]);
|
||||
vi.mocked(client.createConversation).mockImplementationOnce(
|
||||
() =>
|
||||
new Promise<EditorAgentConversationDetail>((_resolve, reject) => {
|
||||
rejectCreate = reject;
|
||||
}),
|
||||
);
|
||||
const { result } = renderHook(() =>
|
||||
useEditorAgentConversation({ projectId: 'project-1', client }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoadingConversations).toBe(false);
|
||||
});
|
||||
|
||||
let sendPromise!: Promise<void>;
|
||||
act(() => {
|
||||
sendPromise = result.current.sendMessage('首次创建失败前停止');
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(client.createConversation).toHaveBeenCalledWith('project-1', {});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.stopCurrentTurn();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
rejectCreate(new Error('创建会话失败'));
|
||||
await sendPromise;
|
||||
});
|
||||
|
||||
expect(client.sendMessage).not.toHaveBeenCalled();
|
||||
expect(result.current.errorMessage).toBeNull();
|
||||
expect(result.current.isWaiting).toBe(false);
|
||||
expect(result.current.isAborting).toBe(false);
|
||||
});
|
||||
|
||||
it('does not send or apply a stale conversation created after switching projects', async () => {
|
||||
const client = createClient();
|
||||
let resolveCreate!: (detail: EditorAgentConversationDetail) => void;
|
||||
|
||||
@@ -10,7 +10,6 @@ import type {
|
||||
EditorAgentMessageResponse,
|
||||
} from '@/packages/shared/src/contracts';
|
||||
|
||||
import { isAbortError } from '../../../services/apiClient.ts';
|
||||
import {
|
||||
cancelEditorAgentToolCall,
|
||||
confirmEditorAgentToolCall,
|
||||
@@ -61,8 +60,6 @@ export type EditorAgentToolCallActionState = {
|
||||
|
||||
type ActiveEditorAgentSend = {
|
||||
requestId: number;
|
||||
conversationId: string;
|
||||
clientMessageId: string;
|
||||
controller: AbortController;
|
||||
};
|
||||
|
||||
@@ -447,7 +444,12 @@ export function useEditorAgentConversation({
|
||||
isWaitingRef.current = true;
|
||||
const requestedProjectId = normalizedProjectId;
|
||||
const requestId = pendingSendRequestIdRef.current + 1;
|
||||
const controller = new AbortController();
|
||||
pendingSendRequestIdRef.current = requestId;
|
||||
activeSendRef.current = {
|
||||
requestId,
|
||||
controller,
|
||||
};
|
||||
setErrorMessage(null);
|
||||
setIsWaiting(true);
|
||||
setPatienceNoticeConversationId(null);
|
||||
@@ -458,12 +460,12 @@ export function useEditorAgentConversation({
|
||||
conversationId = await ensureConversationForSend();
|
||||
if (
|
||||
pendingSendRequestIdRef.current !== requestId ||
|
||||
normalizedProjectIdRef.current !== requestedProjectId
|
||||
normalizedProjectIdRef.current !== requestedProjectId ||
|
||||
controller.signal.aborted
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const clientMessageId = createEditorAgentClientMessageId();
|
||||
const controller = new AbortController();
|
||||
const nextOptimisticMessage = createLocalUserMessage({
|
||||
id: -1,
|
||||
clientMessageId,
|
||||
@@ -471,12 +473,6 @@ export function useEditorAgentConversation({
|
||||
attachments,
|
||||
});
|
||||
optimisticMessage = nextOptimisticMessage;
|
||||
activeSendRef.current = {
|
||||
requestId,
|
||||
conversationId,
|
||||
clientMessageId,
|
||||
controller,
|
||||
};
|
||||
setMessages((currentMessages) => [
|
||||
...currentMessages,
|
||||
nextOptimisticMessage,
|
||||
@@ -516,8 +512,10 @@ export function useEditorAgentConversation({
|
||||
}
|
||||
} catch (error) {
|
||||
const wasStoppedByUser =
|
||||
stoppedSendRequestIdRef.current === requestId && isAbortError(error);
|
||||
stoppedSendRequestIdRef.current === requestId &&
|
||||
controller.signal.aborted;
|
||||
if (wasStoppedByUser) {
|
||||
setErrorMessage(null);
|
||||
if (
|
||||
conversationId &&
|
||||
activeConversationIdRef.current === conversationId
|
||||
|
||||
Reference in New Issue
Block a user