From f075b91ed59f7d7c8fb809f06122362368a72ada Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Wed, 11 Feb 2026 09:00:48 +0100 Subject: [PATCH] Fix thread status when user input is requested --- .../threads/hooks/useThreadEventHandlers.ts | 7 +- .../hooks/useThreadUserInputEvents.test.tsx | 77 +++++++++++++++++++ .../threads/hooks/useThreadUserInputEvents.ts | 19 ++++- 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 src/features/threads/hooks/useThreadUserInputEvents.test.tsx diff --git a/src/features/threads/hooks/useThreadEventHandlers.ts b/src/features/threads/hooks/useThreadEventHandlers.ts index 714fa4ef0..78b47dce7 100644 --- a/src/features/threads/hooks/useThreadEventHandlers.ts +++ b/src/features/threads/hooks/useThreadEventHandlers.ts @@ -64,7 +64,12 @@ export function useThreadEventHandlers({ dispatch, approvalAllowlistRef, }); - const onRequestUserInput = useThreadUserInputEvents({ dispatch }); + const onRequestUserInput = useThreadUserInputEvents({ + dispatch, + markProcessing, + markReviewing, + setActiveTurnId, + }); const { onAgentMessageDelta, diff --git a/src/features/threads/hooks/useThreadUserInputEvents.test.tsx b/src/features/threads/hooks/useThreadUserInputEvents.test.tsx new file mode 100644 index 000000000..6a148a342 --- /dev/null +++ b/src/features/threads/hooks/useThreadUserInputEvents.test.tsx @@ -0,0 +1,77 @@ +// @vitest-environment jsdom +import { act, renderHook } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import type { RequestUserInputRequest } from "../../../types"; +import { useThreadUserInputEvents } from "./useThreadUserInputEvents"; + +describe("useThreadUserInputEvents", () => { + it("queues request and clears processing/reviewing state for the thread", () => { + const dispatch = vi.fn(); + const markProcessing = vi.fn(); + const markReviewing = vi.fn(); + const setActiveTurnId = vi.fn(); + const request: RequestUserInputRequest = { + workspace_id: "workspace-1", + request_id: "req-1", + params: { + thread_id: "thread-1", + turn_id: "turn-1", + item_id: "item-1", + questions: [], + }, + }; + + const { result } = renderHook(() => + useThreadUserInputEvents({ + dispatch, + markProcessing, + markReviewing, + setActiveTurnId, + }), + ); + + act(() => { + result.current(request); + }); + + expect(dispatch).toHaveBeenCalledWith({ type: "addUserInputRequest", request }); + expect(markProcessing).toHaveBeenCalledWith("thread-1", false); + expect(markReviewing).toHaveBeenCalledWith("thread-1", false); + expect(setActiveTurnId).toHaveBeenCalledWith("thread-1", null); + }); + + it("only queues request when thread id is missing", () => { + const dispatch = vi.fn(); + const markProcessing = vi.fn(); + const markReviewing = vi.fn(); + const setActiveTurnId = vi.fn(); + const request: RequestUserInputRequest = { + workspace_id: "workspace-1", + request_id: "req-1", + params: { + thread_id: " ", + turn_id: "turn-1", + item_id: "item-1", + questions: [], + }, + }; + + const { result } = renderHook(() => + useThreadUserInputEvents({ + dispatch, + markProcessing, + markReviewing, + setActiveTurnId, + }), + ); + + act(() => { + result.current(request); + }); + + expect(dispatch).toHaveBeenCalledWith({ type: "addUserInputRequest", request }); + expect(markProcessing).not.toHaveBeenCalled(); + expect(markReviewing).not.toHaveBeenCalled(); + expect(setActiveTurnId).not.toHaveBeenCalled(); + }); +}); diff --git a/src/features/threads/hooks/useThreadUserInputEvents.ts b/src/features/threads/hooks/useThreadUserInputEvents.ts index c2a164af3..d4bce094e 100644 --- a/src/features/threads/hooks/useThreadUserInputEvents.ts +++ b/src/features/threads/hooks/useThreadUserInputEvents.ts @@ -5,13 +5,28 @@ import type { ThreadAction } from "./useThreadsReducer"; type UseThreadUserInputEventsOptions = { dispatch: Dispatch; + markProcessing: (threadId: string, isProcessing: boolean) => void; + markReviewing: (threadId: string, isReviewing: boolean) => void; + setActiveTurnId: (threadId: string, turnId: string | null) => void; }; -export function useThreadUserInputEvents({ dispatch }: UseThreadUserInputEventsOptions) { +export function useThreadUserInputEvents({ + dispatch, + markProcessing, + markReviewing, + setActiveTurnId, +}: UseThreadUserInputEventsOptions) { return useCallback( (request: RequestUserInputRequest) => { dispatch({ type: "addUserInputRequest", request }); + const threadId = request.params.thread_id.trim(); + if (!threadId) { + return; + } + markProcessing(threadId, false); + markReviewing(threadId, false); + setActiveTurnId(threadId, null); }, - [dispatch], + [dispatch, markProcessing, markReviewing, setActiveTurnId], ); }