@@ -39,7 +39,7 @@ import { GlobalFileNames } from "../shared/globalFileNames"
3939import { defaultModeSlug , getModeBySlug , getFullModeDetails , isToolAllowedForMode } from "../shared/modes"
4040import { EXPERIMENT_IDS , experiments as Experiments , ExperimentId } from "../shared/experiments"
4141import { formatLanguage } from "../shared/language"
42- import { ToolParamName , ToolResponse } from "../shared/tools"
42+ import { ToolParamName , ToolResponse , DiffStrategy } from "../shared/tools"
4343
4444// services
4545import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
@@ -52,7 +52,6 @@ import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../servi
5252// integrations
5353import { DIFF_VIEW_URI_SCHEME , DiffViewProvider } from "../integrations/editor/DiffViewProvider"
5454import { findToolName , formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
55- import { ExitCodeDetails , TerminalProcess } from "../integrations/terminal/TerminalProcess"
5655import { Terminal } from "../integrations/terminal/Terminal"
5756import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry"
5857
@@ -92,8 +91,8 @@ import { RooIgnoreController } from "./ignore/RooIgnoreController"
9291import { type AssistantMessageContent , parseAssistantMessage } from "./assistant-message"
9392import { truncateConversationIfNeeded } from "./sliding-window"
9493import { ClineProvider } from "./webview/ClineProvider"
95- import { DiffStrategy , getDiffStrategy } from "./diff/DiffStrategy"
9694import { validateToolUse } from "./mode-validator"
95+ import { MultiSearchReplaceDiffStrategy } from "./diff/strategies/multi-search-replace"
9796
9897type UserContent = Array < Anthropic . Messages . ContentBlockParam >
9998
@@ -247,8 +246,7 @@ export class Cline extends EventEmitter<ClineEvents> {
247246 telemetryService . captureTaskCreated ( this . taskId )
248247 }
249248
250- // Initialize diffStrategy based on current state.
251- this . updateDiffStrategy ( experiments ?? { } )
249+ this . diffStrategy = new MultiSearchReplaceDiffStrategy ( this . fuzzyMatchThreshold )
252250
253251 onCreated ?.( this )
254252
@@ -283,15 +281,6 @@ export class Cline extends EventEmitter<ClineEvents> {
283281 return getWorkspacePath ( path . join ( os . homedir ( ) , "Desktop" ) )
284282 }
285283
286- // Add method to update diffStrategy.
287- async updateDiffStrategy ( experiments : Partial < Record < ExperimentId , boolean > > ) {
288- this . diffStrategy = getDiffStrategy ( {
289- model : this . api . getModel ( ) . id ,
290- experiments,
291- fuzzyMatchThreshold : this . fuzzyMatchThreshold ,
292- } )
293- }
294-
295284 // Storing task to disk for history
296285
297286 private async ensureTaskDirectoryExists ( ) : Promise < string > {
@@ -308,9 +297,11 @@ export class Cline extends EventEmitter<ClineEvents> {
308297 private async getSavedApiConversationHistory ( ) : Promise < Anthropic . MessageParam [ ] > {
309298 const filePath = path . join ( await this . ensureTaskDirectoryExists ( ) , GlobalFileNames . apiConversationHistory )
310299 const fileExists = await fileExistsAtPath ( filePath )
300+
311301 if ( fileExists ) {
312302 return JSON . parse ( await fs . readFile ( filePath , "utf8" ) )
313303 }
304+
314305 return [ ]
315306 }
316307
@@ -378,7 +369,8 @@ export class Cline extends EventEmitter<ClineEvents> {
378369 const tokenUsage = this . getTokenUsage ( )
379370 this . emit ( "taskTokenUsageUpdated" , this . taskId , tokenUsage )
380371
381- const taskMessage = this . clineMessages [ 0 ] // first message is always the task say
372+ const taskMessage = this . clineMessages [ 0 ] // First message is always the task say
373+
382374 const lastRelevantMessage =
383375 this . clineMessages [
384376 findLastIndex (
@@ -913,11 +905,6 @@ export class Cline extends EventEmitter<ClineEvents> {
913905 }
914906
915907 async abortTask ( isAbandoned = false ) {
916- // if (this.abort) {
917- // console.log(`[subtasks] already aborted task ${this.taskId}.${this.instanceId}`)
918- // return
919- // }
920-
921908 console . log ( `[subtasks] aborting task ${ this . taskId } .${ this . instanceId } ` )
922909
923910 // Will stop any autonomously running promises.
@@ -951,159 +938,6 @@ export class Cline extends EventEmitter<ClineEvents> {
951938
952939 // Tools
953940
954- async executeCommandTool ( command : string , customCwd ?: string ) : Promise < [ boolean , ToolResponse ] > {
955- let workingDir : string
956- if ( ! customCwd ) {
957- workingDir = this . cwd
958- } else if ( path . isAbsolute ( customCwd ) ) {
959- workingDir = customCwd
960- } else {
961- workingDir = path . resolve ( this . cwd , customCwd )
962- }
963-
964- // Check if directory exists
965- try {
966- await fs . access ( workingDir )
967- } catch ( error ) {
968- return [ false , `Working directory '${ workingDir } ' does not exist.` ]
969- }
970-
971- const terminalInfo = await TerminalRegistry . getOrCreateTerminal ( workingDir , ! ! customCwd , this . taskId )
972-
973- // Update the working directory in case the terminal we asked for has
974- // a different working directory so that the model will know where the
975- // command actually executed:
976- workingDir = terminalInfo . getCurrentWorkingDirectory ( )
977-
978- const workingDirInfo = workingDir ? ` from '${ workingDir . toPosix ( ) } '` : ""
979- terminalInfo . terminal . show ( ) // weird visual bug when creating new terminals (even manually) where there's an empty space at the top.
980- let userFeedback : { text ?: string ; images ?: string [ ] } | undefined
981- let didContinue = false
982- let completed = false
983- let result : string = ""
984- let exitDetails : ExitCodeDetails | undefined
985- const { terminalOutputLineLimit = 500 } = ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
986-
987- const sendCommandOutput = async ( line : string , terminalProcess : TerminalProcess ) : Promise < void > => {
988- try {
989- const { response, text, images } = await this . ask ( "command_output" , line )
990- if ( response === "yesButtonClicked" ) {
991- // proceed while running
992- } else {
993- userFeedback = { text, images }
994- }
995- didContinue = true
996- terminalProcess . continue ( ) // continue past the await
997- } catch {
998- // This can only happen if this ask promise was ignored, so ignore this error
999- }
1000- }
1001-
1002- const process = terminalInfo . runCommand ( command , {
1003- onLine : ( line , process ) => {
1004- if ( ! didContinue ) {
1005- sendCommandOutput ( Terminal . compressTerminalOutput ( line , terminalOutputLineLimit ) , process )
1006- } else {
1007- this . say ( "command_output" , Terminal . compressTerminalOutput ( line , terminalOutputLineLimit ) )
1008- }
1009- } ,
1010- onCompleted : ( output ) => {
1011- result = output ?? ""
1012- completed = true
1013- } ,
1014- onShellExecutionComplete : ( details ) => {
1015- exitDetails = details
1016- } ,
1017- onNoShellIntegration : async ( message ) => {
1018- await this . say ( "shell_integration_warning" , message )
1019- } ,
1020- } )
1021-
1022- await process
1023-
1024- // Wait for a short delay to ensure all messages are sent to the webview
1025- // This delay allows time for non-awaited promises to be created and
1026- // for their associated messages to be sent to the webview, maintaining
1027- // the correct order of messages (although the webview is smart about
1028- // grouping command_output messages despite any gaps anyways)
1029- await delay ( 50 )
1030-
1031- result = Terminal . compressTerminalOutput ( result , terminalOutputLineLimit )
1032-
1033- // keep in case we need it to troubleshoot user issues, but this should be removed in the future
1034- // if everything looks good:
1035- console . debug (
1036- "[execute_command status]" ,
1037- JSON . stringify (
1038- {
1039- completed,
1040- userFeedback,
1041- hasResult : result . length > 0 ,
1042- exitDetails,
1043- terminalId : terminalInfo . id ,
1044- workingDir : workingDirInfo ,
1045- isTerminalBusy : terminalInfo . busy ,
1046- } ,
1047- null ,
1048- 2 ,
1049- ) ,
1050- )
1051-
1052- if ( userFeedback ) {
1053- await this . say ( "user_feedback" , userFeedback . text , userFeedback . images )
1054- return [
1055- true ,
1056- formatResponse . toolResult (
1057- `Command is still running in terminal ${ terminalInfo . id } ${ workingDirInfo } .${
1058- result . length > 0 ? `\nHere's the output so far:\n${ result } ` : ""
1059- } \n\nThe user provided the following feedback:\n<feedback>\n${ userFeedback . text } \n</feedback>`,
1060- userFeedback . images ,
1061- ) ,
1062- ]
1063- } else if ( completed ) {
1064- let exitStatus : string = ""
1065- if ( exitDetails !== undefined ) {
1066- if ( exitDetails . signal ) {
1067- exitStatus = `Process terminated by signal ${ exitDetails . signal } (${ exitDetails . signalName } )`
1068- if ( exitDetails . coreDumpPossible ) {
1069- exitStatus += " - core dump possible"
1070- }
1071- } else if ( exitDetails . exitCode === undefined ) {
1072- result += "<VSCE exit code is undefined: terminal output and command execution status is unknown.>"
1073- exitStatus = `Exit code: <undefined, notify user>`
1074- } else {
1075- if ( exitDetails . exitCode !== 0 ) {
1076- exitStatus += "Command execution was not successful, inspect the cause and adjust as needed.\n"
1077- }
1078- exitStatus += `Exit code: ${ exitDetails . exitCode } `
1079- }
1080- } else {
1081- result += "<VSCE exitDetails == undefined: terminal output and command execution status is unknown.>"
1082- exitStatus = `Exit code: <undefined, notify user>`
1083- }
1084-
1085- let workingDirInfo : string = workingDir ? ` within working directory '${ workingDir . toPosix ( ) } '` : ""
1086- const newWorkingDir = terminalInfo . getCurrentWorkingDirectory ( )
1087-
1088- if ( newWorkingDir !== workingDir ) {
1089- workingDirInfo += `\nNOTICE: Your command changed the working directory for this terminal to '${ newWorkingDir . toPosix ( ) } ' so you MUST adjust future commands accordingly because they will be executed in this directory`
1090- }
1091-
1092- const outputInfo = `\nOutput:\n${ result } `
1093- return [
1094- false ,
1095- `Command executed in terminal ${ terminalInfo . id } ${ workingDirInfo } . ${ exitStatus } ${ outputInfo } ` ,
1096- ]
1097- } else {
1098- return [
1099- false ,
1100- `Command is still running in terminal ${ terminalInfo . id } ${ workingDirInfo } .${
1101- result . length > 0 ? `\nHere's the output so far:\n${ result } ` : ""
1102- } \n\nYou will be updated on the terminal status and new output in the future.`,
1103- ]
1104- }
1105- }
1106-
1107941 async * attemptApiRequest ( previousApiReqIndex : number , retryAttempt : number = 0 ) : ApiStream {
1108942 let mcpHub : McpHub | undefined
1109943
@@ -1566,6 +1400,7 @@ export class Cline extends EventEmitter<ClineEvents> {
15661400 }
15671401
15681402 if ( ! block . partial ) {
1403+ this . recordToolUsage ( block . name )
15691404 telemetryService . captureToolUsage ( this . taskId , block . name )
15701405 }
15711406
@@ -2699,16 +2534,19 @@ export class Cline extends EventEmitter<ClineEvents> {
26992534 return getApiMetrics ( combineApiRequests ( combineCommandSequences ( this . clineMessages . slice ( 1 ) ) ) )
27002535 }
27012536
2702- public recordToolUsage ( { toolName, success = true } : { toolName : ToolName ; success ?: boolean } ) {
2537+ public recordToolUsage ( toolName : ToolName ) {
27032538 if ( ! this . toolUsage [ toolName ] ) {
27042539 this . toolUsage [ toolName ] = { attempts : 0 , failures : 0 }
27052540 }
27062541
27072542 this . toolUsage [ toolName ] . attempts ++
2708-
2709- if ( ! success ) {
2710- this . toolUsage [ toolName ] . failures ++
2543+ }
2544+ public recordToolError ( toolName : ToolName ) {
2545+ if ( ! this . toolUsage [ toolName ] ) {
2546+ this . toolUsage [ toolName ] = { attempts : 0 , failures : 0 }
27112547 }
2548+
2549+ this . toolUsage [ toolName ] . failures ++
27122550 }
27132551
27142552 public getToolUsage ( ) {
0 commit comments