From 7838f5fdaf2454a04d60076bebc3bbef482fb9fe Mon Sep 17 00:00:00 2001 From: code-crusher Date: Tue, 23 Dec 2025 19:44:52 +0530 Subject: [PATCH 1/8] fix race condition with the isUserInput flag --- src/package.json | 2 +- .../src/components/chat/ChatTextArea.tsx | 47 ++++++++++++++++--- webview-ui/src/components/chat/ChatView.tsx | 2 +- .../components/kilocode/BottomControls.tsx | 16 +++---- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/package.json b/src/package.json index 20310db709..cc6d6ae047 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "matterai", - "version": "4.204.1", + "version": "4.205.0", "icon": "assets/icons/matterai-ic.png", "galleryBanner": { "color": "#FFFFFF", diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 160641a262..b03c5b7053 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -213,6 +213,9 @@ export const ChatTextArea = forwardRef( const [imageWarning, setImageWarning] = useState(null) // kilocode_change const [materialIconsBaseUri, setMaterialIconsBaseUri] = useState("") + // const [isUserInput, setIsUserInput] = useState(false) + const isUserInputRef = useRef(false) // Use ref to avoid re-renders + // get the icons base uri on mount useEffect(() => { const w = window as any @@ -823,17 +826,26 @@ export const ChatTextArea = forwardRef( if (!parent) return null const siblings = Array.from(parent.childNodes) const index = siblings.indexOf(elNode) - const targetIndex = remaining === 0 ? index : index + 1 - remaining = Math.max(remaining - 1, 0) - return createRangeAt(parent, targetIndex) + + if (remaining === 0) { + return createRangeAt(parent, index) + } else if (remaining === 1) { + return createRangeAt(parent, index + 1) + } else { + remaining -= 1 + return null // Continue to next sibling + } } for (const child of Array.from(elNode.childNodes)) { const childLength = getNodeTextLength(child) if (remaining <= childLength) { - return walk(child) + const result = walk(child) + if (result) return result + // If walk returns null, continue to next child + } else { + remaining -= childLength } - remaining -= childLength } } @@ -853,6 +865,14 @@ export const ChatTextArea = forwardRef( useLayoutEffect(() => { if (!textAreaRef.current) return + + // Only update innerHTML if the change is not from user input + // This prevents destroying the selection when user is typing or pressing Enter + if (isUserInputRef.current) { + isUserInputRef.current = false // Reset flag + return // Skip innerHTML update to preserve selection + } + const html = valueToHtml(inputValue) if (textAreaRef.current.innerHTML !== html) { textAreaRef.current.innerHTML = html @@ -990,6 +1010,15 @@ export const ChatTextArea = forwardRef( resetHistoryNavigation() handleSend() + return + } + + if (handleHistoryNavigation(event, showContextMenu, isComposing)) { + return + } + + if (handleHistoryNavigation(event, showContextMenu, isComposing)) { + return } if (handleHistoryNavigation(event, showContextMenu, isComposing)) { @@ -1065,8 +1094,11 @@ export const ChatTextArea = forwardRef( useLayoutEffect(() => { if (intendedCursorPosition !== null) { - setCaretPosition(intendedCursorPosition) - setIntendedCursorPosition(null) + // Use setTimeout to ensure this runs after the DOM is fully updated + setTimeout(() => { + setCaretPosition(intendedCursorPosition) + setIntendedCursorPosition(null) + }, 0) } }, [inputValue, intendedCursorPosition, setCaretPosition]) @@ -1075,6 +1107,7 @@ export const ChatTextArea = forwardRef( const handleInputChange = useCallback(() => { const newValue = getPlainTextFromInput() setInputValue(newValue) + isUserInputRef.current = true // Mark this as user input using ref resetOnInputChange() const newCursorPosition = getCaretPosition() diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index b01cea95b6..af0f2a410e 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -2062,7 +2062,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction +
{/* Top section: Title/Subtitle left, Icons right */}
diff --git a/webview-ui/src/components/kilocode/BottomControls.tsx b/webview-ui/src/components/kilocode/BottomControls.tsx index a34d36eddc..f0fd5fee4a 100644 --- a/webview-ui/src/components/kilocode/BottomControls.tsx +++ b/webview-ui/src/components/kilocode/BottomControls.tsx @@ -1,8 +1,4 @@ import React from "react" -import { vscode } from "../../utils/vscode" -import { useAppTranslation } from "@/i18n/TranslationContext" -import KiloRulesToggleModal from "./rules/KiloRulesToggleModal" -import BottomButton from "./BottomButton" import { BottomApiConfig } from "./BottomApiConfig" // kilocode_change interface BottomControlsProps { @@ -10,18 +6,18 @@ interface BottomControlsProps { } const BottomControls: React.FC = ({ showApiConfig = false }) => { - const { t } = useAppTranslation() + // const { t } = useAppTranslation() - const showFeedbackOptions = () => { - vscode.postMessage({ type: "showFeedbackOptions" }) - } + // const showFeedbackOptions = () => { + // vscode.postMessage({ type: "showFeedbackOptions" }) + // } return (
{showApiConfig && }
-
+ {/*
= ({ showApiConfig = false } onClick={showFeedbackOptions} />
-
+
*/}
) } From fc67bef677754e758b208439fbb03333ea62ae71 Mon Sep 17 00:00:00 2001 From: code-crusher Date: Tue, 23 Dec 2025 20:23:29 +0530 Subject: [PATCH 2/8] API Streaming Failed to have retry button --- src/core/task/Task.ts | 83 +++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 08dd14134e..cbcd5fe9f7 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -3198,7 +3198,10 @@ export class Task extends EventEmitter implements TaskLike { errorMsg = "Unknown error" } - const baseDelay = requestDelaySeconds || 5 + await this.ask("api_req_failed", errorMsg) + + // Wait for the delay before retrying + const baseDelay = requestDelaySeconds || 0 let exponentialDelay = Math.min( Math.ceil(baseDelay * Math.pow(2, retryAttempt)), MAX_EXPONENTIAL_BACKOFF_SECONDS, @@ -3212,21 +3215,16 @@ export class Task extends EventEmitter implements TaskLike { if (geminiRetryDetails) { const match = geminiRetryDetails?.retryDelay?.match(/^(\d+)s$/) if (match) { - exponentialDelay = Number(match[1]) + 1 + exponentialDelay = parseInt(match[1], 10) } } } - // Wait for the greater of the exponential delay or the rate limit delay - const finalDelay = Math.max(exponentialDelay, rateLimitDelay) - - // Show countdown timer with exponential backoff - for (let i = finalDelay; i > 0; i--) { + for (let i = exponentialDelay; i > 0; i--) { await this.say( "api_req_retry_delayed", `${errorMsg}\n\nRetry attempt ${retryAttempt + 1}\nRetrying in ${i} seconds...`, undefined, - true, ) await delay(1000) } @@ -3235,7 +3233,6 @@ export class Task extends EventEmitter implements TaskLike { "api_req_retry_delayed", `${errorMsg}\n\nRetry attempt ${retryAttempt + 1}\nRetrying now...`, undefined, - false, ) // Delegate generator output from the recursive call with @@ -3263,15 +3260,65 @@ export class Task extends EventEmitter implements TaskLike { } } - // No error, so we can continue to yield all remaining chunks. - // (Needs to be placed outside of try/catch since it we want caller to - // handle errors not with api_req_failed as that is reserved for first - // chunk failures only.) - // This delegates to another generator or iterable object. In this case, - // it's saying "yield all remaining values from this iterator". This - // effectively passes along all subsequent chunks from the original - // stream. - yield* iterator + // No error on first chunk, so we can continue to yield all remaining chunks. + // Wrap in try/catch to handle mid-stream errors and allow retry. + try { + yield* iterator + } catch (error) { + // Reset streaming state since we encountered an error + this.isStreaming = false + + // kilocode_change start + if (apiConfiguration?.apiProvider === "kilocode" && isAnyRecognizedKiloCodeError(error)) { + const { response } = await (isPaymentRequiredError(error) + ? this.ask( + "payment_required_prompt", + JSON.stringify({ + title: error.error?.title ?? t("kilocode:lowCreditWarning.title"), + message: error.error?.message ?? t("kilocode:lowCreditWarning.message"), + balance: error.error?.balance ?? "0.00", + buyCreditsUrl: error.error?.buyCreditsUrl ?? getAppUrl("/profile"), + }), + ) + : this.ask( + "invalid_model", + JSON.stringify({ + modelId: apiConfiguration.kilocodeModel, + error: { + status: error.status, + message: error.message, + }, + }), + )) + + if (response === "retry_clicked") { + yield* this.attemptApiRequest(retryAttempt + 1) + } else { + // Handle other responses or cancellations if necessary + throw error // Rethrow to signal failure upwards + } + return + } + // kilocode_change end + + // For mid-stream failures, show the retry dialog to allow user to retry + const { response } = await this.ask( + "api_req_failed", + error.message ?? JSON.stringify(serializeError(error), null, 2), + ) + + if (response !== "yesButtonClicked") { + // This will never happen since if noButtonClicked, we will + // clear current task, aborting this instance. + throw new Error("API request failed") + } + + await this.say("api_req_retried") + + // Delegate generator output from the recursive call. + yield* this.attemptApiRequest() + return + } } // Checkpoints From 1793c640c6057794b05ed4fda40ab4af01a58363 Mon Sep 17 00:00:00 2001 From: code-crusher Date: Tue, 23 Dec 2025 20:44:29 +0530 Subject: [PATCH 3/8] fix previous commands still show run/reject buttons --- webview-ui/src/components/chat/ChatView.tsx | 2 +- webview-ui/src/components/chat/CommandExecution.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index af0f2a410e..d9f6e51dbd 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1686,7 +1686,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction diff --git a/webview-ui/src/components/chat/CommandExecution.tsx b/webview-ui/src/components/chat/CommandExecution.tsx index 7d0d0bdfec..bb1119403a 100644 --- a/webview-ui/src/components/chat/CommandExecution.tsx +++ b/webview-ui/src/components/chat/CommandExecution.tsx @@ -194,7 +194,7 @@ export const CommandExecution = ({
)} - {onPrimaryButtonClick && onSecondaryButtonClick && ( + {onPrimaryButtonClick && onSecondaryButtonClick && enableButtons && (