(params: EditFileParams, task: Task, callbacks: ToolCallbacks)
| 137 | private partialToolAskRelPath: string | undefined |
| 138 | |
| 139 | async execute(params: EditFileParams, task: Task, callbacks: ToolCallbacks): Promise<void> { |
| 140 | // Coerce old_string/new_string to handle malformed native tool calls where they could be non-strings. |
| 141 | // In native mode, malformed calls can pass numbers/objects; normalize those to "" to avoid later crashes. |
| 142 | const file_path = params.file_path |
| 143 | const old_string = typeof params.old_string === "string" ? params.old_string : "" |
| 144 | const new_string = typeof params.new_string === "string" ? params.new_string : "" |
| 145 | const expected_replacements = params.expected_replacements ?? 1 |
| 146 | const { askApproval, handleError, pushToolResult } = callbacks |
| 147 | let relPathForErrorHandling: string | undefined |
| 148 | let operationPreviewForErrorHandling: string | undefined |
| 149 | |
| 150 | const finalizePartialToolAskIfNeeded = async (relPath: string): Promise<void> => { |
| 151 | if (!this.didSendPartialToolAsk) { |
| 152 | return |
| 153 | } |
| 154 | |
| 155 | if (this.partialToolAskRelPath && this.partialToolAskRelPath !== relPath) { |
| 156 | return |
| 157 | } |
| 158 | |
| 159 | const absolutePath = path.resolve(task.cwd, relPath) |
| 160 | const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath) |
| 161 | |
| 162 | const sharedMessageProps: ClineSayTool = { |
| 163 | tool: "appliedDiff", |
| 164 | path: getReadablePath(task.cwd, relPath), |
| 165 | diff: operationPreviewForErrorHandling, |
| 166 | isOutsideWorkspace, |
| 167 | } |
| 168 | |
| 169 | // Finalize the existing partial tool ask row so the UI doesn't get stuck in a spinner state. |
| 170 | await task.ask("tool", JSON.stringify(sharedMessageProps), false).catch(() => {}) |
| 171 | } |
| 172 | |
| 173 | const recordFailureForPathAndMaybeEscalate = async (relPath: string, formattedError: string): Promise<void> => { |
| 174 | const currentCount = (task.consecutiveMistakeCountForEditFile.get(relPath) || 0) + 1 |
| 175 | task.consecutiveMistakeCountForEditFile.set(relPath, currentCount) |
| 176 | |
| 177 | if (currentCount >= 2) { |
| 178 | await task.say("diff_error", formattedError) |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | try { |
| 183 | // Validate required parameters |
| 184 | if (!file_path) { |
| 185 | task.consecutiveMistakeCount++ |
| 186 | task.recordToolError("edit_file") |
| 187 | task.didToolFailInCurrentTurn = true |
| 188 | pushToolResult(await task.sayAndCreateMissingParamError("edit_file", "file_path")) |
| 189 | return |
| 190 | } |
| 191 | |
| 192 | // Determine relative path - file_path can be absolute or relative |
| 193 | let relPath: string |
| 194 | if (path.isAbsolute(file_path)) { |
| 195 | relPath = path.relative(task.cwd, file_path) |
| 196 | } else { |
nothing calls this directly
no test coverage detected