@@ -26,14 +26,95 @@ export async function writeToFileTool(
2626 let newContent : string | undefined = block . params . content
2727 let predictedLineCount : number | undefined = parseInt ( block . params . line_count ?? "0" )
2828
29- if ( ! relPath || ! newContent ) {
30- // checking for newContent ensure relPath is complete
31- // wait so we can determine if it's a new file or editing an existing file
29+ // Handle partial blocks first - minimal validation, just streaming
30+ if ( block . partial ) {
31+ if ( ! relPath || newContent === undefined ) {
32+ // checking for newContent ensure relPath is complete
33+ // wait so we can determine if it's a new file or editing an existing file
34+ return
35+ }
36+
37+ const accessAllowed = cline . rooIgnoreController ?. validateAccess ( relPath )
38+ if ( ! accessAllowed ) {
39+ await cline . say ( "rooignore_error" , relPath )
40+ pushToolResult ( formatResponse . toolError ( formatResponse . rooIgnoreError ( relPath ) ) )
41+ return
42+ }
43+
44+ // Check if file exists using cached map or fs.access
45+ let fileExists : boolean
46+ if ( cline . diffViewProvider . editType !== undefined ) {
47+ fileExists = cline . diffViewProvider . editType === "modify"
48+ } else {
49+ const absolutePath = path . resolve ( cline . cwd , relPath )
50+ fileExists = await fileExistsAtPath ( absolutePath )
51+ cline . diffViewProvider . editType = fileExists ? "modify" : "create"
52+ }
53+
54+ // pre-processing newContent for partial streaming
55+ if ( newContent . startsWith ( "```" ) ) {
56+ newContent = newContent . split ( "\n" ) . slice ( 1 ) . join ( "\n" ) . trim ( )
57+ }
58+ if ( newContent . endsWith ( "```" ) ) {
59+ newContent = newContent . split ( "\n" ) . slice ( 0 , - 1 ) . join ( "\n" ) . trim ( )
60+ }
61+ if ( ! cline . api . getModel ( ) . id . includes ( "claude" ) ) {
62+ newContent = unescapeHtmlEntities ( newContent )
63+ }
64+
65+ const fullPath = relPath ? path . resolve ( cline . cwd , removeClosingTag ( "path" , relPath ) ) . toPosix ( ) : ""
66+ const isOutsideWorkspace = isPathOutsideWorkspace ( fullPath )
67+
68+ const sharedMessageProps : ClineSayTool = {
69+ tool : fileExists ? "editedExistingFile" : "newFileCreated" ,
70+ path : getReadablePath ( cline . cwd , removeClosingTag ( "path" , relPath ) ) ,
71+ content : newContent ,
72+ isOutsideWorkspace,
73+ }
74+
75+ try {
76+ // update gui message
77+ const partialMessage = JSON . stringify ( sharedMessageProps )
78+ await cline . ask ( "tool" , partialMessage , block . partial ) . catch ( ( ) => { } )
79+
80+ // update editor
81+ if ( ! cline . diffViewProvider . isEditing ) {
82+ // open the editor and prepare to stream content in
83+ await cline . diffViewProvider . open ( relPath )
84+ }
85+
86+ // editor is open, stream content in
87+ await cline . diffViewProvider . update (
88+ everyLineHasLineNumbers ( newContent ) ? stripLineNumbers ( newContent ) : newContent ,
89+ false ,
90+ )
91+
92+ return
93+ } catch ( error ) {
94+ await handleError ( "writing file" , error )
95+ await cline . diffViewProvider . reset ( )
96+ return
97+ }
98+ }
99+
100+ // Handle non-partial blocks - full validation and processing
101+ if ( ! relPath ) {
102+ cline . consecutiveMistakeCount ++
103+ cline . recordToolError ( "write_to_file" )
104+ pushToolResult ( await cline . sayAndCreateMissingParamError ( "write_to_file" , "path" ) )
105+ await cline . diffViewProvider . reset ( )
32106 return
33107 }
34108
35- const accessAllowed = cline . rooIgnoreController ?. validateAccess ( relPath )
109+ if ( newContent === undefined ) {
110+ cline . consecutiveMistakeCount ++
111+ cline . recordToolError ( "write_to_file" )
112+ pushToolResult ( await cline . sayAndCreateMissingParamError ( "write_to_file" , "content" ) )
113+ await cline . diffViewProvider . reset ( )
114+ return
115+ }
36116
117+ const accessAllowed = cline . rooIgnoreController ?. validateAccess ( relPath )
37118 if ( ! accessAllowed ) {
38119 await cline . say ( "rooignore_error" , relPath )
39120 pushToolResult ( formatResponse . toolError ( formatResponse . rooIgnoreError ( relPath ) ) )
@@ -42,7 +123,6 @@ export async function writeToFileTool(
42123
43124 // Check if file exists using cached map or fs.access
44125 let fileExists : boolean
45-
46126 if ( cline . diffViewProvider . editType !== undefined ) {
47127 fileExists = cline . diffViewProvider . editType === "modify"
48128 } else {
@@ -66,7 +146,7 @@ export async function writeToFileTool(
66146 }
67147
68148 // Determine if the path is outside the workspace
69- const fullPath = relPath ? path . resolve ( cline . cwd , removeClosingTag ( "path" , relPath ) ) : ""
149+ const fullPath = relPath ? path . resolve ( cline . cwd , removeClosingTag ( "path" , relPath ) ) . toPosix ( ) : ""
70150 const isOutsideWorkspace = isPathOutsideWorkspace ( fullPath )
71151
72152 const sharedMessageProps : ClineSayTool = {
@@ -77,176 +157,138 @@ export async function writeToFileTool(
77157 }
78158
79159 try {
80- if ( block . partial ) {
81- // update gui message
82- const partialMessage = JSON . stringify ( sharedMessageProps )
83- await cline . ask ( "tool" , partialMessage , block . partial ) . catch ( ( ) => { } )
160+ if ( predictedLineCount === undefined ) {
161+ cline . consecutiveMistakeCount ++
162+ cline . recordToolError ( "write_to_file" )
84163
85- // update editor
86- if ( ! cline . diffViewProvider . isEditing ) {
87- // open the editor and prepare to stream content in
88- await cline . diffViewProvider . open ( relPath )
89- }
164+ // Calculate the actual number of lines in the content
165+ const actualLineCount = newContent . split ( "\n" ) . length
90166
91- // editor is open, stream content in
92- await cline . diffViewProvider . update (
93- everyLineHasLineNumbers ( newContent ) ? stripLineNumbers ( newContent ) : newContent ,
94- false ,
167+ // Check if this is a new file or existing file
168+ const isNewFile = ! fileExists
169+
170+ // Check if diffStrategy is enabled
171+ const diffStrategyEnabled = ! ! cline . diffStrategy
172+
173+ // Use more specific error message for line_count that provides guidance based on the situation
174+ await cline . say (
175+ "error" ,
176+ `Roo tried to use write_to_file${
177+ relPath ? ` for '${ relPath . toPosix ( ) } '` : ""
178+ } but the required parameter 'line_count' was missing or truncated after ${ actualLineCount } lines of content were written. Retrying...`,
95179 )
96180
181+ pushToolResult (
182+ formatResponse . toolError (
183+ formatResponse . lineCountTruncationError ( actualLineCount , isNewFile , diffStrategyEnabled ) ,
184+ ) ,
185+ )
186+ await cline . diffViewProvider . revertChanges ( )
97187 return
98- } else {
99- if ( ! relPath ) {
100- cline . consecutiveMistakeCount ++
101- cline . recordToolError ( "write_to_file" )
102- pushToolResult ( await cline . sayAndCreateMissingParamError ( "write_to_file" , "path" ) )
103- await cline . diffViewProvider . reset ( )
104- return
105- }
106-
107- if ( ! newContent ) {
108- cline . consecutiveMistakeCount ++
109- cline . recordToolError ( "write_to_file" )
110- pushToolResult ( await cline . sayAndCreateMissingParamError ( "write_to_file" , "content" ) )
111- await cline . diffViewProvider . reset ( )
112- return
113- }
188+ }
114189
115- if ( ! predictedLineCount ) {
116- cline . consecutiveMistakeCount ++
117- cline . recordToolError ( "write_to_file" )
190+ cline . consecutiveMistakeCount = 0
118191
119- // Calculate the actual number of lines in the content
120- const actualLineCount = newContent . split ( "\n" ) . length
192+ // if isEditingFile false, that means we have the full contents of the file already.
193+ // it's important to note how cline function works, you can't make the assumption that the block.partial conditional will always be called since it may immediately get complete, non-partial data. So cline part of the logic will always be called.
194+ // in other words, you must always repeat the block.partial logic here
195+ if ( ! cline . diffViewProvider . isEditing ) {
196+ // show gui message before showing edit animation
197+ const partialMessage = JSON . stringify ( sharedMessageProps )
198+ await cline . ask ( "tool" , partialMessage , true ) . catch ( ( ) => { } ) // sending true for partial even though it's not a partial, cline shows the edit row before the content is streamed into the editor
199+ await cline . diffViewProvider . open ( relPath )
200+ }
121201
122- // Check if this is a new file or existing file
123- const isNewFile = ! fileExists
202+ await cline . diffViewProvider . update (
203+ everyLineHasLineNumbers ( newContent ) ? stripLineNumbers ( newContent ) : newContent ,
204+ true ,
205+ )
124206
125- // Check if diffStrategy is enabled
126- const diffStrategyEnabled = ! ! cline . diffStrategy
207+ await delay ( 300 ) // wait for diff view to update
208+ cline . diffViewProvider . scrollToFirstDiff ( )
127209
128- // Use more specific error message for line_count that provides guidance based on the situation
129- await cline . say (
130- "error" ,
131- `Roo tried to use write_to_file${
132- relPath ? ` for '${ relPath . toPosix ( ) } '` : ""
133- } but the required parameter 'line_count' was missing or truncated after ${ actualLineCount } lines of content were written. Retrying...`,
134- )
210+ // Check for code omissions before proceeding
211+ if ( detectCodeOmission ( cline . diffViewProvider . originalContent || "" , newContent , predictedLineCount ) ) {
212+ if ( cline . diffStrategy ) {
213+ await cline . diffViewProvider . revertChanges ( )
135214
136215 pushToolResult (
137216 formatResponse . toolError (
138- formatResponse . lineCountTruncationError ( actualLineCount , isNewFile , diffStrategyEnabled ) ,
217+ `Content appears to be truncated (file has ${
218+ newContent . split ( "\n" ) . length
219+ } lines but was predicted to have ${ predictedLineCount } lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`,
139220 ) ,
140221 )
141- await cline . diffViewProvider . revertChanges ( )
142222 return
143- }
144-
145- cline . consecutiveMistakeCount = 0
146-
147- // if isEditingFile false, that means we have the full contents of the file already.
148- // it's important to note how cline function works, you can't make the assumption that the block.partial conditional will always be called since it may immediately get complete, non-partial data. So cline part of the logic will always be called.
149- // in other words, you must always repeat the block.partial logic here
150- if ( ! cline . diffViewProvider . isEditing ) {
151- // show gui message before showing edit animation
152- const partialMessage = JSON . stringify ( sharedMessageProps )
153- await cline . ask ( "tool" , partialMessage , true ) . catch ( ( ) => { } ) // sending true for partial even though it's not a partial, cline shows the edit row before the content is streamed into the editor
154- await cline . diffViewProvider . open ( relPath )
155- }
156-
157- await cline . diffViewProvider . update (
158- everyLineHasLineNumbers ( newContent ) ? stripLineNumbers ( newContent ) : newContent ,
159- true ,
160- )
161-
162- await delay ( 300 ) // wait for diff view to update
163- cline . diffViewProvider . scrollToFirstDiff ( )
164-
165- // Check for code omissions before proceeding
166- if ( detectCodeOmission ( cline . diffViewProvider . originalContent || "" , newContent , predictedLineCount ) ) {
167- if ( cline . diffStrategy ) {
168- await cline . diffViewProvider . revertChanges ( )
169-
170- pushToolResult (
171- formatResponse . toolError (
172- `Content appears to be truncated (file has ${
173- newContent . split ( "\n" ) . length
174- } lines but was predicted to have ${ predictedLineCount } lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`,
175- ) ,
223+ } else {
224+ vscode . window
225+ . showWarningMessage (
226+ "Potential code truncation detected. This happens when the AI reaches its max output limit." ,
227+ "Follow cline guide to fix the issue" ,
176228 )
177- return
178- } else {
179- vscode . window
180- . showWarningMessage (
181- "Potential code truncation detected. cline happens when the AI reaches its max output limit." ,
182- "Follow cline guide to fix the issue" ,
183- )
184- . then ( ( selection ) => {
185- if ( selection === "Follow cline guide to fix the issue" ) {
186- vscode . env . openExternal (
187- vscode . Uri . parse (
188- "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments" ,
189- ) ,
190- )
191- }
192- } )
193- }
229+ . then ( ( selection ) => {
230+ if ( selection === "Follow cline guide to fix the issue" ) {
231+ vscode . env . openExternal (
232+ vscode . Uri . parse (
233+ "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments" ,
234+ ) ,
235+ )
236+ }
237+ } )
194238 }
239+ }
195240
196- const completeMessage = JSON . stringify ( {
197- ...sharedMessageProps ,
198- content : fileExists ? undefined : newContent ,
199- diff : fileExists
200- ? formatResponse . createPrettyPatch ( relPath , cline . diffViewProvider . originalContent , newContent )
201- : undefined ,
202- } satisfies ClineSayTool )
203-
204- const didApprove = await askApproval ( "tool" , completeMessage )
205-
206- if ( ! didApprove ) {
207- await cline . diffViewProvider . revertChanges ( )
208- return
209- }
241+ const completeMessage = JSON . stringify ( {
242+ ...sharedMessageProps ,
243+ content : fileExists ? undefined : newContent ,
244+ diff : fileExists
245+ ? formatResponse . createPrettyPatch ( relPath , cline . diffViewProvider . originalContent , newContent )
246+ : undefined ,
247+ } satisfies ClineSayTool )
210248
211- const { newProblemsMessage , userEdits , finalContent } = await cline . diffViewProvider . saveChanges ( )
249+ const didApprove = await askApproval ( "tool" , completeMessage )
212250
213- // Track file edit operation
214- if ( relPath ) {
215- await cline . fileContextTracker . trackFileContext ( relPath , "roo_edited" as RecordSource )
216- }
251+ if ( ! didApprove ) {
252+ await cline . diffViewProvider . revertChanges ( )
253+ return
254+ }
217255
218- cline . didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request
256+ const { newProblemsMessage , userEdits , finalContent } = await cline . diffViewProvider . saveChanges ( )
219257
220- if ( userEdits ) {
221- await cline . say (
222- "user_feedback_diff" ,
223- JSON . stringify ( {
224- tool : fileExists ? "editedExistingFile" : "newFileCreated" ,
225- path : getReadablePath ( cline . cwd , relPath ) ,
226- diff : userEdits ,
227- } satisfies ClineSayTool ) ,
228- )
258+ // Track file edit operation
259+ if ( relPath ) {
260+ await cline . fileContextTracker . trackFileContext ( relPath , "roo_edited" as RecordSource )
261+ }
229262
230- pushToolResult (
231- `The user made the following updates to your content:\n\n${ userEdits } \n\n` +
232- `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${ relPath . toPosix ( ) } . Here is the full, updated content of the file, including line numbers:\n\n` +
233- `<final_file_content path="${ relPath . toPosix ( ) } ">\n${ addLineNumbers (
234- finalContent || "" ,
235- ) } \n</final_file_content>\n\n` +
236- `Please note:\n` +
237- `1. You do not need to re-write the file with these changes, as they have already been applied.\n` +
238- `2. Proceed with the task using this updated file content as the new baseline.\n` +
239- `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` +
240- `${ newProblemsMessage } ` ,
241- )
242- } else {
243- pushToolResult ( `The content was successfully saved to ${ relPath . toPosix ( ) } .${ newProblemsMessage } ` )
244- }
263+ cline . didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request
245264
246- await cline . diffViewProvider . reset ( )
265+ if ( userEdits ) {
266+ await cline . say (
267+ "user_feedback_diff" ,
268+ JSON . stringify ( {
269+ tool : fileExists ? "editedExistingFile" : "newFileCreated" ,
270+ path : getReadablePath ( cline . cwd , relPath ) ,
271+ diff : userEdits ,
272+ } satisfies ClineSayTool ) ,
273+ )
247274
248- return
275+ pushToolResult (
276+ `The user made the following updates to your content:\n\n${ userEdits } \n\n` +
277+ `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${ relPath . toPosix ( ) } . Here is the full, updated content of the file, including line numbers:\n\n` +
278+ `<final_file_content path="${ relPath . toPosix ( ) } ">\n${ addLineNumbers (
279+ finalContent || "" ,
280+ ) } \n</final_file_content>\n\n` +
281+ `Please note:\n` +
282+ `1. You do not need to re-write the file with these changes, as they have already been applied.\n` +
283+ `2. Proceed with the task using this updated file content as the new baseline.\n` +
284+ `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` +
285+ `${ newProblemsMessage } ` ,
286+ )
287+ } else {
288+ pushToolResult ( `The content was successfully saved to ${ relPath . toPosix ( ) } .${ newProblemsMessage } ` )
249289 }
290+
291+ await cline . diffViewProvider . reset ( )
250292 } catch ( error ) {
251293 await handleError ( "writing file" , error )
252294 await cline . diffViewProvider . reset ( )
0 commit comments