From db13b9725e3b4f6a0b392bc807a3a774e9382591 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Thu, 10 Apr 2025 17:16:30 +0200 Subject: [PATCH 1/7] add forceReload event on any tool result. --- refact-agent/gui/src/app/middleware.ts | 20 ++++++++++++++++++- .../gui/src/hooks/useDiffFileReload.ts | 1 + .../gui/src/hooks/useEventBusForIDE.ts | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/refact-agent/gui/src/app/middleware.ts b/refact-agent/gui/src/app/middleware.ts index a52b67ef8..b6704be68 100644 --- a/refact-agent/gui/src/app/middleware.ts +++ b/refact-agent/gui/src/app/middleware.ts @@ -13,6 +13,7 @@ import { setIsWaitingForResponse, upsertToolCall, sendCurrentChatToLspAfterToolCallUpdate, + chatResponse, } from "../features/Chat/Thread"; import { statisticsApi } from "../services/refact/statistics"; import { integrationsApi } from "../services/refact/integrations"; @@ -38,8 +39,12 @@ import { updateConfirmationAfterIdeToolUse, } from "../features/ToolConfirmation/confirmationSlice"; import { setInitialAgentUsage } from "../features/AgentUsage/agentUsageSlice"; -import { ideToolCallResponse } from "../hooks/useEventBusForIDE"; +import { + ideToolCallResponse, + ideForceReload, +} from "../hooks/useEventBusForIDE"; import { upsertToolCallIntoHistory } from "../features/History/historySlice"; +import { isToolResponse } from "../events"; const AUTH_ERROR_MESSAGE = "There is an issue with your API key. Check out your API Key or re-login"; @@ -536,3 +541,16 @@ startListening({ } }, }); + +// JB file refresh +// TBD: this could include diff messages to +startListening({ + actionCreator: chatResponse, + effect: (action, listenerApi) => { + const state = listenerApi.getState(); + if (state.config.host !== "jetbrains") return; + if (!isToolResponse(action.payload)) return; + if (!window.postIntellijMessage) return; + window.postIntellijMessage(ideForceReload()); + }, +}); diff --git a/refact-agent/gui/src/hooks/useDiffFileReload.ts b/refact-agent/gui/src/hooks/useDiffFileReload.ts index e9cf6b0b7..b59f1c0db 100644 --- a/refact-agent/gui/src/hooks/useDiffFileReload.ts +++ b/refact-agent/gui/src/hooks/useDiffFileReload.ts @@ -9,6 +9,7 @@ import { isDiffMessage } from "../services/refact"; * Hook to handle file reloading for diff messages in JetBrains IDE * Ensures each file is only reloaded once per message */ +// Note this won't work if the chat is in the cache. export function useDiffFileReload() { const messages = useAppSelector(selectMessages); const configIdeHost = useAppSelector(selectConfig).host; diff --git a/refact-agent/gui/src/hooks/useEventBusForIDE.ts b/refact-agent/gui/src/hooks/useEventBusForIDE.ts index 67cadb222..46965d208 100644 --- a/refact-agent/gui/src/hooks/useEventBusForIDE.ts +++ b/refact-agent/gui/src/hooks/useEventBusForIDE.ts @@ -63,6 +63,8 @@ export const ideToolCallResponse = createAction<{ accepted: boolean | "indeterminate"; }>("ide/toolEditResponse"); +export const ideForceReload = createAction("ide/forceReload"); + export const useEventsBusForIDE = () => { const [sendTelemetryEvent] = telemetryApi.useLazySendTelemetryChatEventQuery(); From fd7b5a2d79a3ee7aea7cd805049b3cfa29f62c97 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Thu, 10 Apr 2025 17:53:54 +0200 Subject: [PATCH 2/7] rename forceReload event --- refact-agent/gui/src/hooks/useEventBusForIDE.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refact-agent/gui/src/hooks/useEventBusForIDE.ts b/refact-agent/gui/src/hooks/useEventBusForIDE.ts index 46965d208..ec601fd7e 100644 --- a/refact-agent/gui/src/hooks/useEventBusForIDE.ts +++ b/refact-agent/gui/src/hooks/useEventBusForIDE.ts @@ -63,7 +63,7 @@ export const ideToolCallResponse = createAction<{ accepted: boolean | "indeterminate"; }>("ide/toolEditResponse"); -export const ideForceReload = createAction("ide/forceReload"); +export const ideForceReload = createAction("ide/forceReloadProjectTreeFiles"); export const useEventsBusForIDE = () => { const [sendTelemetryEvent] = From 082320990ce74926bf1d72f9d5d70fbf4037175f Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Thu, 10 Apr 2025 17:54:34 +0200 Subject: [PATCH 3/7] chore: rename forceReload --- refact-agent/gui/src/app/middleware.ts | 4 ++-- refact-agent/gui/src/hooks/useEventBusForIDE.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/refact-agent/gui/src/app/middleware.ts b/refact-agent/gui/src/app/middleware.ts index b6704be68..f00f7c3ef 100644 --- a/refact-agent/gui/src/app/middleware.ts +++ b/refact-agent/gui/src/app/middleware.ts @@ -41,7 +41,7 @@ import { import { setInitialAgentUsage } from "../features/AgentUsage/agentUsageSlice"; import { ideToolCallResponse, - ideForceReload, + ideForceReloadProjectTreeFiles, } from "../hooks/useEventBusForIDE"; import { upsertToolCallIntoHistory } from "../features/History/historySlice"; import { isToolResponse } from "../events"; @@ -551,6 +551,6 @@ startListening({ if (state.config.host !== "jetbrains") return; if (!isToolResponse(action.payload)) return; if (!window.postIntellijMessage) return; - window.postIntellijMessage(ideForceReload()); + window.postIntellijMessage(ideForceReloadProjectTreeFiles()); }, }); diff --git a/refact-agent/gui/src/hooks/useEventBusForIDE.ts b/refact-agent/gui/src/hooks/useEventBusForIDE.ts index ec601fd7e..a45290a9d 100644 --- a/refact-agent/gui/src/hooks/useEventBusForIDE.ts +++ b/refact-agent/gui/src/hooks/useEventBusForIDE.ts @@ -63,7 +63,9 @@ export const ideToolCallResponse = createAction<{ accepted: boolean | "indeterminate"; }>("ide/toolEditResponse"); -export const ideForceReload = createAction("ide/forceReloadProjectTreeFiles"); +export const ideForceReloadProjectTreeFiles = createAction( + "ide/forceReloadProjectTreeFiles", +); export const useEventsBusForIDE = () => { const [sendTelemetryEvent] = From 02b628814bc0e3257c4def80a5de0ec3f0de0ce8 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Fri, 11 Apr 2025 12:43:37 +0200 Subject: [PATCH 4/7] fix: compression stop, only stop the chat when it's compressed and more than 40 messages from the last user message. --- refact-agent/gui/src/hooks/useCompressionStop.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/refact-agent/gui/src/hooks/useCompressionStop.ts b/refact-agent/gui/src/hooks/useCompressionStop.ts index aad6687be..de1a9b058 100644 --- a/refact-agent/gui/src/hooks/useCompressionStop.ts +++ b/refact-agent/gui/src/hooks/useCompressionStop.ts @@ -15,11 +15,6 @@ export function useLastSentCompressionStop() { const lastSentCompression = useAppSelector(selectLastSentCompression); const messages = useAppSelector(selectMessages); const stopped = useAppSelector(selectThreadPaused); - useEffect(() => { - if (lastSentCompression && lastSentCompression !== "absent" && !stopped) { - dispatch(setThreadPaused(true)); - } - }, [dispatch, lastSentCompression, stopped]); const messagesFromLastUserMessage = useMemo(() => { return takeFromEndWhile(messages, (message) => !isUserMessage(message)) From 0ef73635d1ec14ed237c1c211f266784eb43ea1e Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Fri, 11 Apr 2025 15:27:42 +0200 Subject: [PATCH 5/7] fix: combine comppression and chat suggestion use similar logic. --- refact-agent/gui/src/components/Chat/Chat.tsx | 7 ++---- .../UsageCounter/useUsageCounter.ts | 7 ++---- .../gui/src/features/Chat/Thread/reducer.ts | 12 ++-------- .../gui/src/features/Chat/Thread/selectors.ts | 5 ----- .../gui/src/features/Chat/Thread/types.ts | 1 - .../gui/src/hooks/useCompressionStop.ts | 22 ++++++++++--------- .../gui/src/hooks/useSendChatRequest.ts | 4 ---- 7 files changed, 18 insertions(+), 40 deletions(-) diff --git a/refact-agent/gui/src/components/Chat/Chat.tsx b/refact-agent/gui/src/components/Chat/Chat.tsx index 5b181e2bf..a28b815d7 100644 --- a/refact-agent/gui/src/components/Chat/Chat.tsx +++ b/refact-agent/gui/src/components/Chat/Chat.tsx @@ -10,7 +10,6 @@ import { useGetCapsQuery, useCapsForToolUse, useAgentUsage, - useLastSentCompressionStop, } from "../../hooks"; import { type Config } from "../../features/Config/configSlice"; import { @@ -54,7 +53,6 @@ export const Chat: React.FC = ({ const { submit, abort, retryFromIndex } = useSendChatRequest(); const chatToolUse = useAppSelector(getSelectedToolUse); - const compressionStop = useLastSentCompressionStop(); const threadNewChatSuggested = useAppSelector(selectThreadNewChatSuggested); const messages = useAppSelector(selectMessages); const capsForToolUse = useCapsForToolUse(); @@ -106,9 +104,8 @@ export const Chat: React.FC = ({ {!isStreaming && preventSend && unCalledTools && ( diff --git a/refact-agent/gui/src/components/UsageCounter/useUsageCounter.ts b/refact-agent/gui/src/components/UsageCounter/useUsageCounter.ts index 9d2c4ec6a..de14d92f5 100644 --- a/refact-agent/gui/src/components/UsageCounter/useUsageCounter.ts +++ b/refact-agent/gui/src/components/UsageCounter/useUsageCounter.ts @@ -3,7 +3,6 @@ import { selectIsStreaming, selectIsWaiting, selectMessages, - // selectLastSentCompression, } from "../../features/Chat"; import { useAppSelector, useLastSentCompressionStop } from "../../hooks"; import { @@ -33,19 +32,17 @@ export function useUsageCounter() { }, [currentThreadUsage]); const isOverflown = useMemo(() => { - if (!compressionStop.stopped) return false; if (compressionStop.strength === "low") return true; if (compressionStop.strength === "medium") return true; if (compressionStop.strength === "high") return true; return false; - }, [compressionStop.stopped, compressionStop.strength]); + }, [compressionStop.strength]); const isWarning = useMemo(() => { - if (!compressionStop.stopped) return false; if (compressionStop.strength === "medium") return true; if (compressionStop.strength === "high") return true; return false; - }, [compressionStop.stopped, compressionStop.strength]); + }, [compressionStop.strength]); const shouldShow = useMemo(() => { return messages.length > 0 && !isStreaming && !isWaiting; diff --git a/refact-agent/gui/src/features/Chat/Thread/reducer.ts b/refact-agent/gui/src/features/Chat/Thread/reducer.ts index d0692fd28..e6185b530 100644 --- a/refact-agent/gui/src/features/Chat/Thread/reducer.ts +++ b/refact-agent/gui/src/features/Chat/Thread/reducer.ts @@ -39,7 +39,6 @@ import { setIsNewChatSuggestionRejected, upsertToolCall, setIncreaseMaxTokens, - setThreadPaused, } from "./actions"; import { formatChatResponse } from "./utils"; import { @@ -79,7 +78,6 @@ const createChatThread = ( boost_reasoning: false, automatic_patch: false, increase_max_tokens: false, - paused: false, }; return chat; }; @@ -209,7 +207,7 @@ export const chatReducer = createReducer(initialState, (builder) => { action.payload.compression_strength !== "absent" ) { state.thread.new_chat_suggested = { - ...state.thread.new_chat_suggested, + wasRejectedByUser: false, wasSuggested: true, }; } @@ -243,7 +241,6 @@ export const chatReducer = createReducer(initialState, (builder) => { builder.addCase(setIsNewChatSuggested, (state, action) => { if (state.thread.id !== action.payload.chatId) return state; - state.thread.paused = true; state.thread.new_chat_suggested = { wasSuggested: action.payload.value, }; @@ -251,7 +248,7 @@ export const chatReducer = createReducer(initialState, (builder) => { builder.addCase(setIsNewChatSuggestionRejected, (state, action) => { if (state.thread.id !== action.payload.chatId) return state; - state.thread.paused = false; + state.prevent_send = false; state.thread.new_chat_suggested = { ...state.thread.new_chat_suggested, wasRejectedByUser: action.payload.value, @@ -279,7 +276,6 @@ export const chatReducer = createReducer(initialState, (builder) => { state.streaming = true; state.thread.read = false; state.prevent_send = false; - state.thread.paused = false; }); builder.addCase(removeChatFromCache, (state, action) => { @@ -427,10 +423,6 @@ export const chatReducer = createReducer(initialState, (builder) => { state.thread.increase_max_tokens = action.payload; }); - builder.addCase(setThreadPaused, (state, action) => { - state.thread.paused = action.payload; - }); - builder.addMatcher( capsApi.endpoints.getCaps.matchFulfilled, (state, action) => { diff --git a/refact-agent/gui/src/features/Chat/Thread/selectors.ts b/refact-agent/gui/src/features/Chat/Thread/selectors.ts index 563ba6102..7a0e3e0e8 100644 --- a/refact-agent/gui/src/features/Chat/Thread/selectors.ts +++ b/refact-agent/gui/src/features/Chat/Thread/selectors.ts @@ -94,8 +94,3 @@ export const selectLastSentCompression = createSelector( return lastCompression; }, ); - -export const selectThreadPaused = createSelector( - selectThread, - (thread) => thread.paused ?? false, -); diff --git a/refact-agent/gui/src/features/Chat/Thread/types.ts b/refact-agent/gui/src/features/Chat/Thread/types.ts index 7c907148b..6a038aacf 100644 --- a/refact-agent/gui/src/features/Chat/Thread/types.ts +++ b/refact-agent/gui/src/features/Chat/Thread/types.ts @@ -29,7 +29,6 @@ export type ChatThread = { currentMaximumContextTokens?: number; currentMessageContextTokens?: number; increase_max_tokens?: boolean; - paused?: boolean; }; export type SuggestedChat = { diff --git a/refact-agent/gui/src/hooks/useCompressionStop.ts b/refact-agent/gui/src/hooks/useCompressionStop.ts index de1a9b058..6bcbb4c85 100644 --- a/refact-agent/gui/src/hooks/useCompressionStop.ts +++ b/refact-agent/gui/src/hooks/useCompressionStop.ts @@ -2,10 +2,12 @@ import { useEffect, useCallback, useMemo } from "react"; import { useAppSelector } from "./useAppSelector"; import { useAppDispatch } from "./useAppDispatch"; import { + selectChatId, selectLastSentCompression, selectMessages, - selectThreadPaused, - setThreadPaused, + setIsNewChatSuggested, + setIsNewChatSuggestionRejected, + setPreventSend, } from "../features/Chat"; import { takeFromEndWhile } from "../utils"; import { isUserMessage } from "../events"; @@ -14,7 +16,7 @@ export function useLastSentCompressionStop() { const dispatch = useAppDispatch(); const lastSentCompression = useAppSelector(selectLastSentCompression); const messages = useAppSelector(selectMessages); - const stopped = useAppSelector(selectThreadPaused); + const chatId = useAppSelector(selectChatId); const messagesFromLastUserMessage = useMemo(() => { return takeFromEndWhile(messages, (message) => !isUserMessage(message)) @@ -25,16 +27,16 @@ export function useLastSentCompressionStop() { if ( lastSentCompression && lastSentCompression !== "absent" && - messagesFromLastUserMessage >= 40 && - !stopped + messagesFromLastUserMessage >= 40 ) { - dispatch(setThreadPaused(true)); + dispatch(setPreventSend({ id: chatId })); + dispatch(setIsNewChatSuggested({ chatId, value: true })); } - }, [dispatch, lastSentCompression, messagesFromLastUserMessage, stopped]); + }, [chatId, dispatch, lastSentCompression, messagesFromLastUserMessage]); const resume = useCallback(() => { - dispatch(setThreadPaused(false)); - }, [dispatch]); + dispatch(setIsNewChatSuggestionRejected({ chatId, value: true })); + }, [chatId, dispatch]); - return { stopped, resume, strength: lastSentCompression }; + return { resume, strength: lastSentCompression }; } diff --git a/refact-agent/gui/src/hooks/useSendChatRequest.ts b/refact-agent/gui/src/hooks/useSendChatRequest.ts index 08610c8fc..abe194453 100644 --- a/refact-agent/gui/src/hooks/useSendChatRequest.ts +++ b/refact-agent/gui/src/hooks/useSendChatRequest.ts @@ -56,7 +56,6 @@ import { import { v4 as uuidv4 } from "uuid"; import { upsertToolCallIntoHistory } from "../features/History/historySlice"; -import { useLastSentCompressionStop } from "./useCompressionStop"; type SubmitHandlerParams = | { @@ -356,7 +355,6 @@ export function useAutoSend() { const sendImmediately = useAppSelector(selectSendImmediately); const wasInteracted = useAppSelector(getToolsInteractionStatus); // shows if tool confirmation popup was interacted by user const areToolsConfirmed = useAppSelector(getToolsConfirmationStatus); - const compressionStop = useLastSentCompressionStop(); const { sendMessages, abort, messagesWithSystemPrompt } = useSendChatRequest(); // TODO: make a selector for this, or show tool formation @@ -381,7 +379,6 @@ export function useAutoSend() { const lastMessage = currentMessages.slice(-1)[0]; if ( - !compressionStop.stopped && isAssistantMessage(lastMessage) && lastMessage.tool_calls && lastMessage.tool_calls.length > 0 @@ -418,6 +415,5 @@ export function useAutoSend() { isIntegration, thread.mode, thread, - compressionStop.stopped, ]); } From 75341d5a8a127d512c36a9874a555b1dc0e113ad Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Fri, 11 Apr 2025 15:36:42 +0200 Subject: [PATCH 6/7] enable send if the user dimisses the request to start a new chat --- .../src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx b/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx index 8ae74ecc1..cd19d5f00 100644 --- a/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx +++ b/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx @@ -13,9 +13,11 @@ import { import { popBackTo, push } from "../../../features/Pages/pagesSlice"; import { telemetryApi } from "../../../services/refact"; import { + enableSend, newChatAction, selectChatId, setIsNewChatSuggestionRejected, + setPreventSend, } from "../../../features/Chat"; import { Link } from "../../Link"; @@ -64,6 +66,7 @@ export const SuggestNewChat = ({ const handleClose = () => { dispatch(setIsNewChatSuggestionRejected({ chatId, value: true })); + dispatch(enableSend({ id: chatId })); void sendTelemetryEvent({ scope: `dismissedNewChatSuggestionWarning`, From b539196232070918f6854ca5a107fbad0053fe5d Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Fri, 11 Apr 2025 15:55:01 +0200 Subject: [PATCH 7/7] fix: open chat suggestion box when restoring a large chat. --- .../src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx | 1 - refact-agent/gui/src/features/Chat/Thread/reducer.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx b/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx index cd19d5f00..121f65511 100644 --- a/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx +++ b/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx @@ -17,7 +17,6 @@ import { newChatAction, selectChatId, setIsNewChatSuggestionRejected, - setPreventSend, } from "../../../features/Chat"; import { Link } from "../../Link"; diff --git a/refact-agent/gui/src/features/Chat/Thread/reducer.ts b/refact-agent/gui/src/features/Chat/Thread/reducer.ts index e6185b530..54059a303 100644 --- a/refact-agent/gui/src/features/Chat/Thread/reducer.ts +++ b/refact-agent/gui/src/features/Chat/Thread/reducer.ts @@ -333,7 +333,7 @@ export const chatReducer = createReducer(initialState, (builder) => { lastUserMessage.compression_strength !== "absent" ) { state.thread.new_chat_suggested = { - ...state.thread.new_chat_suggested, + wasRejectedByUser: false, wasSuggested: true, }; }