diff --git a/site/src/pages/AgentsPage/AgentChatPage.tsx b/site/src/pages/AgentsPage/AgentChatPage.tsx index 0f6aaee97b..5226c27577 100644 --- a/site/src/pages/AgentsPage/AgentChatPage.tsx +++ b/site/src/pages/AgentsPage/AgentChatPage.tsx @@ -752,6 +752,7 @@ const AgentChatPage: FC = () => { req: request, }); store.clearStreamState(); + store.setChatStatus("running"); setPendingEditMessageId(null); } catch (error) { setPendingEditMessageId(null); @@ -790,6 +791,15 @@ const AgentChatPage: FC = () => { // WebSocket stream. if (!response.queued) { store.clearStreamState(); + // Optimistically set status to "running" so the + // "Thinking..." indicator appears immediately. + // The server accepted the message (not queued), + // so it will start processing. The WebSocket + // status:running event no-ops via the + // setChatStatus guard. If the server transitions + // to error/pending instead, the WebSocket event + // overrides this optimistic value. + store.setChatStatus("running"); if (response.message) { store.upsertDurableMessage(response.message); } diff --git a/site/src/pages/AgentsPage/components/ChatConversation/chatStore.createStore.test.ts b/site/src/pages/AgentsPage/components/ChatConversation/chatStore.createStore.test.ts index cd80e0c53e..99c009e52a 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/chatStore.createStore.test.ts +++ b/site/src/pages/AgentsPage/components/ChatConversation/chatStore.createStore.test.ts @@ -654,4 +654,25 @@ describe("selectIsAwaitingFirstStreamChunk", () => { // we should not be in a "starting" state. expect(selectIsAwaitingFirstStreamChunk(store.getSnapshot())).toBe(false); }); + + it("returns true after optimistic send: clearStreamState + setChatStatus('running') + upsertDurableMessage", () => { + const store = createChatStore(); + // Simulate a completed previous turn: assistant replied, + // then server transitioned to "pending". + store.upsertDurableMessage(makeMessage(1, "user", "first question")); + store.upsertDurableMessage(makeMessage(2, "assistant", "first answer")); + store.setChatStatus("pending"); + + // Verify baseline: not awaiting during pending. + expect(selectIsAwaitingFirstStreamChunk(store.getSnapshot())).toBe(false); + + // Simulate handleSend after POST returns (non-queued). + // This is the exact sequence from AgentChatPage.tsx. + store.clearStreamState(); + store.setChatStatus("running"); + store.upsertDurableMessage(makeMessage(3, "user", "follow-up")); + + // "Thinking..." should appear immediately. + expect(selectIsAwaitingFirstStreamChunk(store.getSnapshot())).toBe(true); + }); });