diff --git a/src/core/tools/__tests__/writeToFileTool.spec.ts b/src/core/tools/__tests__/writeToFileTool.spec.ts index 78e60cbaa5..0844cb66fe 100644 --- a/src/core/tools/__tests__/writeToFileTool.spec.ts +++ b/src/core/tools/__tests__/writeToFileTool.spec.ts @@ -6,7 +6,6 @@ import { fileExistsAtPath } from "../../../utils/fs" import { detectCodeOmission } from "../../../integrations/editor/detect-omission" import { isPathOutsideWorkspace } from "../../../utils/pathUtils" import { getReadablePath } from "../../../utils/path" -import { unescapeHtmlEntities } from "../../../utils/text-normalization" import { everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text" import { ToolUse, ToolResponse } from "../../../shared/tools" import { writeToFileTool } from "../writeToFileTool" @@ -54,10 +53,6 @@ vi.mock("../../../utils/path", () => ({ getReadablePath: vi.fn().mockReturnValue("test/path.txt"), })) -vi.mock("../../../utils/text-normalization", () => ({ - unescapeHtmlEntities: vi.fn().mockImplementation((content) => content), -})) - vi.mock("../../../integrations/misc/extract-text", () => ({ everyLineHasLineNumbers: vi.fn().mockReturnValue(false), stripLineNumbers: vi.fn().mockImplementation((content) => content), @@ -104,7 +99,6 @@ describe("writeToFileTool", () => { const mockedDetectCodeOmission = detectCodeOmission as MockedFunction const mockedIsPathOutsideWorkspace = isPathOutsideWorkspace as MockedFunction const mockedGetReadablePath = getReadablePath as MockedFunction - const mockedUnescapeHtmlEntities = unescapeHtmlEntities as MockedFunction const mockedEveryLineHasLineNumbers = everyLineHasLineNumbers as MockedFunction const mockedStripLineNumbers = stripLineNumbers as MockedFunction const mockedPathResolve = path.resolve as MockedFunction @@ -124,7 +118,6 @@ describe("writeToFileTool", () => { mockedDetectCodeOmission.mockReturnValue(false) mockedIsPathOutsideWorkspace.mockReturnValue(false) mockedGetReadablePath.mockReturnValue("test/path.txt") - mockedUnescapeHtmlEntities.mockImplementation((content) => content) mockedEveryLineHasLineNumbers.mockReturnValue(false) mockedStripLineNumbers.mockImplementation((content) => content) @@ -288,20 +281,19 @@ describe("writeToFileTool", () => { expect(mockCline.diffViewProvider.update).toHaveBeenCalledWith("", true) }) - it("unescapes HTML entities for non-Claude models", async () => { + it("preserves HTML entities for all models", async () => { + // Test with non-Claude model mockCline.api.getModel.mockReturnValue({ id: "gpt-4" }) - await executeWriteFileTool({ content: "<test>" }) + expect(mockCline.diffViewProvider.update).toHaveBeenCalledWith("<test>", true) - expect(mockedUnescapeHtmlEntities).toHaveBeenCalledWith("<test>") - }) + // Reset mocks + mockCline.diffViewProvider.update.mockClear() - it("skips HTML unescaping for Claude models", async () => { + // Test with Claude model mockCline.api.getModel.mockReturnValue({ id: "claude-3" }) - - await executeWriteFileTool({ content: "<test>" }) - - expect(mockedUnescapeHtmlEntities).not.toHaveBeenCalled() + await executeWriteFileTool({ content: "&hello"" }) + expect(mockCline.diffViewProvider.update).toHaveBeenCalledWith("&hello"", true) }) it("strips line numbers from numbered content", async () => { diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index dcdd134624..b766220cdf 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -11,7 +11,6 @@ import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } f import { formatResponse } from "../prompts/responses" import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" -import { unescapeHtmlEntities } from "../../utils/text-normalization" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" export async function applyDiffToolLegacy( @@ -25,9 +24,8 @@ export async function applyDiffToolLegacy( const relPath: string | undefined = block.params.path let diffContent: string | undefined = block.params.diff - if (diffContent && !cline.api.getModel().id.includes("claude")) { - diffContent = unescapeHtmlEntities(diffContent) - } + // HTML entities should be preserved exactly as provided + // to ensure accurate diff matching and prevent unintended changes const sharedMessageProps: ClineSayTool = { tool: "appliedDiff", diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts index d0fe655750..ed3703d8c5 100644 --- a/src/core/tools/multiApplyDiffTool.ts +++ b/src/core/tools/multiApplyDiffTool.ts @@ -11,7 +11,6 @@ import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } f import { formatResponse } from "../prompts/responses" import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" -import { unescapeHtmlEntities } from "../../utils/text-normalization" import { parseXmlForDiff } from "../../utils/xml" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" import { applyDiffToolLegacy } from "./applyDiffTool" @@ -421,13 +420,9 @@ Original error: ${errorMessage}` let successCount = 0 let formattedError = "" - // Pre-process all diff items for HTML entity unescaping if needed - const processedDiffItems = !cline.api.getModel().id.includes("claude") - ? diffItems.map((item) => ({ - ...item, - content: item.content ? unescapeHtmlEntities(item.content) : item.content, - })) - : diffItems + // HTML entities should be preserved exactly as provided + // to ensure accurate diff matching and prevent unintended changes + const processedDiffItems = diffItems // Apply all diffs at once with the array-based method const diffResult = (await cline.diffStrategy?.applyDiff(originalContent, processedDiffItems)) ?? { diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index 5abd96a20a..e74a185735 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -13,7 +13,6 @@ import { stripLineNumbers, everyLineHasLineNumbers } from "../../integrations/mi import { getReadablePath } from "../../utils/path" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { detectCodeOmission } from "../../integrations/editor/detect-omission" -import { unescapeHtmlEntities } from "../../utils/text-normalization" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" @@ -83,9 +82,8 @@ export async function writeToFileTool( newContent = newContent.split("\n").slice(0, -1).join("\n") } - if (!cline.api.getModel().id.includes("claude")) { - newContent = unescapeHtmlEntities(newContent) - } + // HTML entities should be preserved exactly as provided + // to ensure content is written exactly as intended // Determine if the path is outside the workspace const fullPath = relPath ? path.resolve(cline.cwd, removeClosingTag("path", relPath)) : ""