fix(ask-ai): resolve stream subscription race condition on new thread… (#21916)

## Description
Resolves a race condition in the Ask AI feature where the first
assistant reply in a newly created thread does not stream into the UI
and only appears after sending a second message.

### What's Changed
- **Immediate Thread Subscription:** Updated `useAgentChat.ts` to
immediately set `currentAiChatThread` to the newly generated `threadId`
instead of deferring it until after the `SEND_CHAT_MESSAGE` mutation
finishes.
- **The Bug:** Previously, the backend worker processed the AI chat job
so quickly that the stream completed and fired the `message-persisted`
event *before* the frontend established the SSE subscription.
- **The Fix:** By setting the thread ID immediately, the
`useAgentChatSubscription` hook now properly connects and listens to the
SSE stream before the backend begins emitting chunks, guaranteeing the
first message streams seamlessly.

### How to Test
1. Open the Ask AI panel and start a completely new thread.
2. Send an initial message (e.g., "Hello!").
3. Observe that the AI's response successfully streams into the chat
without needing a workaround or page refresh.

Closes #21694

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
This commit is contained in:
Akash!
2026-06-25 19:11:56 +05:30
committed by GitHub
parent c3183f7828
commit 87329c8810
@@ -1,7 +1,7 @@
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { useApolloClient } from '@apollo/client/react';
import { useStore } from 'jotai';
import { useCallback, useState } from 'react';
import { useCallback } from 'react';
import { type ExtendedUIMessage } from 'twenty-shared/ai';
import { isDefined, isValidUuid } from 'twenty-shared/utils';
import { v4 } from 'uuid';
@@ -44,8 +44,6 @@ export const useAgentChat = (
const setCurrentAiChatThread = useSetAtomState(currentAiChatThreadState);
const store = useStore();
const [, setPendingThreadIdAfterFirstSend] = useState<string | null>(null);
const setAgentChatUploadedFiles = useSetAtomState(
agentChatUploadedFilesState,
);
@@ -87,7 +85,7 @@ export const useAgentChat = (
}
if (draftKey === AGENT_CHAT_NEW_THREAD_DRAFT_KEY) {
setPendingThreadIdAfterFirstSend(threadId);
setCurrentAiChatThread(threadId);
}
setAgentChatInput('');
@@ -184,14 +182,6 @@ export const useAgentChat = (
}
dispatchBrowserEvent(AGENT_CHAT_REFETCH_MESSAGES_EVENT_NAME);
setPendingThreadIdAfterFirstSend((pendingId) => {
if (isDefined(pendingId)) {
setCurrentAiChatThread(pendingId);
}
return null;
});
} catch (error) {
const restoredDraftKey =
draftKey === AGENT_CHAT_NEW_THREAD_DRAFT_KEY ? threadId : draftKey;
@@ -225,12 +215,6 @@ export const useAgentChat = (
dispatchBrowserEvent(AGENT_CHAT_RESTORE_EDITOR_CONTENT_EVENT_NAME, {
content: contentToSend,
});
if (draftKey === AGENT_CHAT_NEW_THREAD_DRAFT_KEY) {
setCurrentAiChatThread(threadId);
}
setPendingThreadIdAfterFirstSend(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [