@@ -6,9 +6,16 @@ import pWaitFor from "p-wait-for"
66import * as vscode from "vscode"
77import * as yaml from "yaml"
88
9- import { type Language , type ProviderSettings , type GlobalState , TelemetryEventName } from "@roo-code/types"
9+ import {
10+ type Language ,
11+ type ProviderSettings ,
12+ type GlobalState ,
13+ type ClineMessage ,
14+ TelemetryEventName ,
15+ } from "@roo-code/types"
1016import { CloudService } from "@roo-code/cloud"
1117import { TelemetryService } from "@roo-code/telemetry"
18+ import { type ApiMessage } from "../task-persistence/apiMessages"
1219
1320import { ClineProvider } from "./ClineProvider"
1421import { changeLanguage , t } from "../../i18n"
@@ -58,6 +65,200 @@ export const webviewMessageHandler = async (
5865 const updateGlobalState = async < K extends keyof GlobalState > ( key : K , value : GlobalState [ K ] ) =>
5966 await provider . contextProxy . setValue ( key , value )
6067
68+ /**
69+ * Shared utility to find message indices based on timestamp
70+ */
71+ const findMessageIndices = ( messageTs : number , currentCline : any ) => {
72+ const timeCutoff = messageTs - 1000 // 1 second buffer before the message
73+ const messageIndex = currentCline . clineMessages . findIndex ( ( msg : ClineMessage ) => msg . ts && msg . ts >= timeCutoff )
74+ const apiConversationHistoryIndex = currentCline . apiConversationHistory . findIndex (
75+ ( msg : ApiMessage ) => msg . ts && msg . ts >= timeCutoff ,
76+ )
77+ return { messageIndex, apiConversationHistoryIndex }
78+ }
79+
80+ /**
81+ * Removes just the target message, preserving messages after the next user message
82+ */
83+ const removeMessagesJustThis = async (
84+ currentCline : any ,
85+ messageIndex : number ,
86+ apiConversationHistoryIndex : number ,
87+ ) => {
88+ // Find the next user message first
89+ const nextUserMessage = currentCline . clineMessages
90+ . slice ( messageIndex + 1 )
91+ . find ( ( msg : ClineMessage ) => msg . type === "say" && msg . say === "user_feedback" )
92+
93+ // Handle UI messages
94+ if ( nextUserMessage ) {
95+ // Find absolute index of next user message
96+ const nextUserMessageIndex = currentCline . clineMessages . findIndex (
97+ ( msg : ClineMessage ) => msg === nextUserMessage ,
98+ )
99+
100+ // Keep messages before current message and after next user message
101+ await currentCline . overwriteClineMessages ( [
102+ ...currentCline . clineMessages . slice ( 0 , messageIndex ) ,
103+ ...currentCline . clineMessages . slice ( nextUserMessageIndex ) ,
104+ ] )
105+ } else {
106+ // If no next user message, keep only messages before current message
107+ await currentCline . overwriteClineMessages ( currentCline . clineMessages . slice ( 0 , messageIndex ) )
108+ }
109+
110+ // Handle API messages
111+ if ( apiConversationHistoryIndex !== - 1 ) {
112+ if ( nextUserMessage && nextUserMessage . ts ) {
113+ // Keep messages before current API message and after next user message
114+ await currentCline . overwriteApiConversationHistory ( [
115+ ...currentCline . apiConversationHistory . slice ( 0 , apiConversationHistoryIndex ) ,
116+ ...currentCline . apiConversationHistory . filter (
117+ ( msg : ApiMessage ) => msg . ts && msg . ts >= nextUserMessage . ts ,
118+ ) ,
119+ ] )
120+ } else {
121+ // If no next user message, keep only messages before current API message
122+ await currentCline . overwriteApiConversationHistory (
123+ currentCline . apiConversationHistory . slice ( 0 , apiConversationHistoryIndex ) ,
124+ )
125+ }
126+ }
127+ }
128+
129+ /**
130+ * Removes the target message and all subsequent messages
131+ */
132+ const removeMessagesThisAndSubsequent = async (
133+ currentCline : any ,
134+ messageIndex : number ,
135+ apiConversationHistoryIndex : number ,
136+ ) => {
137+ // Delete this message and all that follow
138+ await currentCline . overwriteClineMessages ( currentCline . clineMessages . slice ( 0 , messageIndex ) )
139+
140+ if ( apiConversationHistoryIndex !== - 1 ) {
141+ await currentCline . overwriteApiConversationHistory (
142+ currentCline . apiConversationHistory . slice ( 0 , apiConversationHistoryIndex ) ,
143+ )
144+ }
145+ }
146+
147+ /**
148+ * Handles message deletion operations with user confirmation
149+ */
150+ const handleDeleteOperation = async ( messageTs : number ) : Promise < void > => {
151+ const options = [
152+ t ( "common:confirmation.delete_just_this_message" ) ,
153+ t ( "common:confirmation.delete_this_and_subsequent" ) ,
154+ ]
155+
156+ const answer = await vscode . window . showInformationMessage (
157+ t ( "common:confirmation.delete_message" ) ,
158+ { modal : true } ,
159+ ...options ,
160+ )
161+
162+ // Only proceed if user selected one of the options and we have a current cline
163+ if ( answer && options . includes ( answer ) && provider . getCurrentCline ( ) ) {
164+ const currentCline = provider . getCurrentCline ( ) !
165+ const { messageIndex, apiConversationHistoryIndex } = findMessageIndices ( messageTs , currentCline )
166+
167+ if ( messageIndex !== - 1 ) {
168+ try {
169+ const { historyItem } = await provider . getTaskWithId ( currentCline . taskId )
170+
171+ // Check which option the user selected
172+ if ( answer === options [ 0 ] ) {
173+ // Delete just this message
174+ await removeMessagesJustThis ( currentCline , messageIndex , apiConversationHistoryIndex )
175+ } else if ( answer === options [ 1 ] ) {
176+ // Delete this message and all subsequent
177+ await removeMessagesThisAndSubsequent ( currentCline , messageIndex , apiConversationHistoryIndex )
178+ }
179+
180+ // Initialize with history item after deletion
181+ await provider . initClineWithHistoryItem ( historyItem )
182+ } catch ( error ) {
183+ console . error ( "Error in delete message:" , error )
184+ vscode . window . showErrorMessage (
185+ `Error deleting message: ${ error instanceof Error ? error . message : String ( error ) } ` ,
186+ )
187+ }
188+ }
189+ }
190+ }
191+
192+ /**
193+ * Handles message editing operations with user confirmation
194+ */
195+ const handleEditOperation = async ( messageTs : number , editedContent : string ) : Promise < void > => {
196+ const options = [
197+ t ( "common:confirmation.edit_this_and_delete_subsequent" ) ,
198+ t ( "common:confirmation.edit_just_this_message" ) ,
199+ ]
200+
201+ const answer = await vscode . window . showInformationMessage (
202+ t ( "common:confirmation.edit_message" ) ,
203+ { modal : true } ,
204+ ...options ,
205+ )
206+
207+ // Only proceed if user selected one of the options and we have a current cline
208+ if ( answer && options . includes ( answer ) && provider . getCurrentCline ( ) ) {
209+ const currentCline = provider . getCurrentCline ( ) !
210+ const { messageIndex, apiConversationHistoryIndex } = findMessageIndices ( messageTs , currentCline )
211+
212+ if ( messageIndex !== - 1 ) {
213+ try {
214+ // Check which option the user selected
215+ if ( answer === options [ 0 ] ) {
216+ // Edit this message and delete subsequent
217+ await removeMessagesThisAndSubsequent ( currentCline , messageIndex , apiConversationHistoryIndex )
218+ } else if ( answer === options [ 1 ] ) {
219+ // Edit just this message
220+ await removeMessagesJustThis ( currentCline , messageIndex , apiConversationHistoryIndex )
221+ }
222+
223+ // Process the edited message as a regular user message
224+ // This will add it to the conversation and trigger an AI response
225+ webviewMessageHandler ( provider , {
226+ type : "askResponse" ,
227+ askResponse : "messageResponse" ,
228+ text : editedContent ,
229+ } )
230+
231+ // Don't initialize with history item for edit operations
232+ // The webviewMessageHandler will handle the conversation state
233+ } catch ( error ) {
234+ console . error ( "Error in edit message:" , error )
235+ vscode . window . showErrorMessage (
236+ `Error editing message: ${ error instanceof Error ? error . message : String ( error ) } ` ,
237+ )
238+ }
239+ }
240+ }
241+ }
242+
243+ /**
244+ * Handles message modification operations (delete or edit) with confirmation dialog
245+ * @param messageTs Timestamp of the message to operate on
246+ * @param operation Type of operation ('delete' or 'edit')
247+ * @param editedContent New content for edit operations
248+ * @returns Promise<void>
249+ */
250+ const handleMessageModificationsOperation = async (
251+ messageTs : number ,
252+ operation : "delete" | "edit" ,
253+ editedContent ?: string ,
254+ ) : Promise < void > => {
255+ if ( operation === "delete" ) {
256+ await handleDeleteOperation ( messageTs )
257+ } else if ( operation === "edit" && editedContent ) {
258+ await handleEditOperation ( messageTs , editedContent )
259+ }
260+ }
261+
61262 switch ( message . type ) {
62263 case "webviewDidLaunch" :
63264 // Load custom modes first
@@ -989,108 +1190,19 @@ export const webviewMessageHandler = async (
9891190 }
9901191 break
9911192 case "deleteMessage" : {
992- const answer = await vscode . window . showInformationMessage (
993- t ( "common:confirmation.delete_message" ) ,
994- { modal : true } ,
995- t ( "common:confirmation.just_this_message" ) ,
996- t ( "common:confirmation.this_and_subsequent" ) ,
997- )
998-
1193+ if ( provider . getCurrentCline ( ) && typeof message . value === "number" && message . value ) {
1194+ await handleMessageModificationsOperation ( message . value , "delete" )
1195+ }
1196+ break
1197+ }
1198+ case "submitEditedMessage" : {
9991199 if (
1000- ( answer === t ( "common:confirmation.just_this_message" ) ||
1001- answer === t ( "common:confirmation.this_and_subsequent" ) ) &&
10021200 provider . getCurrentCline ( ) &&
10031201 typeof message . value === "number" &&
1004- message . value
1202+ message . value &&
1203+ message . editedMessageContent
10051204 ) {
1006- const timeCutoff = message . value - 1000 // 1 second buffer before the message to delete
1007-
1008- const messageIndex = provider
1009- . getCurrentCline ( ) !
1010- . clineMessages . findIndex ( ( msg ) => msg . ts && msg . ts >= timeCutoff )
1011-
1012- const apiConversationHistoryIndex = provider
1013- . getCurrentCline ( )
1014- ?. apiConversationHistory . findIndex ( ( msg ) => msg . ts && msg . ts >= timeCutoff )
1015-
1016- if ( messageIndex !== - 1 ) {
1017- const { historyItem } = await provider . getTaskWithId ( provider . getCurrentCline ( ) ! . taskId )
1018-
1019- if ( answer === t ( "common:confirmation.just_this_message" ) ) {
1020- // Find the next user message first
1021- const nextUserMessage = provider
1022- . getCurrentCline ( ) !
1023- . clineMessages . slice ( messageIndex + 1 )
1024- . find ( ( msg ) => msg . type === "say" && msg . say === "user_feedback" )
1025-
1026- // Handle UI messages
1027- if ( nextUserMessage ) {
1028- // Find absolute index of next user message
1029- const nextUserMessageIndex = provider
1030- . getCurrentCline ( ) !
1031- . clineMessages . findIndex ( ( msg ) => msg === nextUserMessage )
1032-
1033- // Keep messages before current message and after next user message
1034- await provider
1035- . getCurrentCline ( ) !
1036- . overwriteClineMessages ( [
1037- ...provider . getCurrentCline ( ) ! . clineMessages . slice ( 0 , messageIndex ) ,
1038- ...provider . getCurrentCline ( ) ! . clineMessages . slice ( nextUserMessageIndex ) ,
1039- ] )
1040- } else {
1041- // If no next user message, keep only messages before current message
1042- await provider
1043- . getCurrentCline ( ) !
1044- . overwriteClineMessages (
1045- provider . getCurrentCline ( ) ! . clineMessages . slice ( 0 , messageIndex ) ,
1046- )
1047- }
1048-
1049- // Handle API messages
1050- if ( apiConversationHistoryIndex !== - 1 ) {
1051- if ( nextUserMessage && nextUserMessage . ts ) {
1052- // Keep messages before current API message and after next user message
1053- await provider
1054- . getCurrentCline ( ) !
1055- . overwriteApiConversationHistory ( [
1056- ...provider
1057- . getCurrentCline ( ) !
1058- . apiConversationHistory . slice ( 0 , apiConversationHistoryIndex ) ,
1059- ...provider
1060- . getCurrentCline ( ) !
1061- . apiConversationHistory . filter (
1062- ( msg ) => msg . ts && msg . ts >= nextUserMessage . ts ,
1063- ) ,
1064- ] )
1065- } else {
1066- // If no next user message, keep only messages before current API message
1067- await provider
1068- . getCurrentCline ( ) !
1069- . overwriteApiConversationHistory (
1070- provider
1071- . getCurrentCline ( ) !
1072- . apiConversationHistory . slice ( 0 , apiConversationHistoryIndex ) ,
1073- )
1074- }
1075- }
1076- } else if ( answer === t ( "common:confirmation.this_and_subsequent" ) ) {
1077- // Delete this message and all that follow
1078- await provider
1079- . getCurrentCline ( ) !
1080- . overwriteClineMessages ( provider . getCurrentCline ( ) ! . clineMessages . slice ( 0 , messageIndex ) )
1081- if ( apiConversationHistoryIndex !== - 1 ) {
1082- await provider
1083- . getCurrentCline ( ) !
1084- . overwriteApiConversationHistory (
1085- provider
1086- . getCurrentCline ( ) !
1087- . apiConversationHistory . slice ( 0 , apiConversationHistoryIndex ) ,
1088- )
1089- }
1090- }
1091-
1092- await provider . initClineWithHistoryItem ( historyItem )
1093- }
1205+ await handleMessageModificationsOperation ( message . value , "edit" , message . editedMessageContent )
10941206 }
10951207 break
10961208 }
0 commit comments